-
Notifications
You must be signed in to change notification settings - Fork 26
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
Improve type annotation of codebase, add CI for mypy, fix a string format in test #191
Conversation
+ workaround a mypy bug: python/mypy#1465
Codecov ReportAttention:
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #191 +/- ##
==========================================
- Coverage 82.35% 81.00% -1.35%
==========================================
Files 47 47
Lines 2420 2517 +97
Branches 339 365 +26
==========================================
+ Hits 1993 2039 +46
- Misses 356 383 +27
- Partials 71 95 +24
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
yikes. didn't look in detail but may be you just needed a config like https://mypy.readthedocs.io/en/stable/config_file.html#confval-ignore_missing_imports ? |
BTW, checkout references to automated tools in datalad/datalad#6884 (comment) - might come handy |
Then when someday the packages start providing a real |
FWIW, an advice from a Debian developer -- use |
|
ok, thank you for the effort! to add typing we should test typing ;) I am ok to limit for now to just this selected set of files, but if you would like challenge -- try to annotate more (then adjust tox.ini to remove skips and subselection of modules) |
$ mypy --ignore-missing-imports . Success: no issues found in 52 source files
this useless parameter confuse me
all files can be included in the test already (there was just a 1 line glitch in setup.py)
|
I'm packing for an Holliday & will be offline ... second leg of the trip is DebCamp in Cambridge on Thursday... |
python -m pip install --upgrade tox | ||
|
||
- name: Run type checker | ||
run: tox -e typing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW -- we better adopt workflow like https://github.com/dandi/zarr_checksum/blob/2a30d1b482e96aa475f0a480f813be6ab8f9a1c9/.github/workflows/test.yml -- I like how it centralizes other testing as well to that single workflow + tox. WDYT?
@jwodder -- would you be so kind to review this PR and provide your recommendations for further typing annotation in this project? |
meanwhile, as green and per above comment -- let's merge. Feedback will be considered for future work. |
pyupgrade can automaticaly replace the Optional and Union later when baseline is bumped. |
the py.typed flag can now be added to this project (and included in release artefacts) |
@@ -11,6 +11,7 @@ | |||
import os | |||
import sys | |||
from functools import wraps | |||
from typing import Any, Dict, List, Optional, Tuple, Union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would strongly recommend dropping support for Python 3.6 (It's been EOL for almost two years), after which you can add from __future__ import annotations
to all the source files and then change annotations like Union[None, str, Tuple[str, str]]
to None | str | tuple[str, str]
, get rid of the quotes around some other annotations, and get rid of most typing
imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could make use of https://github.com/asottile/pyupgrade here indeed. Submitted #194
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much for this review. I greatly enjoy learning new tricks.
I found this great reference that match what you said about from __future ...
that I did t full grasped at first time:
https://adamj.eu/tech/2021/05/21/python-type-hints-how-to-upgrade-syntax-with-pyupgrade/
path: Optional[str] = None, | ||
version: Union[None, str, Tuple[str, str]] = None, | ||
cite_module: bool = False, | ||
tags: List[str] = ['implementation'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a list — a mutable structure — as a default value to a Python function is a well-known footgun. Do one of the following instead:
- Change
tags
totags: Optional[List[str]] = None
and, inside the function, settags
to["implementation"]
if it'sNone
. - Change tags to
tags: Sequence[str] = ("implementation",)
(Note the trailing comma to create a one-element tuple.) If this breaks any of your code, then using a list here was a bug to begin with.
@@ -141,7 +149,7 @@ def objname(self): | |||
else: | |||
return None | |||
|
|||
def __contains__(self, entry): | |||
def __contains__(self, entry: 'Citation') -> bool: # Self PEP673 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If, as the comment suggests, entry
is only supposed to be of the exact same type as self
(not even allowing for subclassing), then it should be annotated with Self
instead of "Citation"
. As Self
is only used as an annotation, and if you take my advice above about from __future__ import annotations
, you can import Self
by simply adding this to the top of the file:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import Self
typing_extensions
is a dependency of mypy, so you won't even have to adjust the project's dependencies!
@@ -253,7 +268,8 @@ def cite(self, entry, **kwargs): | |||
citation = self.citations[citation_key] | |||
except KeyError: | |||
self.citations[citation_key] = citation = Citation(entry_, **kwargs) | |||
assert(citation.key == citation_key) | |||
assert type(citation) is Citation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you want to prohibit subclassing, this would be better written as assert isinstance(citation, Citation)
.
@@ -454,7 +479,11 @@ def __init__(self, collector, outputs="stdout,pickle", fn=DUECREDIT_FILE): | |||
] | |||
|
|||
@staticmethod | |||
def _get_output_handler(type_, collector, fn=None): | |||
def _get_output_handler( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest annotating type_
as Literal["stdout", "stderr", "pickle"]
and using @typing.overload
to select the correct return type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or an Enum, I don't know if this is a public API
any(filter(lambda x: x.cite_module, package_citations)) or \ | ||
any(filter(lambda x: _is_contained(package, x), cited_modobj)): | ||
any(filter(lambda x: x.cite_module, package_citations)) or any(filter(lambda x: _is_contained(package, x), cited_modobj)): # type: ignore | ||
# WIP, mypy doesn(t understand the 'ignore' on a continuated line |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Doesn't" is spelled with an apostrophe, not a parenthesis.
# https://github.com/python/mypy/issues/9242#issuecomment-667586397 | ||
# https://github.com/spack/spack/pull/35640 | ||
if sys.platform == "win32": | ||
OSExceptions = (OSError, WindowsError) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WindowsError
has been a subclass of OSError
since Python 3.3. Just use OSError
by itself.
|
||
from .. import __main__, __version__ | ||
from .. import due | ||
|
||
if TYPE_CHECKING: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why guard this behind if TYPE_CHECKING
? Unless you're supporting pre-7.0 pytest (Why would you?), you can just import these types unconditionally.
mypy | ||
types-requests | ||
commands = | ||
mypy --ignore-missing-imports {posargs} duecredit/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I advise against using --ignore-missing-imports
, as will also swallow errors about importing from a module that doesn't exist. If the goal is to deal with dependencies that lack typing information, the better way is to add an override to the mypy config for each such dependency like so:
[mypy-packagename.*]
ignore_missing_imports = True
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to ship mininimalistic .pyi type hints for cité citeproc and others ?
I mean as an interim solution ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so.
def test_noincorrect_import_if_no_lxml_numpy(monkeypatch, kwargs, env, stubbed_env): | ||
def test_noincorrect_import_if_no_lxml_numpy( | ||
monkeypatch: 'MonkeyPatch', | ||
kwargs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These arguments aren't annotated. I suggest adding allow_incomplete_defs = False
to the project's mypy configuration so that such problems are caught.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I m working with mypy --strict
that list 220-something todo ... these items are not forgtoten
This is just an example on how/why add annotations, they enable a new kind of linting with
mypy
; they also make it easier for newcomers to contribute to bigger projects;To do it more logicaly, I would first need to spend more time on
citeproc-py
, but I see there's globaly much more to do on this other project. (but I cheated withsudo touch /usr/lib/python3/dist-packages/citeproc/py.typed
)this includes a workaround to a mypy bug: python/mypy#1465