Skip to content
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

Additional type hints added to stub openpyxl #9487

Closed
wants to merge 6 commits into from

Conversation

ArnabRollin
Copy link
Contributor

Reason for change

The VS Code Python static type-checker does not recognise

Here is an example:

from openpyxl import load_workbook

wb = load_workbook("myxl.xlsx") # Error: Function 'load_workbook' is partially unknown

Mypy doesn't seem to find the types-openpyxl package

(Working-with-Excel) -> pipenv install types-openpyxl
Installing types-openpyxl...
Pipfile.lock (43417e) out of date, updating to (83c742)...
Locking [packages] dependencies...
Locking [dev-packages] dependencies...
Updated Pipfile.lock (c868c9d2bef5b5f5f3c9b17280f5f7a15a9a9522fd50d8cb558d1eb0e083c742)!
Installing dependencies from Pipfile.lock (83c742)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
(Working-with-Excel) -> mypy app.py
app.py:1: error: Library stubs not installed for "openpyxl"  [import]
app.py:1: note: Hint: "python3 -m pip install types-openpyxl"
app.py:1: note: (or run "mypy --install-types" to install all missing stub packages)
app.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports

Please point out if I am wrong anywhere.

ArnabRollin and others added 3 commits January 10, 2023 16:56
Updated classes `Workbook`, `Worksheet`, `Cell`. Type checking works in VS Code
@github-actions

This comment has been minimized.

@Avasam
Copy link
Collaborator

Avasam commented Jan 10, 2023

(I'm not a typeshed maintainer, but I contribute a lot here)
The VS Code Python static type-checker, named "Pylance", uses https://github.com/microsoft/pyright behind the scene. The reason you get partially unknown errors is because you're using some strict typing settings.

There's a big semantic difference between "Unknown" and "Any". One says to be careful because the type isn't checked. The other says you can truly use anything. Nowadays typeshed uses Any as an escape hatch to say "we can't accurately represent the type under the current type system".
mypy doesn't have an Unknown pseudo-type, it just uses Any.
That's also why, for semantic reasons, in typeshed we prefer to leave parameters and return types untyped. Or otherwise use the alias Incomplete (from _typeshed import Incomplete). See https://github.com/python/typeshed/blob/main/CONTRIBUTING.md#incomplete-stubs


To fix the issue on pyright/pylance. You could:

To resolve the issue with mypy. Since you already installed types-openpyxl, make sure that you are using mypy from your virtual environment. I am not personally familiar with pipenv, but it's possible that mypy on your path isn't pointing to the one in your project's environment. You can try running pipenv install mypy and python3 -m mypy app.py.


pyright comes with a copy of typeshed's third-party stubs. mypy doesn't and you have to install them manually.
pyright also comes with a copy of https://github.com/microsoft/python-type-stubs/

openpyxl is also very special. All those class members using descriptors are actually properties in disguise. With runtime type-checking and type coercion. I'm currently working on merging the stubs from https://github.com/microsoft/python-type-stubs/tree/main/openpyxl

@ArnabRollin
Copy link
Contributor Author

The reason you get partially unknown errors is because you're using some strict typing settings.

I usually like to put my code in strict mode. But, when I found the type stubs, I thought they were a bit incomplete... So I updated these.

@AlexWaygood
Copy link
Member

But, when I found the type stubs, I thought they were a bit incomplete... So I updated these.

They are indeed :)

And contributions are very welcome! Thank you for the PR!

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pandas (https://github.com/pandas-dev/pandas)
+ pandas/io/excel/_openpyxl.py:73: error: Argument 1 to "load_workbook" has incompatible type "IO[bytes]"; expected "str"  [arg-type]
+ pandas/io/excel/_openpyxl.py:101: error: Argument 1 to "save" of "Workbook" has incompatible type "IO[bytes]"; expected "str"  [arg-type]
+ pandas/io/excel/_openpyxl.py:554: error: Argument 1 to "load_workbook" has incompatible type "Union[Union[str, PathLike[str]], ReadBuffer[bytes]]"; expected "str"  [arg-type]

@AlexWaygood AlexWaygood self-requested a review January 11, 2023 22:33
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! There's some good stuff in here, but I'm afraid there are also quite a few errors that we need to sort out. Remarks below:

@@ -19,60 +25,60 @@ TYPE_ERROR: str
TYPE_FORMULA_CACHE_STRING: str
VALID_TYPES: Incomplete

def get_type(t, value): ...
def get_time_format(t): ...
def get_type(t: Any, value: Any) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • t looks like it has to be a class of some kind, due to this line here, where it's used as a key in a dictionary that only has classes as keys: https://foss.heptapod.net/openpyxl/openpyxl/-/blob/branch/3.0/openpyxl/cell/cell.py#L73
  • value could be literally any object, so it's better to use object here rather than Any. Any is an "escape hatch" in the type system for things that are truly inexpressible. It's very unsafe, so it's better to use object whenever possible.
  • The function always either returns a str or None.
Suggested change
def get_type(t: Any, value: Any) -> Any: ...
def get_type(t: type, value: object) -> str | None: ...

def get_type(t, value): ...
def get_time_format(t): ...
def get_type(t: Any, value: Any) -> Any: ...
def get_time_format(t: Any) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_time_format(t: Any) -> Any: ...
def get_time_format(t: datetime) -> str: ...

worksheet: Worksheet,
row: int | None = ...,
column: int | None = ...,
value: str | float | int | datetime | None = ...,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 484 specifies that functions annotated as accepting float should also accept int, so int is redundant in this union

Suggested change
value: str | float | int | datetime | None = ...,
value: str | float | datetime | None = ...,

@property
def value(self): ...
def value(self) -> str | float | int | datetime | None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def value(self) -> str | float | int | datetime | None: ...
def value(self) -> str | float | datetime | None: ...

@value.setter
def value(self, value) -> None: ...
def value(self, value: str | float | int | datetime | None) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def value(self, value: str | float | int | datetime | None) -> None: ...
def value(self, value: str | float | datetime | None) -> None: ...

def delete_cols(self, idx: int, amount: int = ...) -> None: ...
def move_range(self, cell_range: CellRange, rows: int = ..., cols: int = ..., translate: bool = ...) -> None: ...
@property
def print_title_rows(self) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave unannotated if you don't know the type.

@property
def print_title_cols(self): ...
def print_title_cols(self) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave unannotated if you don't know the type.

Comment on lines +132 to +134
def print_titles(self) -> Any: ...
@property
def print_area(self): ...
def print_area(self) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave unannotated if you don't know the type.

@print_area.setter
def print_area(self, value) -> None: ...
def print_area(self, value: str) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@print_title_cols.setter
def print_title_cols(self, cols) -> None: ...
def print_title_cols(self, cols: int) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mynhardtburger
Copy link
Contributor

@ArnabRollin are you still working on this, or can I offer my help to implement the change requests?

@AlexWaygood
Copy link
Member

AlexWaygood commented Feb 19, 2023

@mynhardtburger would you like to pick up where @ArnabRollin picked off? You'd be very welcome to! (@ArnabRollin, you'll be listed as a co-author, of course.)

@mynhardtburger
Copy link
Contributor

@AlexWaygood I would like to try...
I assume the process would be to fork ArnabRollin's work, work through your suggested changes and then submit a new PR?

This is my first time contributing to a public project.

@AlexWaygood
Copy link
Member

@AlexWaygood I would like to try... I assume the process would be to fork ArnabRollin's work, work through your suggested changes and then submit a new PR?

This is my first time contributing to a public project.

Yes, that would be the process! :)

@Avasam
Copy link
Collaborator

Avasam commented Feb 19, 2023

@AlexWaygood I would like to try... I assume the process would be to fork ArnabRollin's work, work through your suggested changes and then submit a new PR?

This is my first time contributing to a public project.

If you do, please tag me in the new PR so I can update the description in #9511

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants