From 27610da2406242dda05c284ebbaf0ba8a47a63ea Mon Sep 17 00:00:00 2001 From: martonvago Date: Wed, 18 Jun 2025 10:19:39 +0100 Subject: [PATCH 1/4] build: :heavy_plus_sign: add mypy --- justfile | 6 +++++- pyproject.toml | 1 + uv.lock | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index 5ffeefad1..3d7eb9e2d 100644 --- a/justfile +++ b/justfile @@ -2,7 +2,7 @@ just --list --unsorted # Run all build-related recipes in the justfile -run-all: install-deps format-python check-python check-unused test-python check-security check-spelling check-commits build-website +run-all: install-deps format-python check-python type-check-python check-unused test-python check-security check-spelling check-commits build-website # Install the pre-commit hooks install-precommit: @@ -29,6 +29,10 @@ test-python: check-python: uv run ruff check . +# Check Python types +type-check-python: + uv run mypy . + # Reformat Python code to match coding style and general structure format-python: uv run ruff check --fix . diff --git a/pyproject.toml b/pyproject.toml index 18e4d3dee..151c8eccf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ dev = [ "datamodel-code-generator>=0.28.5", "genbadge>=1.1.2", "jupyter>=1.1.1", + "mypy>=1.16.1", "pre-commit>=4.2.0", "pytest>=8.3.5", "pytest-cov>=6.1.1", diff --git a/uv.lock b/uv.lock index a6572aabb..b4598804e 100644 --- a/uv.lock +++ b/uv.lock @@ -1094,6 +1094,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410 }, ] +[[package]] +name = "mypy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493 }, + { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687 }, + { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723 }, + { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980 }, + { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328 }, + { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321 }, + { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480 }, + { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839 }, + { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634 }, + { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584 }, + { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886 }, + { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923 }, +] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1835,6 +1861,7 @@ dev = [ { name = "datamodel-code-generator" }, { name = "genbadge" }, { name = "jupyter" }, + { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1862,6 +1889,7 @@ dev = [ { name = "datamodel-code-generator", specifier = ">=0.28.5" }, { name = "genbadge", specifier = ">=1.1.2" }, { name = "jupyter", specifier = ">=1.1.1" }, + { name = "mypy", specifier = ">=1.16.1" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.1.1" }, From ab4c5ce372ed1843384cccb9c0bc680c3389f8c2 Mon Sep 17 00:00:00 2001 From: martonvago Date: Wed, 18 Jun 2025 16:02:51 +0100 Subject: [PATCH 2/4] build: :wrench: update mypy config --- mypy.ini | 5 +++ pyproject.toml | 3 ++ .../check_datapackage/py.typed | 0 src/seedcase_sprout/py.typed | 0 tools/vulture-allowlist.py | 1 + uv.lock | 39 +++++++++++++++++++ 6 files changed, 48 insertions(+) create mode 100644 mypy.ini create mode 100644 src/seedcase_sprout/check_datapackage/py.typed create mode 100644 src/seedcase_sprout/py.typed diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..4ed651d33 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +python_version = 3.12 + +[mypy-quartodoc.*] +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index 151c8eccf..e7272eda9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,9 @@ dev = [ "quartodoc>=0.9.1", "ruff>=0.11.4", "time-machine>=2.16.0", + "types-jsonschema>=4.24.0.20250528", + "types-requests>=2.32.4.20250611", + "types-tabulate>=0.9.0.20241207", "typos>=1.31.1", "vulture>=2.14", ] diff --git a/src/seedcase_sprout/check_datapackage/py.typed b/src/seedcase_sprout/check_datapackage/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/src/seedcase_sprout/py.typed b/src/seedcase_sprout/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/tools/vulture-allowlist.py b/tools/vulture-allowlist.py index 7972018f3..e32debac0 100644 --- a/tools/vulture-allowlist.py +++ b/tools/vulture-allowlist.py @@ -1,4 +1,5 @@ # ruff: noqa +# type: ignore email # unused variable (src/seedcase_sprout/properties.py:91) given_name # unused variable (src/seedcase_sprout/properties.py:92) family_name # unused variable (src/seedcase_sprout/properties.py:93) diff --git a/uv.lock b/uv.lock index b4598804e..e4c849026 100644 --- a/uv.lock +++ b/uv.lock @@ -1869,6 +1869,9 @@ dev = [ { name = "quartodoc" }, { name = "ruff" }, { name = "time-machine" }, + { name = "types-jsonschema" }, + { name = "types-requests" }, + { name = "types-tabulate" }, { name = "typos" }, { name = "vulture" }, ] @@ -1897,6 +1900,9 @@ dev = [ { name = "quartodoc", specifier = ">=0.9.1" }, { name = "ruff", specifier = ">=0.11.4" }, { name = "time-machine", specifier = ">=2.16.0" }, + { name = "types-jsonschema", specifier = ">=4.24.0.20250528" }, + { name = "types-requests", specifier = ">=2.32.4.20250611" }, + { name = "types-tabulate", specifier = ">=0.9.0.20241207" }, { name = "typos", specifier = ">=1.31.1" }, { name = "vulture", specifier = ">=2.14" }, ] @@ -2099,6 +2105,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, ] +[[package]] +name = "types-jsonschema" +version = "4.24.0.20250528" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/a0/a78769197ca769500565cd2708e12ed5fbc9452da1853effd7d8ad292f0a/types_jsonschema-4.24.0.20250528.tar.gz", hash = "sha256:7e28c64e0ae7980eeb158105b20663fc6a6b8f81d5f86ea6614aa0014417bd1e", size = 15128 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/47/945ecdc1b6741159fca8169565444c1aba0ed7487efe7d408c86151ad50c/types_jsonschema-4.24.0.20250528-py3-none-any.whl", hash = "sha256:6a906b5ff73ac11c8d1e0b6c30a9693e1e4e1ab56c56c932b3a7e081b86d187b", size = 15280 }, +] + [[package]] name = "types-python-dateutil" version = "2.9.0.20241206" @@ -2108,6 +2126,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, ] +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643 }, +] + +[[package]] +name = "types-tabulate" +version = "0.9.0.20241207" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307 }, +] + [[package]] name = "typing-extensions" version = "4.13.1" From a0d302899b2fbcc76d1b471bd6c11ae6265630fb Mon Sep 17 00:00:00 2001 From: martonvago Date: Thu, 19 Jun 2025 08:58:18 +0100 Subject: [PATCH 3/4] fix: :rotating_light: fix mypy errors --- src/seedcase_sprout/as_readme_text.py | 2 +- src/seedcase_sprout/check_data.py | 23 +++++++++++-------- .../check_datapackage/constants.py | 10 +++++--- .../check_datapackage/internals.py | 4 ++-- src/seedcase_sprout/check_properties.py | 9 +++++--- src/seedcase_sprout/constants.py | 2 +- src/seedcase_sprout/examples.py | 4 ++-- src/seedcase_sprout/get_nested_attr.py | 6 ++--- src/seedcase_sprout/internals/functionals.py | 2 +- src/seedcase_sprout/internals/read.py | 2 +- src/seedcase_sprout/join_resource_batches.py | 3 ++- src/seedcase_sprout/map_data_types.py | 5 +++- src/seedcase_sprout/properties.py | 1 + src/seedcase_sprout/read_properties.py | 4 ++-- src/seedcase_sprout/read_resource_batches.py | 2 +- .../get_sprout_resource_errors.py | 2 +- src/seedcase_sprout/write_resource_batch.py | 2 +- src/seedcase_sprout/write_resource_data.py | 2 +- tests/assert_raises_errors.py | 4 ++-- tests/test_create_properties_script.py | 2 ++ tests/test_extract_resource_properties.py | 3 ++- 21 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/seedcase_sprout/as_readme_text.py b/src/seedcase_sprout/as_readme_text.py index ba585b160..ece7d13ba 100644 --- a/src/seedcase_sprout/as_readme_text.py +++ b/src/seedcase_sprout/as_readme_text.py @@ -40,7 +40,7 @@ def join_names(licenses: list[LicenseProperties] | None) -> str: Returns: A comma-separated list of names. """ - return ", ".join(license.name for license in licenses) if licenses else "N/A" + return ", ".join(str(license.name) for license in licenses) if licenses else "N/A" def format_date(created: str | None) -> str: diff --git a/src/seedcase_sprout/check_data.py b/src/seedcase_sprout/check_data.py index c35b52c48..fd25e9390 100644 --- a/src/seedcase_sprout/check_data.py +++ b/src/seedcase_sprout/check_data.py @@ -1,3 +1,5 @@ +from typing import cast + import polars as pl from seedcase_sprout.check_properties import ( @@ -70,7 +72,7 @@ def check_data( def _check_column_names( data: pl.DataFrame, resource_properties: ResourceProperties -) -> str: +) -> pl.DataFrame: """Checks that column names in `data` match those in `resource_properties`. Columns may appear in any order. @@ -89,7 +91,9 @@ def _check_column_names( columns_in_data = data.schema.names() columns_in_resource = [ field.name - for field in get_nested_attr(resource_properties, "schema.fields", default=[]) + for field in cast( + list, get_nested_attr(resource_properties, "schema.fields", default=[]) + ) ] extra_columns_in_data = [ name for name in columns_in_data if name not in columns_in_resource @@ -142,15 +146,16 @@ def _check_column_types( Raises: ExceptionGroup: A group of `ValueError`s, one per incorrectly typed column. """ - fields: list[FieldProperties] = get_nested_attr( - resource_properties, "schema.fields", default=[] + fields = cast( + list[FieldProperties], + get_nested_attr(resource_properties, "schema.fields", default=[]), ) polars_schema = data.schema errors = [ - _get_column_type_error(polars_schema[field.name], field) + _get_column_type_error(polars_schema[str(field.name)], field) for field in fields if not _polars_and_datapackage_types_match( - polars_schema[field.name], field.type + polars_schema[str(field.name)], field.type ) ] @@ -178,16 +183,16 @@ def _get_column_type_error( A `ValueError`. """ allowed_types = _map(_get_allowed_polars_types(field.type), str) - allowed_types = ( + allowed_types_str = ( allowed_types[0] if len(allowed_types) == 1 else f"one of {', '.join(allowed_types)}" ) if field.type == "geopoint": - allowed_types = "an Array of a numeric type with size 2" + allowed_types_str = "an Array of a numeric type with size 2" return ValueError( f"Expected type of column '{field.name}' " - f"to be {allowed_types} but found {polars_type}." + f"to be {allowed_types_str} but found {polars_type}." ) diff --git a/src/seedcase_sprout/check_datapackage/constants.py b/src/seedcase_sprout/check_datapackage/constants.py index fea00c46c..ad1a2d349 100644 --- a/src/seedcase_sprout/check_datapackage/constants.py +++ b/src/seedcase_sprout/check_datapackage/constants.py @@ -14,9 +14,13 @@ class RequiredFieldType(str, Enum): COMPLEX_VALIDATORS = {"allOf", "anyOf", "oneOf"} -DATA_PACKAGE_SCHEMA_PATH: Path = files( - "seedcase_sprout.check_datapackage.schemas" -).joinpath("data-package-schema.json") +DATA_PACKAGE_SCHEMA_PATH = Path( + str( + files("seedcase_sprout.check_datapackage.schemas").joinpath( + "data-package-schema.json" + ) + ) +) NAME_PATTERN = r"^[a-z0-9._-]+$" diff --git a/src/seedcase_sprout/check_datapackage/internals.py b/src/seedcase_sprout/check_datapackage/internals.py index 62f07d2a0..efb2564c4 100644 --- a/src/seedcase_sprout/check_datapackage/internals.py +++ b/src/seedcase_sprout/check_datapackage/internals.py @@ -14,7 +14,7 @@ ) -def _read_json(path: Path) -> list | dict: +def _read_json(path: Path) -> dict: """Reads the contents of a JSON file into an object.""" return loads(path.read_text()) @@ -100,7 +100,7 @@ def _validation_errors_to_check_errors( CheckError( message=error.message, json_path=_get_full_json_path_from_error(error), - validator=error.validator, + validator=str(error.validator), ) for error in _unwrap_errors(list(validation_errors)) if error.validator not in COMPLEX_VALIDATORS diff --git a/src/seedcase_sprout/check_properties.py b/src/seedcase_sprout/check_properties.py index 5afdf05e6..8d400afb6 100644 --- a/src/seedcase_sprout/check_properties.py +++ b/src/seedcase_sprout/check_properties.py @@ -106,7 +106,8 @@ def check_resource_properties(properties: ResourceProperties) -> ResourcePropert # TODO: This probably is better placed in the `check-datapackage` package. except ExceptionGroup as error_info: for error in error_info.exceptions: - error.json_path = error.json_path.replace(".resources[0]", "") + if isinstance(error, cdp.CheckError): + error.json_path = error.json_path.replace(".resources[0]", "") raise error_info return properties @@ -141,8 +142,10 @@ def _generic_check_properties( errors = cdp.check_properties(properties_dict) errors += get_sprout_package_errors(properties_dict) - if isinstance(properties_dict.get("resources"), list): - for index, resource in enumerate(properties_dict.get("resources")): + + resources = properties_dict.get("resources") + if isinstance(resources, list): + for index, resource in enumerate(resources): if isinstance(resource, dict): errors += get_sprout_resource_errors(resource, index) diff --git a/src/seedcase_sprout/constants.py b/src/seedcase_sprout/constants.py index f41be8185..3004abae3 100644 --- a/src/seedcase_sprout/constants.py +++ b/src/seedcase_sprout/constants.py @@ -13,4 +13,4 @@ """The name of the timestamp column added to the batch data (only used internally).""" BATCH_TIMESTAMP_COLUMN_NAME = "_batch_file_timestamp_" -TEMPLATES_PATH: Path = files("seedcase_sprout").joinpath("templates") +TEMPLATES_PATH = Path(str(files("seedcase_sprout").joinpath("templates"))) diff --git a/src/seedcase_sprout/examples.py b/src/seedcase_sprout/examples.py index 574958e4e..a8e21fb43 100644 --- a/src/seedcase_sprout/examples.py +++ b/src/seedcase_sprout/examples.py @@ -340,7 +340,7 @@ class ExamplePackage(AbstractContextManager): def __init__( self, - package_name: str = example_package_properties().name, + package_name: str = str(example_package_properties().name), with_resources: bool = True, ): """Initialise the `ExamplePackage` context manager. @@ -375,7 +375,7 @@ def __enter__(self) -> PackagePath: package_properties.resources = [resource_properties] # Create resource folders - package_path.resource(resource_properties.name).mkdir( + package_path.resource(str(resource_properties.name)).mkdir( exist_ok=True, parents=True ) diff --git a/src/seedcase_sprout/get_nested_attr.py b/src/seedcase_sprout/get_nested_attr.py index 899918381..e0e9089c3 100644 --- a/src/seedcase_sprout/get_nested_attr.py +++ b/src/seedcase_sprout/get_nested_attr.py @@ -36,14 +36,14 @@ class Outer: get_nested_attr(Outer(), "middle.inner") ``` """ - attributes = attributes.split(".") - if any(not attribute.isidentifier() for attribute in attributes): + attributes_list = attributes.split(".") + if any(not attribute.isidentifier() for attribute in attributes_list): raise ValueError( "`attributes` should contain valid identifiers separated by `.`." ) try: - for attribute in attributes: + for attribute in attributes_list: base_object = getattr(base_object, attribute) except AttributeError: return default diff --git a/src/seedcase_sprout/internals/functionals.py b/src/seedcase_sprout/internals/functionals.py index 6563bde17..638da05e0 100644 --- a/src/seedcase_sprout/internals/functionals.py +++ b/src/seedcase_sprout/internals/functionals.py @@ -73,5 +73,5 @@ def add(a, b): ``` """ if len(y) == 1: - y = repeat(y[0], len(x)) + y = list(repeat(y[0], len(x))) return list(map(fn, x, y)) diff --git a/src/seedcase_sprout/internals/read.py b/src/seedcase_sprout/internals/read.py index 1ff162083..420c09227 100644 --- a/src/seedcase_sprout/internals/read.py +++ b/src/seedcase_sprout/internals/read.py @@ -2,7 +2,7 @@ from pathlib import Path -def _read_json(path: Path) -> list | dict: +def _read_json(path: Path) -> dict: """Reads the contents of a JSON file into an object. Args: diff --git a/src/seedcase_sprout/join_resource_batches.py b/src/seedcase_sprout/join_resource_batches.py index 3019663d9..677e2581b 100644 --- a/src/seedcase_sprout/join_resource_batches.py +++ b/src/seedcase_sprout/join_resource_batches.py @@ -5,6 +5,7 @@ check_resource_properties, ) from seedcase_sprout.constants import BATCH_TIMESTAMP_COLUMN_NAME +from seedcase_sprout.get_nested_attr import get_nested_attr from seedcase_sprout.properties import ResourceProperties @@ -69,7 +70,7 @@ def join_resource_batches( ) data = pl.concat(data_list) - primary_key = resource_properties.schema.primary_key + primary_key = get_nested_attr(resource_properties, "schema.primary_key") data = _drop_duplicate_obs_units(data, primary_key) check_data(data, resource_properties) diff --git a/src/seedcase_sprout/map_data_types.py b/src/seedcase_sprout/map_data_types.py index 269461982..13c3803fe 100644 --- a/src/seedcase_sprout/map_data_types.py +++ b/src/seedcase_sprout/map_data_types.py @@ -1,3 +1,5 @@ +from typing import cast + import polars as pl from seedcase_sprout.properties import FieldType @@ -49,7 +51,8 @@ def _get_allowed_datapackage_types(polars_type: pl.DataType) -> list[FieldType]: Returns: The allowed Data Package types. """ - allowed_types = _POLARS_TO_DATAPACKAGE.get(polars_type.base_type(), ["any"]) + base_type = cast(type[pl.DataType], polars_type.base_type()) + allowed_types = _POLARS_TO_DATAPACKAGE.get(base_type, ["any"]) if ( isinstance(polars_type, pl.Array) diff --git a/src/seedcase_sprout/properties.py b/src/seedcase_sprout/properties.py index b8eb99049..306bf3da4 100644 --- a/src/seedcase_sprout/properties.py +++ b/src/seedcase_sprout/properties.py @@ -25,6 +25,7 @@ ) +@dataclass class Properties(ABC): """An abstract base class for all `*Properties` classes holding common logic.""" diff --git a/src/seedcase_sprout/read_properties.py b/src/seedcase_sprout/read_properties.py index 7a51b88d5..5d9742c05 100644 --- a/src/seedcase_sprout/read_properties.py +++ b/src/seedcase_sprout/read_properties.py @@ -35,7 +35,7 @@ def read_properties(path: Path | None = None) -> PackageProperties: """ path = path or PackagePath().properties() _check_is_file(path) - properties = _read_json(path) - properties = PackageProperties.from_dict(properties) + properties_dict = _read_json(path) + properties = PackageProperties.from_dict(properties_dict) check_properties(properties) return properties diff --git a/src/seedcase_sprout/read_resource_batches.py b/src/seedcase_sprout/read_resource_batches.py index 8bbcf8e95..9d02d82e4 100644 --- a/src/seedcase_sprout/read_resource_batches.py +++ b/src/seedcase_sprout/read_resource_batches.py @@ -60,7 +60,7 @@ def read_resource_batches( """ check_resource_properties(resource_properties) if paths is None: - paths = PackagePath().resource_batch_files(resource_properties.name) + paths = PackagePath().resource_batch_files(str(resource_properties.name)) _map(paths, _check_is_file) return _map2(paths, [resource_properties], _read_parquet_batch_file) diff --git a/src/seedcase_sprout/sprout_checks/get_sprout_resource_errors.py b/src/seedcase_sprout/sprout_checks/get_sprout_resource_errors.py index 80b2b371f..a7187f420 100644 --- a/src/seedcase_sprout/sprout_checks/get_sprout_resource_errors.py +++ b/src/seedcase_sprout/sprout_checks/get_sprout_resource_errors.py @@ -72,7 +72,7 @@ def _check_resource_path_format( ): return [] - expected_path = _create_resource_data_path(name) + expected_path = _create_resource_data_path(str(name)) if path == expected_path: return [] diff --git a/src/seedcase_sprout/write_resource_batch.py b/src/seedcase_sprout/write_resource_batch.py index 477c7cdec..560b8c0ba 100644 --- a/src/seedcase_sprout/write_resource_batch.py +++ b/src/seedcase_sprout/write_resource_batch.py @@ -58,7 +58,7 @@ def write_resource_batch( check_resource_properties(resource_properties) check_data(data, resource_properties) - batch_path = PackagePath(package_path).resource_batch(resource_properties.name) + batch_path = PackagePath(package_path).resource_batch(str(resource_properties.name)) batch_path.mkdir(exist_ok=True, parents=True) # TODO: Move out some of this into the create_batch_file_name during refactoring batch_file_path = batch_path / _create_batch_file_name() diff --git a/src/seedcase_sprout/write_resource_data.py b/src/seedcase_sprout/write_resource_data.py index 86105960f..50c783143 100644 --- a/src/seedcase_sprout/write_resource_data.py +++ b/src/seedcase_sprout/write_resource_data.py @@ -48,7 +48,7 @@ def write_resource_data( sp.write_resource_data(data, resource_properties) """ check_data(data, resource_properties) - data_path = PackagePath(package_path).resource_data(resource_properties.name) + data_path = PackagePath(package_path).resource_data(str(resource_properties.name)) data.write_parquet(data_path) return data_path diff --git a/tests/assert_raises_errors.py b/tests/assert_raises_errors.py index bb11b6898..48bef1deb 100644 --- a/tests/assert_raises_errors.py +++ b/tests/assert_raises_errors.py @@ -6,7 +6,7 @@ def assert_raises_errors( - fn: Callable, error_type: type[BaseException], error_count: int = None + fn: Callable, error_type: type[BaseException], error_count: int | None = None ) -> None: """Asserts that the function raises a group of errors of the given type.""" with raises(ExceptionGroup) as error_info: @@ -18,6 +18,6 @@ def assert_raises_errors( assert len(errors) == error_count -def assert_raises_check_errors(fn: Callable, error_count: int = None) -> None: +def assert_raises_check_errors(fn: Callable, error_count: int | None = None) -> None: """Asserts that the function raises a group of `CheckError`s.""" assert_raises_errors(fn, CheckError, error_count) diff --git a/tests/test_create_properties_script.py b/tests/test_create_properties_script.py index 93ce4948a..9b466ad06 100644 --- a/tests/test_create_properties_script.py +++ b/tests/test_create_properties_script.py @@ -42,6 +42,8 @@ def test_works_with_custom_path(tmp_path): def load_properties(path: Path) -> PackageProperties: """Loads `properties` object from file.""" spec = spec_from_file_location("test_module", path) + assert spec + assert spec.loader module = module_from_spec(spec) spec.loader.exec_module(module) return module.properties diff --git a/tests/test_extract_resource_properties.py b/tests/test_extract_resource_properties.py index 8a846812d..ed55c0984 100644 --- a/tests/test_extract_resource_properties.py +++ b/tests/test_extract_resource_properties.py @@ -19,7 +19,8 @@ def _keep_extractable_properties( example_properties: ResourceProperties, ) -> ResourceProperties: """Filter example properties to only keep the extractable properties.""" - + assert example_properties.schema + assert example_properties.schema.fields fields = list( map( lambda field: FieldProperties(name=field.name, type=field.type), From 544c1ecadfafc6d50e72f9532f720a7a16e94172 Mon Sep 17 00:00:00 2001 From: martonvago Date: Thu, 19 Jun 2025 13:57:42 +0100 Subject: [PATCH 4/4] refactor: :recycle: move mypy check into check-python --- justfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/justfile b/justfile index 3d7eb9e2d..b009dfd06 100644 --- a/justfile +++ b/justfile @@ -2,7 +2,7 @@ just --list --unsorted # Run all build-related recipes in the justfile -run-all: install-deps format-python check-python type-check-python check-unused test-python check-security check-spelling check-commits build-website +run-all: install-deps format-python check-python check-unused test-python check-security check-spelling check-commits build-website # Install the pre-commit hooks install-precommit: @@ -25,12 +25,11 @@ test-python: -i coverage.xml \ -o htmlcov/coverage.svg -# Check Python code with the linter for any errors that need manual attention +# Check Python code for any errors that need manual attention check-python: + # Check formatting uv run ruff check . - -# Check Python types -type-check-python: + # Check types uv run mypy . # Reformat Python code to match coding style and general structure