Skip to content

Version 3 adds breaking type annotations that do not comply with mypy #3226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gothicVI opened this issue Mar 18, 2025 · 26 comments
Open

Version 3 adds breaking type annotations that do not comply with mypy #3226

gothicVI opened this issue Mar 18, 2025 · 26 comments
Assignees
Labels
bug something broken P2 considered for next cycle regression this used to work

Comments

@gothicVI
Copy link
Contributor

gothicVI commented Mar 18, 2025

With the release of dash 3.0 our CI/CD fails for stuff that used to work.
Here's a minimum working example:

from dash import Dash, dcc, html
from dash.dependencies import Input, Output
from typing import Callable

app = Dash(__name__)

def create_layout() -> html.Div:
    return html.Div([
        dcc.Input(id='input-text', type='text', value='', placeholder='Enter text'),
        html.Div(id='output-text')
    ])  

app.layout = create_layout

@app.callback(Output('output-text', 'children'), Input('input-text', 'value'))
def update_output(value: str) -> str:
    return f'You entered: {value}'

if __name__ == '__main__':
    app.run(debug=True, port=9000)

running mypy on this file results in:

$ mypy t.py --strict
t.py:1: error: Skipping analyzing "dash": module is installed, but missing library stubs or py.typed marker  [import-untyped]
t.py:2: error: Skipping analyzing "dash.dependencies": module is installed, but missing library stubs or py.typed marker  [import-untyped]
t.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
t.py:15: error: Untyped decorator makes function "update_output" untyped  [misc]
Found 3 errors in 1 file (checked 1 source file)

which we used to solve by adding # type: ignore[misc] to every callback call and by adding

[[tool.mypy.overrides]]
module = [
  "dash.*",
  "dash_ag_grid",
  "dash_bootstrap_components.*",
  "plotly.*",
]
ignore_missing_imports = true

to our pyproject.toml.

However, when updating to version 3, we get:

$ mypy --strict t.py 
t.py:8: error: Returning Any from function declared to return "Div"  [no-any-return]
t.py:13: error: Property "layout" defined in "Dash" is read-only  [misc]
t.py:15: error: Call to untyped function "Input" in typed context  [no-untyped-call]
t.py:15: error: Call to untyped function "Output" in typed context  [no-untyped-call]
t.py:15: error: Call to untyped function "callback" in typed context  [no-untyped-call]
t.py:15: note: Error code "no-untyped-call" not covered by "type: ignore" comment
Found 5 errors in 1 file (checked 1 source file)

without changing anything else.

This can't be intended behavior, right? How to fix that?

community post: https://community.plotly.com/t/dash-3-0-fails-mypy/91308

@T4rk1n
Copy link
Contributor

T4rk1n commented Mar 18, 2025

I only had trouble with mypy, the typing generation tests were originally designed with mypy but at some point it updated and couldn't find types anymore. There is a py.typed file in dash package.

I suggest switching over to pyright, that is what is used in vscode and 10x better imo.

@gothicVI
Copy link
Contributor Author

gothicVI commented Mar 18, 2025

I'll give that a look but we can not simply dump mypy from our code base.
This needs to be fixed not on a linter level because there is something wrong in the code.

E.g. looking at https://github.com/plotly/dash/blob/dev/dash/dependencies.py#L29 and following it is clear, that there are no type annotations for Input, Output, State, and their superclass DashDependency. So there's no point in having py.typed in the code base if the code is not typed.

@gvwilson gvwilson added bug something broken P2 considered for next cycle labels Mar 18, 2025
@gothicVI
Copy link
Contributor Author

There are several issues with the claimed "typedness" for dash. In a fresh virtual environment I get:

$ mypy .
dash-core-components is not a valid Python package name
$ cd dash
mypy: "types.py" shadows library module "types"
note: A user-defined top-level module with name "types" is not supported
$ cd ..
$ pyright 
# see https://gist.github.com/gothicVI/34eb60a2a2f61b02d6a2c50027355a11 for very long output
$ cd dash
$ pyright 
# see https://gist.github.com/gothicVI/b2f30b6cbfbc648909eb3f82c4560c2c for lengthy output

which tells me that the packages violates python conventions and is not typed.
@T4rk1n how does that work for you?

@gvwilson
Copy link
Contributor

Hi @gothicVI - thanks for filing this issue. We're aware that there's still work to do on Dash typing, and given resources at our end we have to take it in stages. If you would like to post a PR with a handful of the changes you would like to see, we can review that right away, and it will help us figure out how much effort it will be to solve the whole problem. Thanks - @gvwilson

@gvwilson gvwilson self-assigned this Mar 20, 2025
@gvwilson gvwilson changed the title [BUG] Dash 3.0 fails mypy add/change type annotations to satisfy mypy and other tools Mar 20, 2025
@gothicVI
Copy link
Contributor Author

Hi @gvwilson thanks for taking a look. To be completely honest I do not know how to start contributing in that regard because I do not fully understand the internal workings yet. I might give it a look but can't prommis any PR.

I think the most needed changes would be to figure out what exactly breaks mypy when changing to version 3. Don't get me wrong, mypy did complain earlier due to there not being any annotations or stub files so we commented that out as mentioned above. But the release of version 3 did break that which it shouldn't. If needed revert the changes for the moment!

After that I'd suggest sticking to the general python conventions like no - in module names and not shadowing of built in libraries like types. I'd also suggest switching from setup.py to pyproject.toml and to include mypy and potentially other linters or analyzers into the regular process. Set up pre-commit and a CI pipeline for, e.g., ruff, mypy, and pyright and have every pushed commit be checked.

Please do not get me wrong. I love this project and this should not be understood as a personal attack against anyone who contributed typing code! Yet I think it is quite dangerous to push a major version upgrade without thorough testing - especially in such a large project that so many other projects rely on.

@gvwilson
Copy link
Contributor

HI @gothicVI - thanks very much for your quick feedback. Reverting these changes isn't an option - there are a great many features that our users have wanted for a long time - so I'd like to focus on ways to move forward. If you can start a PR to convert from setup.py to pyproject.toml (which ought to be more straightforward than adding typing) that would be a great help. Thanks - @gvwilson

@gothicVI
Copy link
Contributor Author

@gvwilson I didn't mean to revert the release of version 3 but just the commits that break the typing system.

Either way, can you or anyone else confirm that the new version breaks mypy at all?

Independently I've looked into converting the setup.py approach to a pyproject.toml approach.
However, I can't even build the current project state due to dash/labextension/dist/dash-jupyterlab.tgz being required but missing. What's happening here? How does the build process even work?

My attempt at conversion can be found here: gothicVI@ed59db7
I'd start a PR once I'm sure the project can be built at all.

@gothicVI gothicVI changed the title add/change type annotations to satisfy mypy and other tools Version 3 adds breaking type annotations that do not comply with mypy Mar 25, 2025
@ndrezn ndrezn added the regression this used to work label Mar 31, 2025
@gothicVI
Copy link
Contributor Author

@gvwilson any news?

@gvwilson
Copy link
Contributor

Nothing yet, but I'll be sure to post when there is.

@gvwilson
Copy link
Contributor

gvwilson commented Apr 1, 2025

@gothicVI can you please tell us more about the build issue you're having? when I go through the steps in CONTRIBUTING.md on MacOS everything seems to build correctly (though there are currently some failing tests - I'm looking into those). What platform are you on and are any steps breaking before you hit the jupyterlab issue? Separately, can you please create a PR with your pyproject.toml file that doesn't delete the existing requirements/* files? We can work on getting that in place in parallel with typing changes. thanks - @gvwilson

@gothicVI
Copy link
Contributor Author

gothicVI commented Apr 1, 2025

@gvwilson I honestly can no longer reproduce the issue I had but it would fail due to dash/labextension/dist/dash-jupyterlab.tgz not existing. Following the same steps though I'm running into issues because orjson does not build with python 3.13 even though that should be supported as per the project. The problem here might be that dash does not support 3.13 itself.

I'll try again with 3.11.

Irrespectively, can you reproduce the initial issue though?

@T4rk1n
Copy link
Contributor

T4rk1n commented Apr 4, 2025

Investigated the type checkers, I found that mypy doesn't even resolve the html/dcc types that were added while it works fine with pyright/vscode/pycharm.

This error results from that:

t.py:8: error: Returning Any from function declared to return "Div"  [no-any-return]

It happens that mypy can't resolve the @_explicitize_args we put in front of the __init__ of the components and ends up with Any instead of the component.

Adding type vars can resolve the return type, but break the init types. Can probably refactor the decorator behavior inside the __new__ of ComponentMeta.

error: Property "layout" defined in "Dash" is read-only  [misc]

Has been fixed in 3.0.2 by community PR: #3249

t.py:15: error: Call to untyped function "Input" in typed context  [no-untyped-call]
t.py:15: error: Call to untyped function "Output" in typed context  [no-untyped-call]

The dash dependencies will be typed in next dash release by #3259

t.py:15: error: Call to untyped function "callback" in typed context  [no-untyped-call]

This can be fixed/silenced with def callback(...) -> Callable[..., Any], also added in #3259, the global @callback contains more types for the arguments that should be added in the app one. Maybe a type var can be used, but seems to work well when the function is typed.

@gothicVI
Copy link
Contributor Author

gothicVI commented Apr 4, 2025

@T4rk1n that looks great. Any change to add mypy to the standard CI suite as well? I use pyright as well but mypy is the de-facto standard as it's the official type checker.

@T4rk1n
Copy link
Contributor

T4rk1n commented Apr 4, 2025

@T4rk1n that looks great. Any change to add mypy to the standard CI suite as well? I use pyright as well but mypy is the de-facto standard as it's the official type checker.

I'll probably add a basic test for mypy to not throw error with a basic app and make sure it resolve the types of the components. But for the rest of the codebase it should run pyright as mypy is very slow in comparison for the same kind of linting.

#3259 Also fix the component types, that may reveal actual typing errors in layout when using mypy.

@gvwilson
Copy link
Contributor

gvwilson commented Apr 8, 2025

@gothicVI please have a look at #3254 and let me know what you think?

@gothicVI
Copy link
Contributor Author

Some things seem to be fixed but other not so much:

/tmp $ git clone [email protected]:plotly/dash.git
/tmp $ python3 -m venv venv
/tmp $ source venv/bin/activate
(venv) /tmp $ pip install mypy
(venv) /tmp $ cd dash
(venv) /tmp/dash $ git switch pyright-typing-fixes
(venv) /tmp/dash $ pip install -e .
(venv) /tmp/dash $ pip list
Package            Version   Editable project location
------------------ --------- -------------------------
blinker            1.9.0
certifi            2025.1.31
charset-normalizer 3.4.1
click              8.1.8
dash               3.0.2     /tmp/dash
Flask              3.0.3
idna               3.10
importlib_metadata 8.6.1
itsdangerous       2.2.0
Jinja2             3.1.6
MarkupSafe         3.0.2
mypy               1.15.0
mypy-extensions    1.0.0
narwhals           1.34.1
nest-asyncio       1.6.0
packaging          24.2
pip                24.3.1
plotly             6.0.1
requests           2.32.3
retrying           1.3.4
setuptools         78.1.0
six                1.17.0
typing_extensions  4.13.1
urllib3            2.3.0
Werkzeug           3.0.6
zipp               3.21.0
(venv) /tmp/dash $ cd ..
(venv) /tmp $ cat t.py
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
from typing import Callable

app = Dash(__name__)

def create_layout() -> html.Div:
    return html.Div([
        dcc.Input(id='input-text', type='text', value='', placeholder='Enter text'),
        html.Div(id='output-text')
    ])  

app.layout = create_layout

@app.callback(Output('output-text', 'children'), Input('input-text', 'value'))
def update_output(value: str) -> str:
    return f'You entered: {value}'

if __name__ == '__main__':
    app.run(debug=True, port=9000)
(venv) /tmp $ mypy --strict t.py
t.py:1: error: Module "dash" has no attribute "Dash"  [attr-defined]
t.py:1: error: Module "dash" has no attribute "dcc"  [attr-defined]
t.py:1: error: Module "dash" has no attribute "html"  [attr-defined]
t.py:2: error: Cannot find implementation or library stub for module named "dash.dependencies"  [import-not-found]
t.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
t.py:15: error: Untyped decorator makes function "update_output" untyped  [misc]
Found 5 errors in 1 file (checked 1 source file)

@T4rk1n
Copy link
Contributor

T4rk1n commented Apr 10, 2025

You need to build the components and run it from where it can find dash if running locally.

I get no more error when running this example.

@gothicVI
Copy link
Contributor Author

@T4rk1n damn good point. I have 0 experience with JS/TS though and am somewhat stuck with

$ npm run build  

> build
> run-s private::build.*

sh: line 1: run-s: command not found

while

$ node -v
v23.9.0
$ npm -v
11.2.0

@gvwilson
Copy link
Contributor

@gothicVI what OS are you on?

@gothicVI
Copy link
Contributor Author

@gvwilson Manjaro Linux

@gvwilson
Copy link
Contributor

OK, I don't understand why that wouldn't work. Can you please check
out a fresh copy of the dash repo, go into it, and then run the
following and send me all the output? I'm [email protected].

$ npx lerna clean
$ rm -rf venv node_modules
$ python -m venv venv
$ . venv/bin/activate
$ pip install -e '.[dev,testing,ci]' # note the quotes
$ nvm use lts/iron
$ npm i
$ npm run build
$ npm run setup-tests.py

@gothicVI
Copy link
Contributor Author

@gvwilson here you go https://gist.github.com/gothicVI/8a8cb53ba0be2f060d30ebf004edbaeb
This fails at installing parts of the ci dependency due to python 3.13 not being supported...

I'll try and give it a shot with a machine running python 3.11 tomorrow.

@gothicVI
Copy link
Contributor Author

@gvwilson might have fixed it with a small diff:

$ git diff
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 96495aa4..43553cca 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -7,7 +7,7 @@ ipython<9.0.0
 mimesis<=11.1.0
 mock==4.0.3
 numpy<=1.26.3
-orjson==3.10.3
+orjson==3.10.16
 openpyxl
 pandas>=1.4.0
 pyarrow

This allowed me to build the dependencies. For the full output see https://gist.github.com/gothicVI/5410fa76c5b177b048f8809a28b4773c

Finally, leaving the cloned dash directory to check t.py still gets me:

$ mypy --strict t.py
t.py:8: error: Returning Any from function declared to return "Div"  [no-any-return]
t.py:15: error: Call to untyped function "callback" in typed context  [no-untyped-call]
t.py:15: error: Untyped decorator makes function "update_output" untyped  [misc]
t.py:15: error: Call to untyped function "Output" in typed context  [no-untyped-call]
t.py:15: error: Call to untyped function "Input" in typed context  [no-untyped-call]
Found 5 errors in 1 file (checked 1 source file)

@gvwilson
Copy link
Contributor

thanks @gothicVI - as noted earlier, we're focusing on pyright for type checking. If there are changes you can PR that will satisfy mypy without breaking pyright, I'd be happy to prioritize review.

@gothicVI
Copy link
Contributor Author

hi @gvwilson the initial issue is not solved as you can see. All I solved was building dash using python 3.13. Sure I can write a PR for that little change but you mentioned that the problem was solved on your side with the PR you wrote. I can not reproduce that. Please help me out and let me know what you did to make the suggested changes be compliant with my MWE. Thanks!

@gothicVI
Copy link
Contributor Author

@gvwilson you had mentioned that with your patch you didn't see the issues. What exactly did you do?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken P2 considered for next cycle regression this used to work
Projects
None yet
Development

No branches or pull requests

4 participants