From 5f00863d71c003a89d08910c7e3db0ebf575c460 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sun, 30 Apr 2023 11:14:01 +0100 Subject: [PATCH] Introduce use of JSON recursive type --- .pre-commit-config.yaml | 4 ++-- pyproject.toml | 9 ++++----- requirements.txt | 5 +++-- src/ansible_compat/loaders.py | 2 +- src/ansible_compat/runtime.py | 7 ++++++- src/ansible_compat/schema.py | 8 +++++--- src/ansible_compat/types.py | 23 +++++++++++++++++++++++ test/conftest.py | 4 ++-- test/test_config.py | 5 ++++- test/test_runtime.py | 8 ++++++-- test/test_schema.py | 15 ++++++++++----- 11 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/ansible_compat/types.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7e9e073d..d9fac160 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -97,7 +97,7 @@ repos: name: Upgrade constraints files and requirements files: ^(pyproject\.toml|requirements\.txt)$ language: python - entry: python -m piptools compile --resolver=backtracking --upgrade -q --strip-extras --extra docs --extra test --output-file=requirements.txt pyproject.toml --unsafe-package ansible-core --unsafe-package resolvelib + entry: python -m piptools compile --resolver=backtracking --upgrade -q --strip-extras --extra docs --extra test --output-file=requirements.txt pyproject.toml --unsafe-package ansible-core --unsafe-package resolvelib --unsafe-package typing_extensions pass_filenames: false stages: - manual @@ -107,7 +107,7 @@ repos: name: Check constraints files and requirements files: ^(pyproject\.toml|requirements\.txt)$ language: python - entry: python -m piptools compile --resolver=backtracking -q --strip-extras --extra docs --extra test --output-file=requirements.txt pyproject.toml --unsafe-package ansible-core --unsafe-package resolvelib + entry: python -m piptools compile --resolver=backtracking -q --strip-extras --extra docs --extra test --output-file=requirements.txt pyproject.toml --unsafe-package ansible-core --unsafe-package resolvelib --unsafe-package typing_extensions pass_filenames: false additional_dependencies: - pip-tools>=6.11.0 diff --git a/pyproject.toml b/pyproject.toml index be89f92b..b6c1b7de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "PyYAML", "subprocess-tee>=0.4.1", "jsonschema>=4.6.0", + "typing-extensions>=4.5.0;python_version<'3.10'", ] [project.urls] @@ -117,15 +118,13 @@ filterwarnings = ["error"] select = ["ALL"] ignore = [ # Disabled on purpose: - "E501", # we use black - "RET504", # Unnecessary variable assignment before `return` statement + "ANN101", # Missing type annotation for `self` in method "D203", # incompatible with D211 "D213", # incompatible with D212 + "E501", # we use black + "RET504", # Unnecessary variable assignment before `return` statement # Temporary disabled during adoption: "A003", # Class attribute `exec` is shadowing a python builtin - "ANN101", - "ANN401", - "ARG", "S506", # Probable use of unsafe loader `FullLoader` "S607", # Starting a process with a partial executable path diff --git a/requirements.txt b/requirements.txt index 5c8b7ba4..b29442cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --extra=docs --extra=test --output-file=requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ansible-core --unsafe-package=resolvelib pyproject.toml +# pip-compile --extra=docs --extra=test --output-file=requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ansible-core --unsafe-package=resolvelib --unsafe-package=typing_extensions pyproject.toml # argparse-manpage==4.1 # via ansible-compat (pyproject.toml) @@ -269,8 +269,9 @@ tomli==2.0.1 # build # pyproject-hooks # pytest -typing-extensions==4.5.0 +typing-extensions==4.5.0 ; python_version < "3.10" # via + # ansible-compat (pyproject.toml) # black # mkdocs-ansible # mkdocstrings diff --git a/src/ansible_compat/loaders.py b/src/ansible_compat/loaders.py index 173a70bd..d4261cdc 100644 --- a/src/ansible_compat/loaders.py +++ b/src/ansible_compat/loaders.py @@ -11,7 +11,7 @@ from pathlib import Path -def yaml_from_file(path: Path) -> Any: +def yaml_from_file(path: Path) -> Any: # noqa: ANN401 """Return a loaded YAML file.""" with path.open(encoding="utf-8") as content: return yaml.load(content, Loader=yaml.FullLoader) diff --git a/src/ansible_compat/runtime.py b/src/ansible_compat/runtime.py index 9e5f9f3a..bcbd39e6 100644 --- a/src/ansible_compat/runtime.py +++ b/src/ansible_compat/runtime.py @@ -115,7 +115,12 @@ def __init__( from ansible.utils.display import Display # pylint: disable=unused-argument - def warning(self: Display, msg: str, *, formatted: bool = False) -> None: + def warning( + self: Display, # noqa: ARG001 + msg: str, + *, + formatted: bool = False, # noqa: ARG001 + ) -> None: """Override ansible.utils.display.Display.warning to avoid printing warnings.""" warnings.warn(msg, category=AnsibleWarning, stacklevel=2) diff --git a/src/ansible_compat/schema.py b/src/ansible_compat/schema.py index 8f69eef3..bcad2851 100644 --- a/src/ansible_compat/schema.py +++ b/src/ansible_compat/schema.py @@ -2,11 +2,13 @@ import json from collections.abc import Mapping, Sequence from dataclasses import dataclass -from typing import Any, Union +from typing import Union import jsonschema from jsonschema.validators import validator_for +from ansible_compat.types import JSON + def to_path(schema_path: Sequence[Union[str, int]]) -> str: """Flatten a path to a dot delimited string. @@ -56,8 +58,8 @@ def to_friendly(self) -> str: def validate( - schema: Union[str, Mapping[str, Any]], - data: dict[str, Any], + schema: JSON, + data: JSON, ) -> list[JsonSchemaError]: """Validate some data against a JSON schema. diff --git a/src/ansible_compat/types.py b/src/ansible_compat/types.py new file mode 100644 index 00000000..4514606b --- /dev/null +++ b/src/ansible_compat/types.py @@ -0,0 +1,23 @@ +"""Custom types.""" +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from typing import Union + +try: # py39 does not have TypeAlias + from typing_extensions import TypeAlias +except ImportError: + from typing import TypeAlias # type: ignore[no-redef,attr-defined] + +JSON: TypeAlias = Union[dict[str, "JSON"], list["JSON"], str, int, float, bool, None] +JSON_ro: TypeAlias = Union[ + Mapping[str, "JSON_ro"], + Sequence["JSON_ro"], + str, + int, + float, + bool, + None, +] + +__all__ = ["JSON", "JSON_ro"] diff --git a/test/conftest.py b/test/conftest.py index 33ab1f2f..0b8aea58 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,7 +9,7 @@ @pytest.fixture() # pylint: disable=unused-argument -def runtime(scope: str = "session") -> Generator[Runtime, None, None]: +def runtime(scope: str = "session") -> Generator[Runtime, None, None]: # noqa: ARG001 """Isolated runtime fixture.""" instance = Runtime(isolated=True) yield instance @@ -20,7 +20,7 @@ def runtime(scope: str = "session") -> Generator[Runtime, None, None]: # pylint: disable=unused-argument def runtime_tmp( tmp_path: pathlib.Path, - scope: str = "session", + scope: str = "session", # noqa: ARG001 ) -> Generator[Runtime, None, None]: """Isolated runtime fixture using a temp directory.""" instance = Runtime(project_dir=tmp_path, isolated=True) diff --git a/test/test_config.py b/test/test_config.py index 286d3c7e..4f854aeb 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -63,7 +63,10 @@ def test_ansible_version_missing(monkeypatch: MonkeyPatch) -> None: """Validate ansible_version behavior when ansible is missing.""" monkeypatch.setattr( "subprocess.run", - lambda *args, **kwargs: subprocess.CompletedProcess(args=[], returncode=1), + lambda *args, **kwargs: subprocess.CompletedProcess( # noqa: ARG005 + args=[], + returncode=1, + ), ) with pytest.raises( MissingAnsibleError, diff --git a/test/test_runtime.py b/test/test_runtime.py index 6d4d9d96..038a2114 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -49,7 +49,11 @@ def test_runtime_missing_ansible_module(monkeypatch: MonkeyPatch) -> None: class RaiseException: """Class to raise an exception.""" - def __init__(self, *args: Any, **kwargs: Any) -> None: + def __init__( + self, + *args: Any, # noqa: ARG002,ANN401 + **kwargs: Any, # noqa: ARG002,ANN401 + ) -> None: raise ModuleNotFoundError monkeypatch.setattr("importlib.import_module", RaiseException) @@ -362,7 +366,7 @@ def test__update_env_no_default( def test__update_env( monkeypatch: MonkeyPatch, old_value: str, - default: str, # pylint: disable=unused-argument + default: str, # pylint: disable=unused-argument # noqa: ARG001 value: list[str], result: str, ) -> None: diff --git a/test/test_schema.py b/test/test_schema.py index c5200a3a..b253cb5f 100644 --- a/test/test_schema.py +++ b/test/test_schema.py @@ -1,12 +1,17 @@ """Tests for schema utilities.""" +from __future__ import annotations + import json from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from ansible_compat.schema import JsonSchemaError, json_path, validate +if TYPE_CHECKING: + from ansible_compat.types import JSON + expected_results = [ JsonSchemaError( message="False is not of type 'string'", @@ -31,16 +36,16 @@ ] -def json_from_asset(file_name: str) -> Any: +def json_from_asset(file_name: str) -> JSON: """Load a json file from disk.""" file = Path(__file__).parent / file_name with file.open(encoding="utf-8") as f: - return json.load(f) + return json.load(f) # type: ignore[no-any-return] -def jsonify(data: Any) -> Any: +def jsonify(data: Any) -> JSON: # noqa: ANN401 """Convert object in JSON data structure.""" - return json.loads(json.dumps(data, default=vars, sort_keys=True)) + return json.loads(json.dumps(data, default=vars, sort_keys=True)) # type: ignore[no-any-return] @pytest.mark.parametrize("index", range(1))