diff --git a/docs/source/development/training/test_mfm.py b/docs/source/development/training/test_mfm.py index c97e633fc..f248991c8 100644 --- a/docs/source/development/training/test_mfm.py +++ b/docs/source/development/training/test_mfm.py @@ -4,12 +4,12 @@ from mfm import TimesTable, my_float_multiplier, my_picky_float_multiplier -def test_fm(): +def test_fm() -> None: assert 10 == my_float_multiplier(2, 5) -def test_pfm(): +def test_pfm() -> None: with pytest.raises(ValueError) as err_hndlr: @@ -18,7 +18,7 @@ def test_pfm(): assert str(err_hndlr.value) == "Both x and y must be of type float" -def test_pfm_fail(): +def test_pfm_fail() -> None: with pytest.raises(ValueError) as err_hndlr: @@ -36,7 +36,7 @@ def test_pfm_fail(): (-1.5, -3.0, 4.5), ], ) -def test_pfm_param_noid(x, y, expected): +def test_pfm_param_noid(x: float, y: float, expected: float) -> None: assert expected == my_picky_float_multiplier(x, y) @@ -51,7 +51,7 @@ def test_pfm_param_noid(x, y, expected): ], ids=["++", "-+", "+-", "--"], ) -def test_pfm_param_ids(x, y, expected): +def test_pfm_param_ids(x: float, y: float, expected: float) -> None: assert expected == my_picky_float_multiplier(x, y) @@ -66,13 +66,13 @@ def test_pfm_param_ids(x, y, expected): argvalues=[(3.0,), (-3.0,)], ids=["+", "-"], ) -def test_pfm_twoparam(x, y): +def test_pfm_twoparam(x: float, y: float) -> None: assert 4.5 == abs(my_picky_float_multiplier(x, y)) @pytest.fixture -def twoparam_expected(): +def twoparam_expected() -> dict[str, float]: expected = {"+-+": 4.5, "---": 4.5, "--+": -4.5, "+--": -4.5} return expected @@ -88,7 +88,12 @@ def twoparam_expected(): argvalues=[(3.0,), (-3.0,)], ids=["+", "-"], ) -def test_pfm_twoparam_fixture(request, twoparam_expected, x, y): +def test_pfm_twoparam_fixture( + request: pytest.FixtureRequest, + twoparam_expected: dict[str, float], + x: float, + y: float, +) -> None: expected = twoparam_expected[request.node.callspec.id] @@ -96,19 +101,19 @@ def test_pfm_twoparam_fixture(request, twoparam_expected, x, y): @pytest.fixture() -def times_table_instance(): +def times_table_instance() -> TimesTable: return TimesTable(num=7) -def test_times_table_errors(times_table_instance): +def test_times_table_errors(times_table_instance: TimesTable) -> None: with pytest.raises(TypeError) as err_hndlr: - times_table_instance.table(1.6, 23.9) + times_table_instance.table(1.6, 23.9) # type: ignore assert str(err_hndlr.value) == "'float' object cannot be interpreted as an integer" -def test_times_table_values(times_table_instance): +def test_times_table_values(times_table_instance: TimesTable) -> None: value = times_table_instance.table(2, 7) assert value == [14, 21, 28, 35, 42, 49] diff --git a/poetry.lock b/poetry.lock index a130f225a..c04b797ee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,7 +171,7 @@ bokeh = ["selenium", "bokeh"] [[package]] name = "coverage" -version = "6.4.4" +version = "6.5.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -1640,6 +1640,14 @@ python-versions = ">=3.7" [package.extras] test = ["pre-commit", "pytest"] +[[package]] +name = "types-jsonschema" +version = "4.16.1" +description = "Typing stubs for jsonschema" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "4.3.0" @@ -1701,7 +1709,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = ">=3.9,<3.12" -content-hash = "5b6f520f654421c8abd829e8668c7a6ae21244861cc6779afca39ac75c95eb39" +content-hash = "7241ad7e834e3724472de6c14761a2d3357eba974e0111361f6565cb0520fd16" [metadata.files] alabaster = [] @@ -1972,6 +1980,7 @@ tomli = [ tomli-w = [] tornado = [] traitlets = [] +types-jsonschema = [] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, diff --git a/pyproject.toml b/pyproject.toml index de19aa72a..b034ddd14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ tomli-w = "^1.0.0" scipy = "^1.9.0" jsonschema = "^4.14.0" Shapely = "^1.8.4" +types-jsonschema = "^4.16.1" [tool.poetry.dev-dependencies] pytest = "^7.1.2" diff --git a/setup.cfg b/setup.cfg index fc2332a5a..2840338dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,12 +21,20 @@ extend-ignore = docstring-convention = google [mypy] -ignore_missing_imports = True +ignore_missing_imports = False strict_optional = False +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True [mypy-setup] ignore_errors = True +[mypy-tests.*] +disallow_untyped_calls = False +disallow_untyped_defs = False +disallow_incomplete_defs = False + [isort] profile = black multi_line_output = 3 diff --git a/tests/conftest.py b/tests/conftest.py index 018ec22ac..c02227787 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,13 @@ """Collection of fixtures to assist the testing scripts.""" +import pytest + # An import of LOGGER is required for INFO logging events to be visible to tests # This can be removed as soon as a script that imports logger is imported import virtual_rainforest.core.logger # noqa -def log_check(caplog, expected_log): +def log_check(caplog: pytest.LogCaptureFixture, expected_log: tuple[tuple]) -> None: """Helper function to check that the captured log is as expected. Arguments: diff --git a/tests/test_config.py b/tests/test_config.py index b3a12df3d..3cf5b8d96 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -32,7 +32,7 @@ ), ], ) -def test_check_dict_leaves(d_a, d_b, overlap): +def test_check_dict_leaves(d_a: dict, d_b: dict, overlap: list) -> None: """Checks overlapping dictionary search function.""" assert overlap == config.check_dict_leaves(d_a, d_b, []) @@ -203,7 +203,7 @@ def test_find_schema(caplog, config_dict, expected_exception, expected_log_entri log_check(caplog, expected_log_entries) -def test_construct_combined_schema(caplog): +def test_construct_combined_schema(caplog: pytest.LogCaptureFixture) -> None: """Checks errors for bad or missing json schema.""" # Check that construct_combined_schema fails as expected @@ -241,6 +241,8 @@ def test_construct_combined_schema(caplog): def test_final_validation_log(caplog, expected_log_entries): """Checks that validation passes as expected and produces the correct output.""" + print(type(expected_log_entries)) + config.validate_config(["tests/fixtures"], out_file_name="complete_config") # Remove generated output file @@ -321,7 +323,7 @@ def test_register_schema_errors( with pytest.raises(expected_exception): @register_schema(schema_name) - def to_be_decorated(): + def to_be_decorated() -> dict: return schema to_be_decorated() diff --git a/virtual_rainforest/core/__init__.py b/virtual_rainforest/core/__init__.py index c6d8db79d..9889dc098 100644 --- a/virtual_rainforest/core/__init__.py +++ b/virtual_rainforest/core/__init__.py @@ -5,7 +5,7 @@ @register_schema("core") -def schema(): +def schema() -> dict: """Defines the schema that the core module configuration should conform to.""" schema_file = Path(__file__).parent.resolve() / "core_schema.json" diff --git a/virtual_rainforest/core/config.py b/virtual_rainforest/core/config.py index 7262c61eb..c11ab6ad7 100644 --- a/virtual_rainforest/core/config.py +++ b/virtual_rainforest/core/config.py @@ -11,7 +11,7 @@ from pathlib import Path from typing import Callable, Union -import dpath.util +import dpath.util # type: ignore import jsonschema import tomli_w @@ -37,7 +37,7 @@ def register_schema(module_name: str) -> Callable: KeyError: If a module schema is missing one of the required keys """ - def wrap(func: Callable): + def wrap(func: Callable) -> Callable: if module_name in SCHEMA_REGISTRY: log_and_raise( f"The module schema {module_name} is used multiple times, this " @@ -75,7 +75,9 @@ def wrap(func: Callable): COMPLETE_CONFIG: dict = {} -def check_dict_leaves(d1: dict, d2: dict, conflicts: list = [], path: list = []): +def check_dict_leaves( + d1: dict, d2: dict, conflicts: list = [], path: list = [] +) -> list: """Recursively checks if leaves are repeated between two nested dictionaries. Args: diff --git a/virtual_rainforest/core/grid.py b/virtual_rainforest/core/grid.py index 71a3e1885..c3488debd 100644 --- a/virtual_rainforest/core/grid.py +++ b/virtual_rainforest/core/grid.py @@ -12,11 +12,11 @@ import json import logging -from typing import Callable +from typing import Any, Callable import numpy as np -from shapely.affinity import scale, translate -from shapely.geometry import Polygon +from shapely.affinity import scale, translate # type: ignore +from shapely.geometry import Polygon # type: ignore LOGGER = logging.getLogger("virtual_rainforest.core") @@ -250,7 +250,7 @@ def __repr__(self) -> str: f"cell_ny={self.cell_ny})" ) - def dumps(self, dp: int = 2, **kwargs) -> str: + def dumps(self, dp: int = 2, **kwargs: Any) -> str: """Export a grid as a GeoJSON string. The virtual_rainforest.core.Grid object assumes an unspecified projected @@ -267,7 +267,7 @@ def dumps(self, dp: int = 2, **kwargs) -> str: content = self._get_geojson(dp=dp) return json.dumps(obj=content, **kwargs) - def dump(self, outfile: str, dp: int = 2, **kwargs) -> None: + def dump(self, outfile: str, dp: int = 2, **kwargs: Any) -> None: """Export a grid as a GeoJSON file. The virtual_rainforest.core.Grid object assumes an unspecified projected @@ -287,7 +287,7 @@ def dump(self, outfile: str, dp: int = 2, **kwargs) -> None: with open(outfile, "w") as outf: json.dump(obj=content, fp=outf, **kwargs) - def _get_geojson(self, dp): + def _get_geojson(self, dp: int) -> dict: """Convert the grid to a GeoJSON structured dictionary. Args: diff --git a/virtual_rainforest/plants/__init__.py b/virtual_rainforest/plants/__init__.py index 3e3b03e50..9dc046d25 100644 --- a/virtual_rainforest/plants/__init__.py +++ b/virtual_rainforest/plants/__init__.py @@ -5,7 +5,7 @@ @register_schema("plants") -def schema(): +def schema() -> dict: """Defines the schema that the plant module configuration should conform to.""" schema_file = Path(__file__).parent.resolve() / "plants_schema.json"