Update mypy test. Fix type hints for Python 3.9.

This also bumps the minimum controller version (for mypy only) to Python 3.9.
pull/77679/head
Matt Clay 3 years ago
parent 7471c036ec
commit cc5ac88c4c

@ -156,7 +156,7 @@ class CollectionVerifyResult:
def verify_local_collection(local_collection, remote_collection, artifacts_manager): 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. """Verify integrity of the locally installed collection.
:param local_collection: Collection being checked. :param local_collection: Collection being checked.

@ -88,7 +88,7 @@ class ConcreteArtifactsManager:
return self._ignore_signature_errors return self._ignore_signature_errors
def get_galaxy_artifact_source_info(self, collection): 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 server = collection.src.api_server
try: try:
@ -112,7 +112,7 @@ class ConcreteArtifactsManager:
} }
def get_galaxy_artifact_path(self, collection): 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. """Given a Galaxy-stored collection, return a cached path.
If it's not yet on disk, this method downloads the artifact first. If it's not yet on disk, this method downloads the artifact first.
@ -172,7 +172,7 @@ class ConcreteArtifactsManager:
return b_artifact_path return b_artifact_path
def get_artifact_path(self, collection): 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. """Given a concrete collection pointer, return a cached path.
If it's not yet on disk, this method downloads the artifact first. If it's not yet on disk, this method downloads the artifact first.
@ -237,15 +237,15 @@ class ConcreteArtifactsManager:
return b_artifact_path return b_artifact_path
def _get_direct_collection_namespace(self, collection): 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] return self.get_direct_collection_meta(collection)['namespace'] # type: ignore[return-value]
def _get_direct_collection_name(self, collection): 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] return self.get_direct_collection_meta(collection)['name'] # type: ignore[return-value]
def get_direct_collection_fqcn(self, collection): 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. """Extract FQCN from the given on-disk collection artifact.
If the collection is virtual, ``None`` is returned instead If the collection is virtual, ``None`` is returned instead
@ -261,12 +261,12 @@ class ConcreteArtifactsManager:
)) ))
def get_direct_collection_version(self, collection): 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.""" """Extract version from the given on-disk collection artifact."""
return self.get_direct_collection_meta(collection)['version'] # type: ignore[return-value] return self.get_direct_collection_meta(collection)['version'] # type: ignore[return-value]
def get_direct_collection_dependencies(self, collection): 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.""" """Extract deps from the given on-disk collection artifact."""
collection_dependencies = self.get_direct_collection_meta(collection)['dependencies'] collection_dependencies = self.get_direct_collection_meta(collection)['dependencies']
if collection_dependencies is None: if collection_dependencies is None:
@ -274,7 +274,7 @@ class ConcreteArtifactsManager:
return collection_dependencies # type: ignore[return-value] return collection_dependencies # type: ignore[return-value]
def get_direct_collection_meta(self, collection): 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.""" """Extract meta from the given on-disk collection artifact."""
try: # FIXME: use unique collection identifier as a cache key? try: # FIXME: use unique collection identifier as a cache key?
return self._artifact_meta_cache[collection.src] 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 # FIXME: use random subdirs while preserving the file names
def _download_file(url, b_path, expected_hash, validate_certs, token=None, timeout=60): 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 ^ # ^ NOTE: used in download and verify_collections ^
b_tarball_name = to_bytes( b_tarball_name = to_bytes(
url.rsplit('/', 1)[1], errors='surrogate_or_strict', 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( 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 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 = ( galaxy_yml_schema = (
get_collections_galaxy_meta_info() get_collections_galaxy_meta_info()
) # type: list[dict[str, t.Any]] # FIXME: <-- ) # 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() mandatory_keys = set()
string_keys = set() # type: set[str] string_keys = set() # type: set[str]
@ -570,7 +570,7 @@ def _normalize_galaxy_yml_manifest(
def _get_meta_from_dir( def _get_meta_from_dir(
b_path, # type: bytes 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: try:
return _get_meta_from_installed_dir(b_path) return _get_meta_from_installed_dir(b_path)
except LookupError: except LookupError:
@ -579,7 +579,7 @@ def _get_meta_from_dir(
def _get_meta_from_src_dir( def _get_meta_from_src_dir(
b_path, # type: bytes 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) galaxy_yml = os.path.join(b_path, _GALAXY_YAML)
if not os.path.isfile(galaxy_yml): if not os.path.isfile(galaxy_yml):
raise LookupError( raise LookupError(
@ -641,7 +641,7 @@ def _get_json_from_installed_dir(
def _get_meta_from_installed_dir( def _get_meta_from_installed_dir(
b_path, # type: bytes 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) manifest = _get_json_from_installed_dir(b_path, MANIFEST_FILENAME)
collection_info = manifest['collection_info'] collection_info = manifest['collection_info']
@ -662,7 +662,7 @@ def _get_meta_from_installed_dir(
def _get_meta_from_tar( def _get_meta_from_tar(
b_path, # type: bytes 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): if not tarfile.is_tarfile(b_path):
raise AnsibleError( raise AnsibleError(
"Collection artifact at '{path!s}' is not a valid tar file.". "Collection artifact at '{path!s}' is not a valid tar file.".
@ -710,7 +710,7 @@ def _tarfile_extract(
tar, # type: tarfile.TarFile tar, # type: tarfile.TarFile
member, # type: tarfile.TarInfo 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) tar_obj = tar.extractfile(member)
try: try:
yield member, tar_obj yield member, tar_obj

@ -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 {})) 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: if display is not None:
display.vvvv(f"Using signature at {source}") display.vvvv(f"Using signature at {source}")
try: try:

@ -155,7 +155,7 @@ class CollectionDependencyProvider(AbstractProvider):
return False return False
def identify(self, requirement_or_candidate): 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. """Given requirement or candidate, return an identifier for it.
This is used to identify a requirement or candidate, e.g. This is used to identify a requirement or candidate, e.g.
@ -168,10 +168,10 @@ class CollectionDependencyProvider(AbstractProvider):
def get_preference( def get_preference(
self, # type: CollectionDependencyProvider self, # type: CollectionDependencyProvider
resolution, # type: Candidate | None resolution, # type: t.Optional[Candidate]
candidates, # type: list[Candidate] candidates, # type: list[Candidate]
information, # type: list[t.NamedTuple] information, # type: list[t.NamedTuple]
): # type: (...) -> float | int ): # type: (...) -> t.Union[float, int]
"""Return sort key function return value for given requirement. """Return sort key function return value for given requirement.
This result should be based on preference that is defined as This result should be based on preference that is defined as

@ -2,8 +2,9 @@ mypy[python2]
packaging # type stubs not published separately packaging # type stubs not published separately
types-backports types-backports
types-jinja2 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-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-requests
types-setuptools types-setuptools
types-toml types-toml

@ -1,10 +1,10 @@
# edit "sanity.mypy.in" and generate with: hacking/update-sanity-requirements.py --test mypy # 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 mypy-extensions==0.4.3
packaging==21.2 packaging==21.2
pyparsing==2.4.7 pyparsing==2.4.7
tomli==2.0.1 tomli==2.0.1
typed-ast==1.5.2 typed-ast==1.5.3
types-backports==0.1.3 types-backports==0.1.3
types-cryptography==3.3.15 types-cryptography==3.3.15
types-enum34==1.1.8 types-enum34==1.1.8
@ -13,8 +13,8 @@ types-Jinja2==2.11.9
types-MarkupSafe==1.1.10 types-MarkupSafe==1.1.10
types-paramiko==2.8.13 types-paramiko==2.8.13
types-PyYAML==5.4.12 types-PyYAML==5.4.12
types-requests==2.27.10 types-requests==2.27.25
types-setuptools==57.4.9 types-setuptools==57.4.14
types-toml==0.10.4 types-toml==0.10.6
types-urllib3==1.26.9 types-urllib3==1.26.14
typing-extensions==3.10.0.2 typing-extensions==3.10.0.2

@ -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}.') 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) 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 = ( contexts = (
MyPyContext('ansible-test', ['test/lib/ansible_test/_util/target/sanity/import/'], CONTROLLER_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-test', ['test/lib/ansible_test/_internal/'], controller_python_versions),
MyPyContext('ansible-core', ['lib/ansible/'], 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('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], remote_only_python_versions),
) )
unfiltered_messages = [] # type: t.List[SanityMessage] unfiltered_messages = [] # type: t.List[SanityMessage]

@ -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.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.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.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-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.5:misc # vendored code
lib/ansible/module_utils/six/__init__.py mypy-3.6:misc # vendored code lib/ansible/module_utils/six/__init__.py mypy-3.6:misc # vendored code

Loading…
Cancel
Save