diff --git a/news/a208b0c2-e1ad-4430-b3ed-626e9292f906.trivial.rst b/news/a208b0c2-e1ad-4430-b3ed-626e9292f906.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index c15c68b6de4..d5cdb0784df 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -6,10 +6,6 @@ between parses. pip parses general options twice internally, and shouldn't pass on state. To be consistent, all options will follow this design. """ - -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import os import textwrap import warnings @@ -443,6 +439,7 @@ def editable() -> Option: def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None: value = os.path.abspath(value) + assert option.dest is not None setattr(parser.values, option.dest, value) @@ -466,12 +463,14 @@ def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) def _get_format_control(values: Values, option: Option) -> Any: """Get a format_control object.""" + assert option.dest is not None return getattr(values, option.dest) def _handle_no_binary( option: Option, opt_str: str, value: str, parser: OptionParser ) -> None: + assert parser.values is not None existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( value, @@ -483,6 +482,7 @@ def _handle_no_binary( def _handle_only_binary( option: Option, opt_str: str, value: str, parser: OptionParser ) -> None: + assert parser.values is not None existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( value, @@ -543,7 +543,9 @@ def only_binary() -> Option: # This was made a separate function for unit-testing purposes. -def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]: +def _convert_python_version( + value: str, +) -> Tuple[Optional[Tuple[int, ...]], Optional[str]]: """ Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. @@ -586,6 +588,7 @@ def _handle_python_version( ) raise_option_error(parser, option=option, msg=msg) + assert parser.values is not None parser.values.python_version = version_info @@ -702,6 +705,7 @@ def _handle_no_cache_dir( except ValueError as exc: raise_option_error(parser, option=option, msg=str(exc)) + assert parser.values is not None # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool() # converted to 0 (like "false" or "no") caused cache_dir to be disabled # rather than enabled (logic would say the latter). Thus, we disable @@ -784,6 +788,7 @@ def _handle_no_use_pep517( """ raise_option_error(parser, option=option, msg=msg) + assert parser.values is not None # Otherwise, --no-use-pep517 was passed via the command-line. parser.values.use_pep517 = False @@ -873,6 +878,7 @@ def _handle_merge_hash( ) -> None: """Given a value spelled "algo:digest", append the digest to a list pointed to in a dict by the algo name.""" + assert parser.values is not None if not parser.values.hashes: parser.values.hashes = {} try: diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index a2702db7d35..989067f00d0 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -1,8 +1,4 @@ """Routines related to PyPI, indexes""" - -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import functools import itertools import logging @@ -234,7 +230,7 @@ def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]: def filter_unallowed_hashes( candidates: List[InstallationCandidate], - hashes: Hashes, + hashes: Optional[Hashes], project_name: str, ) -> List[InstallationCandidate]: """ @@ -523,6 +519,7 @@ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: binary_preference = 1 if wheel.build_tag is not None: match = re.match(r"^(\d+)(.*)$", wheel.build_tag) + assert match is not None build_tag_groups = match.groups() build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) else: # sdist @@ -735,6 +732,7 @@ def get_install_candidate( self._log_skipped_link(link, reason=result) return None + assert result is not None return InstallationCandidate( name=link_evaluator.project_name, link=link, @@ -924,6 +922,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: installed_version, ) else: + assert best_candidate is not None logger.debug( "Existing installed version (%s) satisfies requirement " "(most up-to-date version is %s)", @@ -941,6 +940,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: ) raise BestVersionAlreadyInstalled + assert best_candidate is not None logger.debug( "Using version %s (newest of versions: %s)", best_candidate.version, diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 2ec79e65bea..6b441bff623 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -1,8 +1,4 @@ """Locations where we look for configs, install stuff, etc""" - -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import logging import os import sys @@ -87,6 +83,7 @@ def distutils_scheme( prefix = i.install_userbase # type: ignore else: prefix = i.prefix + assert prefix is not None scheme["headers"] = os.path.join( prefix, "include", diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py index 2206c930913..521a7c140cb 100644 --- a/src/pip/_internal/operations/install/legacy.py +++ b/src/pip/_internal/operations/install/legacy.py @@ -19,7 +19,8 @@ class LegacyInstallFailure(Exception): - pass + def __init__(self, cause: Exception): + self.cause = cause def write_installed_files_from_setuptools_record( diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index ccf034eb5a0..eceaac42585 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -1,9 +1,5 @@ """Prepares a distribution for installation """ - -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import logging import mimetypes import os @@ -187,6 +183,7 @@ def unpack_url( """ # non-editable vcs urls if link.is_vcs: + assert location is not None unpack_vcs_link(link, location) return None @@ -306,6 +303,7 @@ def __init__( def _log_preparing_link(self, req: InstallRequirement) -> None: """Provide context for the requirement being prepared.""" + assert req.link is not None if req.link.is_file and not req.original_link_is_in_wheel_cache: message = "Processing %s" information = str(display_path(req.link.file_path)) @@ -325,6 +323,7 @@ def _ensure_link_req_src_dir( self, req: InstallRequirement, parallel_builds: bool ) -> None: """Ensure source_dir of a linked InstallRequirement.""" + assert req.link is not None # Since source_dir is only set for editable requirements. if req.link.is_wheel: # We don't need to unpack wheels, so no need for a source @@ -348,7 +347,7 @@ def _ensure_link_req_src_dir( # installation. # FIXME: this won't upgrade when there's an existing # package unpacked in `req.source_dir` - if is_installable_dir(req.source_dir): + if req.source_dir is not None and is_installable_dir(req.source_dir): raise PreviousBuildDirError( "pip can't proceed with requirements '{}' due to a" "pre-existing build directory ({}). This is likely " @@ -370,10 +369,11 @@ def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes: # and save repetition of conditions, but then we would # report less-useful error messages for unhashable # requirements, complaining that there's no hash provided. - if req.link.is_vcs: - raise VcsHashUnsupported() - if req.link.is_existing_dir(): - raise DirectoryUrlHashUnsupported() + if req.link is not None: + if req.link.is_vcs: + raise VcsHashUnsupported() + if req.link.is_existing_dir(): + raise DirectoryUrlHashUnsupported() # Unpinned packages are asking for trouble when a new version # is uploaded. This isn't a security check, but it saves users @@ -487,7 +487,11 @@ def prepare_linked_requirements_more( reqs = [req for req in reqs if req.needs_more_preparation] for req in reqs: # Determine if any of these requirements were already downloaded. - if self.download_dir is not None and req.link.is_wheel: + if ( + self.download_dir is not None + and req.link is not None + and req.link.is_wheel + ): hashes = self._get_linked_req_hashes(req) file_path = _check_download_dir(req.link, self.download_dir, hashes) if file_path is not None: @@ -521,7 +525,7 @@ def _prepare_linked_requirement( if link.is_existing_dir() and self.in_tree_build: local_file = None - elif link.url not in self._downloaded: + elif link.url not in self._downloaded and req.source_dir is not None: try: local_file = unpack_url( link, req.source_dir, self._download, self.download_dir, hashes diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index ff0dd2f2d58..a5a1bd91cc3 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -1,6 +1,3 @@ -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import logging import os import shutil @@ -249,6 +246,7 @@ def name(self) -> Optional[str]: @property def specifier(self) -> SpecifierSet: + assert self.req is not None return self.req.specifier @property @@ -299,7 +297,7 @@ def hashes(self, trust_internet: bool = True) -> Hashes: """ good_hashes = self.hash_options.copy() link = self.link if trust_internet else self.original_link - if link and link.hash: + if link and link.hash_name and link.hash: good_hashes.setdefault(link.hash_name, []).append(link.hash) return Hashes(good_hashes) @@ -310,7 +308,7 @@ def from_path(self) -> Optional[str]: s = str(self.req) if self.comes_from: if isinstance(self.comes_from, str): - comes_from = self.comes_from + comes_from: Optional[str] = self.comes_from else: comes_from = self.comes_from.from_path() if comes_from: @@ -340,6 +338,7 @@ def ensure_build_location( # When parallel builds are enabled, add a UUID to the build directory # name so multiple builds do not interfere with each other. + assert self.name is not None dir_name: str = canonicalize_name(self.name) if parallel_builds: dir_name = f"{dir_name}_{uuid.uuid4().hex}" @@ -384,7 +383,7 @@ def _set_requirement(self) -> None: def warn_on_mismatching_name(self) -> None: metadata_name = canonicalize_name(self.metadata["Name"]) - if canonicalize_name(self.req.name) == metadata_name: + if self.req is not None and canonicalize_name(self.req.name) == metadata_name: # Everything is fine. return @@ -454,6 +453,7 @@ def is_wheel(self) -> bool: # Things valid for sdists @property def unpacked_source_directory(self) -> str: + assert self.source_dir is not None return os.path.join( self.source_dir, self.link and self.link.subdirectory_fragment or "" ) @@ -607,12 +607,17 @@ def metadata(self) -> Any: return self._metadata def get_dist(self) -> Distribution: + assert self.metadata_directory is not None return _get_dist(self.metadata_directory) def assert_source_matches_version(self) -> None: assert self.source_dir version = self.metadata["version"] - if self.req.specifier and version not in self.req.specifier: + if ( + self.req is not None + and self.req.specifier + and version not in self.req.specifier + ): logger.warning( "Requested %s, but installing version %s", self, @@ -705,6 +710,7 @@ def _clean_zip_name(name: str, prefix: str) -> str: name = name.replace(os.path.sep, "/") return name + assert self.name is not None path = os.path.join(parentdir, path) name = _clean_zip_name(path, rootdir) return self.name + "/" + name @@ -787,6 +793,8 @@ def install( use_user_site: bool = False, pycompile: bool = True, ) -> None: + assert self.name is not None + scheme = get_scheme( self.name, user=use_user_site, @@ -866,7 +874,9 @@ def install( ) except LegacyInstallFailure as exc: self.install_succeeded = False - raise exc.__cause__ + if exc.__cause__ is not None: + raise exc.__cause__ + raise except Exception: self.install_succeeded = True raise diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 09caaa6d534..64b75162d48 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -9,10 +9,6 @@ for sub-dependencies a. "first found, wins" (where the order is breadth first) """ - -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import logging import sys from collections import defaultdict @@ -301,6 +297,8 @@ def _populate_link(self, req: InstallRequirement) -> None: if self.wheel_cache is None or self.preparer.require_hashes: return + + assert req.link is not None cache_entry = self.wheel_cache.get_cache_entry( link=req.link, package_name=req.name, @@ -405,7 +403,9 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None: with indent_log(): # We add req_to_install before its dependencies, so that we # can refer to it when adding dependencies. - if not requirement_set.has_requirement(req_to_install.name): + if req_to_install.name is not None and not requirement_set.has_requirement( + req_to_install.name + ): # 'unnamed' requirements will get added here # 'unnamed' requirements can only come from being directly # provided by the user. @@ -457,6 +457,7 @@ def schedule(req: InstallRequirement) -> None: return if req.constraint: return + assert req.name is not None ordered_reqs.add(req) for dep in self._discovered_dependencies[req.name]: schedule(dep) diff --git a/src/pip/_internal/utils/glibc.py b/src/pip/_internal/utils/glibc.py index 7bd3c20681d..2b838e9efcf 100644 --- a/src/pip/_internal/utils/glibc.py +++ b/src/pip/_internal/utils/glibc.py @@ -1,6 +1,3 @@ -# The following comment should be removed at some point in the future. -# mypy: strict-optional=False - import os import sys from typing import Optional, Tuple @@ -19,12 +16,23 @@ def glibc_version_string_confstr() -> Optional[str]: # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 if sys.platform == "win32": return None + try: # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17": - _, version = os.confstr("CS_GNU_LIBC_VERSION").split() + conf = os.confstr("CS_GNU_LIBC_VERSION") except (AttributeError, OSError, ValueError): - # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + # os.confstr() or CS_GNU_LIBC_VERSION not available. + return None + + if conf is None: return None + + try: + _, version = conf.split() + except ValueError: + # Bad value. + return None + return version diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index d3e9053efd7..8bcda2945e9 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -391,7 +391,7 @@ def write_output(msg: Any, *args: Any) -> None: class StreamWrapper(StringIO): - orig_stream: TextIO = None + orig_stream: TextIO @classmethod def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper": @@ -475,7 +475,7 @@ def parse_netloc(netloc: str) -> Tuple[str, Optional[int]]: """ url = build_url_from_netloc(netloc) parsed = urllib.parse.urlparse(url) - return parsed.hostname, parsed.port + return parsed.hostname or "", parsed.port def split_auth_from_netloc(netloc: str) -> NetlocTuple: @@ -557,7 +557,9 @@ def _redact_netloc(netloc: str) -> Tuple[str]: return (redact_netloc(netloc),) -def split_auth_netloc_from_url(url: str) -> Tuple[str, str, Tuple[str, str]]: +def split_auth_netloc_from_url( + url: str, +) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]: """ Parse a url into separate netloc, auth, and url with no auth.