You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently, pip uses mypy via a pre-commithook and with some custom configuration specified in the setup.cfgfile.
However, due to a surprising difference in behavior in mypy between specifying individual files vs specifying directories to be checked, the way that pre-commit calls mypy ends up unexpectedly hiding 51 type errors that are present in the files being checked and that are prohibited by the mypy configuration specified in setup.cfg.
Environment
pip version: Issue found on commit 8d042c448718355447f133c440f7a931a5ae57c0, which is the tip of the master branch as of the time of this post.
Python version: Python 3.8.5
OS: Ubuntu 20.04.1 LTS (focal) inside WSL2
mypy: version 0.790, same as specified in the pre-commithook config
Expected behavior
The following two commands should produce equivalent results i.e. neither of them finds any type errors on the current master branch (exact commit hash specified above):
pre-commit run mypy --all-files
mypy src/
Actual behavior & How to Reproduce
Running pre-commit run mypy --all-files finds no type errors, as expected — this is enforced by CI.
However, running mypy src/ finds 51 type errors. Adding the --verbose flag to the invocation confirms that mypy indeed found and applied the setup.cfg config, see this excerpt from the (extremely verbose) mypy log:
Cause
Some digging into pre-commit shows that it uses a slightly different but nevertheless reasonable mypy invocation: it explicitly lists all the files to be checked rather than specifying an entire directory. This is probably in order to be able to apply the exclude rules specified in the pre-commitconfig. The abbreviated form of the mypy invocation it uses is the following (I include the full and rather long invocation at the bottom of the issue):
mypy --pretty < ... long list of space-separated .py files in the src/ directory ... >
Unlike the mypy src/ invocation, this mypy invocation surprisingly finds no issues!
I have verified the following things:
The --pretty flag has no effect on the output of the mypy invocation used by pre-commit.
The files where mypy finds errors in the mypy src/ invocation exist in the list of files in the invocation used by pre-commit.
Both invocations successfully locate and use the mypy configuration specified in setup.cfg (confirmed by using the --verbose flag and checking that mypy reports setup.cfg as the source of the applied configuration).
Additionally, the type errors reported by mypy src/ appear to be real issues. Just a couple of examples:
src/pip/_internal/resolution/resolvelib/factory.py:195: error: Item "None" of "Optional[Requirement]" has no attribute "name"
src/pip/_internal/resolution/resolvelib/factory.py:199: error: Item "None" of "Optional[Requirement]" has no attribute "specifier"
At no point is template.req checked for None before line 195 accesses template.req.name, so this is a real issue. Similarly, on line 199, ireq.req is also of type Optional[Requirement] but is never checked for None before ireq.req.specifier is accessed, so this is similarly a real issue.
One could argue that this surprising behavior is a bug in mypy and/or pre-commit and therefore should not be opened in the pip project. I will be opening a corresponding GitHub issue on mypy about this surprising behavior. However, I wanted to directly notify the pip maintainers of this problem as well, because currently the fact that mypy is running via pre-commit is in a way self-deceptive as the specified mypy configuration does actually produce real errors when run outside of pre-commit.
If the maintainers would like me to, I'd be happy to help contribute to resolving this issue.
Output
Below is the full output of mypy src/. As mentioned above, I have verified that this output takes into account the mypy settings configured in setup.cfg.
$ mypy src/
src/pip/_internal/utils/misc.py:526: error: "None" has no attribute "require"src/pip/_internal/utils/logging.py:27: error: Incompatible types in assignment (expression has type "None", variable has type Module)src/pip/_internal/req/req_set.py:36: error: Argument 1 to "canonicalize_name" has incompatible type "Optional[str]"; expected "str"src/pip/_internal/req/req_set.py:44: error: Argument 1 to "canonicalize_name" has incompatible type "Optional[str]"; expected "str"src/pip/_internal/req/req_set.py:130: error: Item "None" of "Optional[Requirement]" has no attribute "specifier"src/pip/_internal/req/req_install.py:367: error: Incompatible types in assignment (expression has type "str", variable has type "NormalizedName")src/pip/_internal/network/auth.py:283: error: "Response" has no attribute "connection"src/pip/_internal/cli/progress_bars.py:21: error: Incompatible types in assignment (expression has type "None", variable has type Module)src/pip/_internal/cli/progress_bars.py:52: error: Argument 1 to "_select_progress_class" has incompatible type "Type[IncrementalBar]"; expected "Bar"src/pip/_internal/cli/progress_bars.py:52: error: Argument 2 to "_select_progress_class" has incompatible type "Type[Bar]"; expected "Bar"
src/pip/_internal/cli/progress_bars.py:125: error: Incompatible types in assignment (expression has type "Tuple[str, str, str]", base class "IncrementalBar" defined the type as "Tuple[str, str, str, str, str, str, str, str, str]")
src/pip/_internal/cli/progress_bars.py:191: error: "AnsiToWin32" has no attribute "isatty"
src/pip/_internal/cli/progress_bars.py:195: error: "AnsiToWin32" has no attribute "flush"
src/pip/_internal/cli/progress_bars.py:211: error: Cannot determine type of 'hide_cursor' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:211: error: Cannot determine type of 'file' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:215: error: Cannot determine type of 'hide_cursor' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:215: error: Cannot determine type of 'file' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:220: error: Cannot determine type of 'hide_cursor' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:220: error: Cannot determine type of 'file' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:225: error: Cannot determine type of 'hide_cursor' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:225: error: Cannot determine type of 'file' in base class 'WindowsMixin'
src/pip/_internal/cli/progress_bars.py:230: error: Cannot determine type of 'hide_cursor' in base class 'WindowsMixin'
src/pip/_internal/resolution/resolvelib/base.py:66: error: Argument 1 to "contains" of "SpecifierSet" has incompatible type "_BaseVersion"; expected "Union[Union[Version, LegacyVersion], str]"
src/pip/_internal/req/constructors.py:191: error: Incompatible types in assignment (expression has type "None", variable has type "Requirement")
src/pip/_internal/req/constructors.py:369: error: Incompatible types in assignment (expression has type "None", variable has type "Requirement")
src/pip/_internal/commands/show.py:65: error: "None" has no attribute "__iter__" (not iterable)
src/pip/_internal/commands/show.py:79: error: "None" has no attribute "__iter__" (not iterable)
src/pip/_internal/commands/debug.py:212: error: Argument 2 to "show_value" has incompatible type "bool"; expected "Optional[str]"
src/pip/_internal/wheel_builder.py:189: error: Argument 1 to "canonicalize_name" has incompatible type "Optional[str]"; expected "str"
src/pip/_internal/wheel_builder.py:205: error: Unsupported operand types for <= ("Version" and "None")
src/pip/_internal/wheel_builder.py:205: note: Left operand is of type "Optional[Version]"
src/pip/_internal/wheel_builder.py:261: error: Argument "backend" to "build_wheel_pep517" has incompatible type "Optional[Pep517HookCaller]"; expected "Pep517HookCaller"
src/pip/_internal/resolution/resolvelib/requirements.py:77: error: Item "None" of "Optional[Requirement]" has no attribute "name"
src/pip/_internal/resolution/resolvelib/requirements.py:111: error: Item "None" of "Optional[Requirement]" has no attribute "specifier"
src/pip/_internal/resolution/resolvelib/requirements.py:112: error: Argument 1 to "contains" of "SpecifierSet" has incompatible type "_BaseVersion"; expected "Union[Union[Version, LegacyVersion], str]"
src/pip/_internal/resolution/resolvelib/requirements.py:150: error: Argument 1 to "contains" of "SpecifierSet" has incompatible type "_BaseVersion"; expected "Union[Union[Version, LegacyVersion], str]"
src/pip/_internal/resolution/resolvelib/requirements.py:160: error: Argument 1 to "contains" of "SpecifierSet" has incompatible type "_BaseVersion"; expected "Union[Union[Version, LegacyVersion], str]"
src/pip/_internal/operations/check.py:52: error: Incompatible return value type (got "Tuple[Dict[NormalizedName, PackageDetails], bool]", expected "Tuple[Dict[str, PackageDetails], bool]")
src/pip/_internal/operations/check.py:139: error: Incompatible return value type (got "Set[NormalizedName]", expected "Set[str]")
src/pip/_internal/operations/check.py:139: note: Perhaps you need a type annotation for "installed"? Suggestion: "Set[str]"
src/pip/_internal/resolution/resolvelib/factory.py:195: error: Item "None" of "Optional[Requirement]" has no attribute "name"
src/pip/_internal/resolution/resolvelib/factory.py:199: error: Item "None" of "Optional[Requirement]" has no attribute "specifier"
src/pip/_internal/resolution/resolvelib/factory.py:364: error: No overload variant of "get" of "Mapping" matches argument type "str"
src/pip/_internal/resolution/resolvelib/factory.py:364: note: Possible overload variant:
src/pip/_internal/resolution/resolvelib/factory.py:364: note: def get(self, key: NormalizedName) -> Optional[Distribution]
src/pip/_internal/resolution/resolvelib/factory.py:364: note: <1 more non-matching overload not shown>
src/pip/_internal/resolution/resolvelib/resolver.py:31: error: Module 'pip._vendor.resolvelib.structs' has no attribute 'Graph'
src/pip/_internal/resolution/resolvelib/resolver.py:92: error: Argument 1 to "canonicalize_name" has incompatible type "Optional[str]"; expected "str"
src/pip/_internal/resolution/resolvelib/resolver.py:116: error: Incompatible types in assignment (expression has type "PipReporter", variable has type "PipDebuggingReporter")
src/pip/_internal/resolution/resolvelib/resolver.py:130: error: Item "None" of "Optional[Result]" has no attribute "mapping"
src/pip/_internal/commands/search.py:130: error: "None" has no attribute "__iter__" (not iterable)
src/pip/_internal/commands/list.py:212: error: "_BaseVersion" has no attribute "is_prerelease"
src/pip/_internal/commands/list.py:219: error: Incompatible return value type (got "None", expected "Distribution")
src/pip/_internal/commands/list.py:227: error: "Distribution" has no attribute "latest_version"; maybe "has_version" or "_get_version"?
src/pip/_internal/commands/list.py:228: error: "Distribution" has no attribute "latest_filetype"
src/pip/_internal/commands/install.py:53: error: Argument 1 to "canonicalize_name" has incompatible type "Optional[str]"; expected "str"
Found 51 errors in 18 files (checked 374 source files)
The full mypy invocation used by pre-commit and its output are the following:
Per python/mypy#9954 the root cause is the follow_imports = skip setting which seems to be inappropriately set here. Changing that setting to silent (exact config below) results in mypy correctly finding the 51 type errors:
I'd be happy to open a PR with this config change, but the mypy CI pass will (correctly!) fail due to the errors. What would be the best way forward here?
@obi1kenobi Interesting find. I guess the only way forward is to file several reasonably small PRs to fix all errors until we can flip the switch in CI.
Currently,
pip
usesmypy
via apre-commit
hook and with some custom configuration specified in thesetup.cfg
file.However, due to a surprising difference in behavior in
mypy
between specifying individual files vs specifying directories to be checked, the way thatpre-commit
callsmypy
ends up unexpectedly hiding 51 type errors that are present in the files being checked and that are prohibited by themypy
configuration specified insetup.cfg
.Environment
8d042c448718355447f133c440f7a931a5ae57c0
, which is the tip of themaster
branch as of the time of this post.pre-commit
hook configExpected behavior
The following two commands should produce equivalent results i.e. neither of them finds any type errors on the current
master
branch (exact commit hash specified above):pre-commit run mypy --all-files
mypy src/
Actual behavior & How to Reproduce
Running
pre-commit run mypy --all-files
finds no type errors, as expected — this is enforced by CI.However, running
mypy src/
finds 51 type errors. Adding the--verbose
flag to the invocation confirms thatmypy
indeed found and applied thesetup.cfg
config, see this excerpt from the (extremely verbose)mypy
log:Cause
Some digging into
pre-commit
shows that it uses a slightly different but nevertheless reasonablemypy
invocation: it explicitly lists all the files to be checked rather than specifying an entire directory. This is probably in order to be able to apply theexclude
rules specified in thepre-commit
config. The abbreviated form of themypy
invocation it uses is the following (I include the full and rather long invocation at the bottom of the issue):Unlike the
mypy src/
invocation, thismypy
invocation surprisingly finds no issues!I have verified the following things:
--pretty
flag has no effect on the output of themypy
invocation used bypre-commit
.mypy
finds errors in themypy src/
invocation exist in the list of files in the invocation used bypre-commit
.mypy
configuration specified insetup.cfg
(confirmed by using the--verbose
flag and checking thatmypy
reportssetup.cfg
as the source of the applied configuration).Additionally, the type errors reported by
mypy src/
appear to be real issues. Just a couple of examples:Here is the corresponding code: https://github.com/pypa/pip/blob/master/src/pip/_internal/resolution/resolvelib/factory.py#L179-L199
On line 195,
template
has typeInstallRequirement
, sotemplate.req
has typeOptional[Requirement]
per: https://github.com/pypa/pip/blob/master/src/pip/_internal/req/req_install.py#L95-L120At no point is
template.req
checked forNone
before line 195 accessestemplate.req.name
, so this is a real issue. Similarly, on line 199,ireq.req
is also of typeOptional[Requirement]
but is never checked forNone
beforeireq.req.specifier
is accessed, so this is similarly a real issue.One could argue that this surprising behavior is a bug in
mypy
and/orpre-commit
and therefore should not be opened in thepip
project. I will be opening a corresponding GitHub issue onmypy
about this surprising behavior. However, I wanted to directly notify thepip
maintainers of this problem as well, because currently the fact thatmypy
is running viapre-commit
is in a way self-deceptive as the specifiedmypy
configuration does actually produce real errors when run outside ofpre-commit
.If the maintainers would like me to, I'd be happy to help contribute to resolving this issue.
Output
Below is the full output of
mypy src/
. As mentioned above, I have verified that this output takes into account themypy
settings configured insetup.cfg
.The full
mypy
invocation used bypre-commit
and its output are the following:The text was updated successfully, but these errors were encountered: