-
Notifications
You must be signed in to change notification settings - Fork 82
WIP: Fix most mypy issues #343
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
base: main
Are you sure you want to change the base?
Conversation
| # TODO: Raise a more descriptive error when cmd_obj is None ? | ||
| cmd_obj = cast(Command, self.distribution.get_command_obj(command, create)) |
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.
Decision to be taken here.
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.
My instinct - get_command_obj should raise the error and always return Command... unless there's a case where None is legitimately expected, in which case this should be wrapped in a helper to raise that error and set the expectation.
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 just did a quick scan through the distutils and setuptools codebase and there's not a single call to get_finalized_command(..., create=False). Maybe the create flag is just cruft that can be eliminated.
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 opened an exploratory PR deprecating (unused, but not removed, for backward compatibility) the create arg: #377
|
mypy:
pyright:
@jaraco I can't help but feel this may be revealing an actual issue? Or at least an incorrect assumption.
|
…into re-enable-mypy
|
I split up all individual mypy code fixes into their own PRs for ease of review. |
jaraco
left a comment
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.
Leaving an initial review. I've gone over most of this before I realized that portions have been extracted elsewhere.
| # TODO: Raise a more descriptive error when cmd_obj is None ? | ||
| cmd_obj = cast(Command, self.distribution.get_command_obj(command, create)) |
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.
My instinct - get_command_obj should raise the error and always return Command... unless there's a case where None is legitimately expected, in which case this should be wrapped in a helper to raise that error and set the expectation.
| # TODO: Raise a more descriptive error when cmd_obj is None ? | ||
| cmd_obj = cast(Command, self.distribution.get_command_obj(command, create)) |
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 just did a quick scan through the distutils and setuptools codebase and there's not a single call to get_finalized_command(..., create=False). Maybe the create flag is just cruft that can be eliminated.
| if not dry_run: | ||
| try: | ||
| name.mkdir(mode=mode, parents=True, exist_ok=True) | ||
| except OSError as exc: | ||
| raise DistutilsFileError(f"could not create '{name}': {exc.args[-1]}") |
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 really don't love how this code gets more nested. What is it about mypy that's forcing different logic here?
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.
This one could be ignored on the line itself without being unsafe.
It's an unused expression, both mypy and pyright warn here because of the lack of assignement, it "looks like" a possible mistake because the result of the expression is not used for anything. But it's just a conditional execution pattern.
It's definitely more of a lint than a type error.
| raise DistutilsArgError(msg) | ||
|
|
||
| for opt, val in opts: | ||
| value: int | str = val |
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.
This is gross. val and value are the same thing. Mypy is forcing some idiosyncratic code. There's got to be a better way (cast opts.__iter__ to the expected types?).
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.
Actually, this may be resolved in mypy 1.18: https://mypy.readthedocs.io/en/stable/changelog.html#flexible-variable-definitions-update
Before 1.18 the flag wasn't stabilized and called allow-redefinition-new (I wasn't aware of its existence either)
Edit: No it doesn't help here because of the difference of scope
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.
There's this related request python/mypy#15988
Which would allow val: str | int = val which is much safer than a cast.
As for casting, I could do
# Unsafely casting to avoid using a different variable
# python/mypy#15988 could allow `val: str | int = val`
for opt, val in cast("list[tuple[str, int | str]]", opts):| assert isinstance(cc, str) | ||
| assert isinstance(cxx, str) | ||
| assert isinstance(cflags, str) | ||
| assert isinstance(ccshared, str) | ||
| assert isinstance(ldshared, str) | ||
| assert isinstance(ldcxxshared, str) | ||
| assert isinstance(ar_flags, str) | ||
| assert isinstance(shlib_suffix, str) |
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 not sure these assertions will always pass. There have been some bugs around these being undefined in unusual environments. Maybe it makes sense to catch these conditions early. Do we have good reason to believe that non-string values returned here would never be viable (would be breaking anyway)?
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.
(same comment in the split PR, for tracking: #366 (comment))
| grp: ModuleType | None = None | ||
| pwd: ModuleType | None = None | ||
| try: | ||
| import grp | ||
| import pwd | ||
| except ImportError: | ||
| grp = pwd = None | ||
| pass |
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 love the asymmetry of this syntax, but if this is the new Pythonic style, I guess it's acceptable. Is it?
It's starting to feel like we need a meta language to express Python imports. It would be so much nicer if we could simply express:
opt_import grp
opt_import pwd
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.
There's definitely much to improve when it comes to static type checkers and conditional imports... (especially with ImportError/ModuleNotFoundError)
Hopefully ty and/or pyrefly will come along with improved semantics and force existing checkers to improve there.
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.
It's possible that this can be solved using mypy 1.18's allow-redefinition
Edit: No it doesn't help here.
| except ImportError: | ||
| grp = pwd = None | ||
| pass |
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'd be tempted to use contextlib.suppress(ImportError) to eliminate the no-op block... except that requires a prior import, which may lead to problems. Also, I give it a 50% chance that mypy recognizes that approach.
|
@jaraco Thanks, I took the time to go through your review even though most things here have been split-up in smaller self-contained PRs. (we can always link back to discussions here) I think most of your comments/concern are extracted to #368, so you can probably ignore that one for now. As for the failing CI, there's a related issue opened at setuptools: pypa/setuptools#5094 |
No description provided.