diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index f76fcc3e75e..0e53339eef1 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -156,7 +156,7 @@ class CollectionVerifyResult: def verify_local_collection(local_collection, remote_collection, artifacts_manager): - # type: (Candidate, Candidate | None, ConcreteArtifactsManager) -> CollectionVerifyResult + # type: (Candidate, t.Optional[Candidate], ConcreteArtifactsManager) -> CollectionVerifyResult """Verify integrity of the locally installed collection. :param local_collection: Collection being checked. diff --git a/lib/ansible/galaxy/collection/concrete_artifact_manager.py b/lib/ansible/galaxy/collection/concrete_artifact_manager.py index b63cf8fec1a..fe1159d893f 100644 --- a/lib/ansible/galaxy/collection/concrete_artifact_manager.py +++ b/lib/ansible/galaxy/collection/concrete_artifact_manager.py @@ -88,7 +88,7 @@ class ConcreteArtifactsManager: return self._ignore_signature_errors def get_galaxy_artifact_source_info(self, collection): - # type: (Candidate) -> dict[str, str | list[dict[str, str]]] + # type: (Candidate) -> dict[str, t.Union[str, list[dict[str, str]]]] server = collection.src.api_server try: @@ -112,7 +112,7 @@ class ConcreteArtifactsManager: } def get_galaxy_artifact_path(self, collection): - # type: (Candidate | Requirement) -> bytes + # type: (t.Union[Candidate, Requirement]) -> bytes """Given a Galaxy-stored collection, return a cached path. If it's not yet on disk, this method downloads the artifact first. @@ -172,7 +172,7 @@ class ConcreteArtifactsManager: return b_artifact_path def get_artifact_path(self, collection): - # type: (Candidate | Requirement) -> bytes + # type: (t.Union[Candidate, Requirement]) -> bytes """Given a concrete collection pointer, return a cached path. If it's not yet on disk, this method downloads the artifact first. @@ -237,15 +237,15 @@ class ConcreteArtifactsManager: return b_artifact_path def _get_direct_collection_namespace(self, collection): - # type: (Candidate) -> str | None + # type: (Candidate) -> t.Optional[str] return self.get_direct_collection_meta(collection)['namespace'] # type: ignore[return-value] def _get_direct_collection_name(self, collection): - # type: (Candidate) -> str | None + # type: (Candidate) -> t.Optional[str] return self.get_direct_collection_meta(collection)['name'] # type: ignore[return-value] def get_direct_collection_fqcn(self, collection): - # type: (Candidate) -> str | None + # type: (Candidate) -> t.Optional[str] """Extract FQCN from the given on-disk collection artifact. If the collection is virtual, ``None`` is returned instead @@ -261,12 +261,12 @@ class ConcreteArtifactsManager: )) def get_direct_collection_version(self, collection): - # type: (Candidate | Requirement) -> str + # type: (t.Union[Candidate, Requirement]) -> str """Extract version from the given on-disk collection artifact.""" return self.get_direct_collection_meta(collection)['version'] # type: ignore[return-value] def get_direct_collection_dependencies(self, collection): - # type: (Candidate | Requirement) -> dict[str, str] + # type: (t.Union[Candidate, Requirement]) -> dict[str, str] """Extract deps from the given on-disk collection artifact.""" collection_dependencies = self.get_direct_collection_meta(collection)['dependencies'] if collection_dependencies is None: @@ -274,7 +274,7 @@ class ConcreteArtifactsManager: return collection_dependencies # type: ignore[return-value] def get_direct_collection_meta(self, collection): - # type: (Candidate | Requirement) -> dict[str, str | dict[str, str] | list[str] | None] + # type: (t.Union[Candidate, Requirement]) -> dict[str, t.Union[str, dict[str, str], list[str], None]] """Extract meta from the given on-disk collection artifact.""" try: # FIXME: use unique collection identifier as a cache key? return self._artifact_meta_cache[collection.src] @@ -447,7 +447,7 @@ def _extract_collection_from_git(repo_url, coll_ver, b_path): # FIXME: use random subdirs while preserving the file names def _download_file(url, b_path, expected_hash, validate_certs, token=None, timeout=60): - # type: (str, bytes, str | None, bool, GalaxyToken, int) -> bytes + # type: (str, bytes, t.Optional[str], bool, GalaxyToken, int) -> bytes # ^ NOTE: used in download and verify_collections ^ b_tarball_name = to_bytes( url.rsplit('/', 1)[1], errors='surrogate_or_strict', @@ -503,14 +503,14 @@ def _consume_file(read_from, write_to=None): def _normalize_galaxy_yml_manifest( - galaxy_yml, # type: dict[str, str | list[str] | dict[str, str] | None] + galaxy_yml, # type: dict[str, t.Union[str, list[str], dict[str, str], None]] b_galaxy_yml_path, # type: bytes ): - # type: (...) -> dict[str, str | list[str] | dict[str, str] | None] + # type: (...) -> dict[str, t.Union[str, list[str], dict[str, str], None]] galaxy_yml_schema = ( get_collections_galaxy_meta_info() ) # type: list[dict[str, t.Any]] # FIXME: <-- - # FIXME: 👆maybe precise type: list[dict[str, bool | str | list[str]]] + # FIXME: 👆maybe precise type: list[dict[str, t.Union[bool, str, list[str]]]] mandatory_keys = set() string_keys = set() # type: set[str] @@ -570,7 +570,7 @@ def _normalize_galaxy_yml_manifest( def _get_meta_from_dir( b_path, # type: bytes -): # type: (...) -> dict[str, str | list[str] | dict[str, str] | None] +): # type: (...) -> dict[str, t.Union[str, list[str], dict[str, str], None]] try: return _get_meta_from_installed_dir(b_path) except LookupError: @@ -579,7 +579,7 @@ def _get_meta_from_dir( def _get_meta_from_src_dir( b_path, # type: bytes -): # type: (...) -> dict[str, str | list[str] | dict[str, str] | None] +): # type: (...) -> dict[str, t.Union[str, list[str], dict[str, str], None]] galaxy_yml = os.path.join(b_path, _GALAXY_YAML) if not os.path.isfile(galaxy_yml): raise LookupError( @@ -641,7 +641,7 @@ def _get_json_from_installed_dir( def _get_meta_from_installed_dir( b_path, # type: bytes -): # type: (...) -> dict[str, str | list[str] | dict[str, str] | None] +): # type: (...) -> dict[str, t.Union[str, list[str], dict[str, str], None]] manifest = _get_json_from_installed_dir(b_path, MANIFEST_FILENAME) collection_info = manifest['collection_info'] @@ -662,7 +662,7 @@ def _get_meta_from_installed_dir( def _get_meta_from_tar( b_path, # type: bytes -): # type: (...) -> dict[str, str | list[str] | dict[str, str] | None] +): # type: (...) -> dict[str, t.Union[str, list[str], dict[str, str], None]] if not tarfile.is_tarfile(b_path): raise AnsibleError( "Collection artifact at '{path!s}' is not a valid tar file.". @@ -710,7 +710,7 @@ def _tarfile_extract( tar, # type: tarfile.TarFile member, # type: tarfile.TarInfo ): - # type: (...) -> t.Iterator[tuple[tarfile.TarInfo, t.IO[bytes] | None]] + # type: (...) -> t.Iterator[tuple[tarfile.TarInfo, t.Optional[t.IO[bytes]]]] tar_obj = tar.extractfile(member) try: yield member, tar_obj diff --git a/lib/ansible/galaxy/collection/gpg.py b/lib/ansible/galaxy/collection/gpg.py index f684de162bc..8641f0d7f72 100644 --- a/lib/ansible/galaxy/collection/gpg.py +++ b/lib/ansible/galaxy/collection/gpg.py @@ -25,7 +25,7 @@ IS_PY310_PLUS = sys.version_info[:2] >= (3, 10) frozen_dataclass = partial(dataclass, frozen=True, **({'slots': True} if IS_PY310_PLUS else {})) -def get_signature_from_source(source, display=None): # type: (str, Display | None) -> str +def get_signature_from_source(source, display=None): # type: (str, t.Optional[Display]) -> str if display is not None: display.vvvv(f"Using signature at {source}") try: diff --git a/lib/ansible/galaxy/dependency_resolution/providers.py b/lib/ansible/galaxy/dependency_resolution/providers.py index 08aeb2074c1..9d82513c268 100644 --- a/lib/ansible/galaxy/dependency_resolution/providers.py +++ b/lib/ansible/galaxy/dependency_resolution/providers.py @@ -155,7 +155,7 @@ class CollectionDependencyProvider(AbstractProvider): return False def identify(self, requirement_or_candidate): - # type: (Candidate | Requirement) -> str + # type: (t.Union[Candidate, Requirement]) -> str """Given requirement or candidate, return an identifier for it. This is used to identify a requirement or candidate, e.g. @@ -168,10 +168,10 @@ class CollectionDependencyProvider(AbstractProvider): def get_preference( self, # type: CollectionDependencyProvider - resolution, # type: Candidate | None + resolution, # type: t.Optional[Candidate] candidates, # type: list[Candidate] information, # type: list[t.NamedTuple] - ): # type: (...) -> float | int + ): # type: (...) -> t.Union[float, int] """Return sort key function return value for given requirement. This result should be based on preference that is defined as diff --git a/test/lib/ansible_test/_data/requirements/sanity.mypy.in b/test/lib/ansible_test/_data/requirements/sanity.mypy.in index b7b8229794a..890caf30f40 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.mypy.in +++ b/test/lib/ansible_test/_data/requirements/sanity.mypy.in @@ -2,8 +2,9 @@ mypy[python2] packaging # type stubs not published separately types-backports types-jinja2 -types-paramiko +types-paramiko < 2.8.14 # newer versions drop support for Python 2.7 types-pyyaml < 6 # PyYAML 6+ stubs do not support Python 2.7 +types-cryptography < 3.3.16 # newer versions drop support for Python 2.7 types-requests types-setuptools types-toml diff --git a/test/lib/ansible_test/_data/requirements/sanity.mypy.txt b/test/lib/ansible_test/_data/requirements/sanity.mypy.txt index d4baf5630dd..e448c9074ed 100644 --- a/test/lib/ansible_test/_data/requirements/sanity.mypy.txt +++ b/test/lib/ansible_test/_data/requirements/sanity.mypy.txt @@ -1,10 +1,10 @@ # edit "sanity.mypy.in" and generate with: hacking/update-sanity-requirements.py --test mypy -mypy==0.931 +mypy==0.950 mypy-extensions==0.4.3 packaging==21.2 pyparsing==2.4.7 tomli==2.0.1 -typed-ast==1.5.2 +typed-ast==1.5.3 types-backports==0.1.3 types-cryptography==3.3.15 types-enum34==1.1.8 @@ -13,8 +13,8 @@ types-Jinja2==2.11.9 types-MarkupSafe==1.1.10 types-paramiko==2.8.13 types-PyYAML==5.4.12 -types-requests==2.27.10 -types-setuptools==57.4.9 -types-toml==0.10.4 -types-urllib3==1.26.9 +types-requests==2.27.25 +types-setuptools==57.4.14 +types-toml==0.10.6 +types-urllib3==1.26.14 typing-extensions==3.10.0.2 diff --git a/test/lib/ansible_test/_internal/commands/sanity/mypy.py b/test/lib/ansible_test/_internal/commands/sanity/mypy.py index 5b83aa8b9f1..fe664ddc9fc 100644 --- a/test/lib/ansible_test/_internal/commands/sanity/mypy.py +++ b/test/lib/ansible_test/_internal/commands/sanity/mypy.py @@ -90,11 +90,22 @@ class MypyTest(SanityMultipleVersion): display.warning(f'Skipping sanity test "{self.name}" due to missing virtual environment support on Python {args.controller_python.version}.') return SanitySkipped(self.name, python.version) + # Temporary hack to make Python 3.8 a remote-only Python version since we'll be dropping controller support for it soon. + # This avoids having to change annotations or add ignores for issues that are specific to that version. + + change_version = '3.8' + + if change_version not in CONTROLLER_PYTHON_VERSIONS or change_version in REMOTE_ONLY_PYTHON_VERSIONS: + raise Exception(f'Remove this hack now that Python {change_version} is not supported by the controller.') + + controller_python_versions = tuple(version for version in CONTROLLER_PYTHON_VERSIONS if version != change_version) + remote_only_python_versions = REMOTE_ONLY_PYTHON_VERSIONS + (change_version,) + contexts = ( - MyPyContext('ansible-test', ['test/lib/ansible_test/_util/target/sanity/import/'], CONTROLLER_PYTHON_VERSIONS), - MyPyContext('ansible-test', ['test/lib/ansible_test/_internal/'], CONTROLLER_PYTHON_VERSIONS), - MyPyContext('ansible-core', ['lib/ansible/'], CONTROLLER_PYTHON_VERSIONS), - MyPyContext('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], REMOTE_ONLY_PYTHON_VERSIONS), + MyPyContext('ansible-test', ['test/lib/ansible_test/_util/target/sanity/import/'], controller_python_versions), + MyPyContext('ansible-test', ['test/lib/ansible_test/_internal/'], controller_python_versions), + MyPyContext('ansible-core', ['lib/ansible/'], controller_python_versions), + MyPyContext('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], remote_only_python_versions), ) unfiltered_messages = [] # type: t.List[SanityMessage] diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 6129674e125..16763b35990 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -286,6 +286,7 @@ lib/ansible/module_utils/six/__init__.py mypy-2.7:assignment # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.5:assignment # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.6:assignment # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.7:assignment # vendored code +lib/ansible/module_utils/six/__init__.py mypy-3.8:assignment # vendored code lib/ansible/module_utils/six/__init__.py mypy-2.7:misc # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.5:misc # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.6:misc # vendored code