-
Notifications
You must be signed in to change notification settings - Fork 152
Add type checking to entire versioneer project (excluding tests) #367
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
Add type checking to entire versioneer project (excluding tests) #367
Conversation
Using 'Any' for the CommandClass variables bypasses some complications with the type system, for example setuptools and distutils Command classes are different, as well as the variable gets complicated with when used as a base class because its assigning an object not a 'Type' per mypy scanning.
class develop(develop): | ||
def run(self): | ||
class develop(_develop): | ||
def run(self) -> None: # type: ignore[override] |
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.
What was the original return type? Or was it marked final?
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.
So this one was a bit confusing at first.
The upstream types-setuptools
defines it like this: https://github.com/python/typeshed/blob/acfde4e40b3a75aeff1d6324a4eb9adbca5579e2/stubs/setuptools/setuptools/command/develop.pyi#L12 (basically using the # type: ignore[override]
as well. Because its the same method definition.
They need this in that upstream because the setuptool develop
command depends on its easy_install
command, which added a flag for the "Deprecation" warning it uses: https://github.com/python/typeshed/blob/acfde4e40b3a75aeff1d6324a4eb9adbca5579e2/stubs/setuptools/setuptools/command/easy_install.pyi#L54
So because the method definitions differ, we need the ignore[override]
here. Nothing actually breaks in implementation though!
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 wonder if it makes more sense to just subclass from Command
, since we don't actually do anything but raise an exception. Then no need to ignore.
from .header import LONG_VERSION_PY, get_root, get_config_from_root # --STRIP DURING BUILD | ||
from .get_versions import get_versions # --STRIP DURING BUILD | ||
from .from_file import write_to_version_file # --STRIP DURING BUILD | ||
|
||
|
||
def get_cmdclass(cmdclass=None): | ||
def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None): |
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.
Shouldn't this be:
def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None): | |
def get_cmdclass(cmdclass: Optional[Dict[str, Type[Command]]] = None): |
@@ -71,12 +72,12 @@ def run(self): | |||
|
|||
# we override different "build_py" commands for both environments | |||
if 'build_py' in cmds: | |||
_build_py = cmds['build_py'] | |||
_build_py: Any = cmds['build_py'] |
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.
_build_py: Any = cmds['build_py'] | |
_build_py: Type[Command] = cmds['build_py'] |
... and so on.
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 did this (and the other Any
stuff in this file) due to errors like the following that were harder to detangle:
versioneer.py:1934: error: Incompatible import of "_build_py" (imported name has type "Type[build_py]", local name has type "Type[Command]") [assignment]
versioneer.py:1936: error: Variable "_build_py" is not valid as a type [valid-type]
versioneer.py:1936: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
versioneer.py:1936: error: Invalid base class "_build_py" [misc]
From looking things up I might have been able to solve it if we required typing_extensions
installed to use versioneer, but I didn't want to add that as a requirement here - at least right now.
I may be able to solve this using TypeVar and similar logic, but I decided that was best tackled separately.
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.
Fair enough. Any
marks an opportunity for refinement, so it won't be hard to spot.
@@ -124,9 +142,9 @@ class NotThisMethod(Exception): | |||
HANDLERS: Dict[str, Dict[str, Callable]] = {} | |||
|
|||
|
|||
def register_vcs_handler(vcs, method): # decorator | |||
def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator |
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.
Not sure if it would actually capture any bad calls, but we can use a TypeVar
to let the checker know that it's a decorator that doesn't change the type of the decorated object:
def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator | |
F = TypeVar('F', bound=Callable) | |
def register_vcs_handler(vcs: str, method: str) -> Callable[[F], F]: # decorator |
At least, I think this should work. Haven't tried it.
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 agree this would be a lot better than what I have here, however I'm considering doing even better than this in a future patch. I didn't want to tie up this larger improvement with further improving this decorator (mainly since users of the library will be unlikely to ever need to touch this decorator.)
That said, if you feel strongly I can do that extra improvement as part of this PR.
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 feel strongly.
"""Create decorator to mark a method as the handler of a VCS.""" | ||
def decorate(f): | ||
def decorate(f: Callable) -> Callable: |
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.
def decorate(f: Callable) -> Callable: | |
def decorate(f: F) -> F: |
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.
See comment at #367 (comment)
@effigies - Are we good to merge given our discussion, or is there something more I should do here? (also, do you want me to continue to hold off on merging until you've approved going foward? - I see I have the button but I generally like to merge after review when it makes sense) |
Ah, sorry. For some reason I thought this was in your court. I'm okay with this as-is. Merging. |
This adds enough of the type checking, (and fixes a few minor type issues) for the entirety of the versioneer repository.
There are still better type checking things we could do, but this is a great start and should allow at least some type check abilities of dependent projects