diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7634c12..c2b72c3 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c937782..c1b70d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,11 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.16 + hooks: + - id: validate-pyproject + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: @@ -10,28 +15,25 @@ repos: - id: check-added-large-files args: ["--maxkb=40960"] - - repo: local + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 hooks: - - id: black - name: Black Formatting - language: system - types: [python] - entry: black - - id: isort - name: iSort Import Sorting - language: system - types: [python] - entry: isort + language_version: python - - id: flake8 - name: Flake8 Formatting - language: system - types: [python] - entry: flake8 --config=./pyproject.toml + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 + hooks: + - id: ruff + args: ["--fix"] + - id: ruff-format + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.9.0 + hooks: - id: mypy - name: MyPy Typecheck - language: system - types: [python] - entry: mypy --config-file=./pyproject.toml + language_version: python + # No reason to run if only tests have changed. They intentionally break typing. + exclude: tests/.* + additional_dependencies: + - types-requests diff --git a/pyproject.toml b/pyproject.toml index 174415e..8eca9d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,12 @@ keywords=["stac", "pydantic", "validation"] authors=[{ name = "Arturo Engineering", email = "engineering@arturo.ai"}] license= { text = "MIT" } requires-python=">=3.8" -dependencies = ["click>=8.1.7", "pydantic>=2.4.1", "geojson-pydantic>=1.0.0", "ciso8601~=2.3"] +dependencies = [ + "click>=8.1.7", + "pydantic>=2.4.1", + "geojson-pydantic>=1.0.0", + "ciso8601~=2.3", +] dynamic = ["version", "readme"] [project.scripts] @@ -30,7 +35,8 @@ homepage = "https://github.com/stac-utils/stac-pydantic" repository ="https://github.com/stac-utils/stac-pydantic.git" [project.optional-dependencies] -dev = ["arrow>=1.2.3", +dev = [ + "arrow>=1.2.3", "pytest>=7.4.2", "pytest-cov>=4.1.0", "pytest-icdiff>=0.8", @@ -38,8 +44,11 @@ dev = ["arrow>=1.2.3", "shapely>=2.0.1", "dictdiffer>=0.9.0", "jsonschema>=4.19.1", - "pyyaml>=6.0.1"] -lint = ["types-requests>=2.31.0.5", + "pyyaml>=6.0.1" +] + +lint = [ + "types-requests>=2.31.0.5", "types-jsonschema>=4.19.0.3", "types-PyYAML>=6.0.12.12", "black>=23.9.1", @@ -48,7 +57,8 @@ lint = ["types-requests>=2.31.0.5", "Flake8-pyproject>=1.2.3", "mypy>=1.5.1", "pre-commit>=3.4.0", - "tox>=4.11.3"] + "tox>=4.11.3" +] [tool.setuptools.dynamic] version = { attr = "stac_pydantic.version.__version__" } @@ -64,10 +74,6 @@ exclude = ["tests*"] [tool.pytest.ini_options] addopts = "-sv --cov stac_pydantic --cov-report xml --cov-report term-missing --cov-fail-under 95" -[tool.black] -line-length = 88 -target-version = ["py311"] - [tool.isort] profile = "black" known_first_party = "stac_pydantic" @@ -75,12 +81,20 @@ known_third_party = ["pydantic", "geojson-pydantic", "click"] default_section = "THIRDPARTY" [tool.mypy] -plugins = "pydantic.mypy" ignore_missing_imports = true exclude = ["tests", ".venv"] -[tool.flake8] -ignore = ["E203", "E501"] -select = ["C","E","F","W","B","B950"] -exclude = ["tests", ".venv"] -max-line-length = 88 +[tool.ruff] +line-length = 88 +select = [ + "C", + "E", + "F", + "W", + "B", +] +ignore = [ + "E203", # line too long, handled by black + "E501", # do not perform function calls in argument defaults + "B028", # No explicit `stacklevel` keyword argument found +] diff --git a/stac_pydantic/api/search.py b/stac_pydantic/api/search.py index a005369..a8ff277 100644 --- a/stac_pydantic/api/search.py +++ b/stac_pydantic/api/search.py @@ -2,8 +2,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast from ciso8601 import parse_rfc3339 -from geojson_pydantic.geometries import ( # type: ignore - GeometryCollection, +from geojson_pydantic.geometries import GeometryCollection # type: ignore +from geojson_pydantic.geometries import ( LineString, MultiLineString, MultiPoint, @@ -117,7 +117,9 @@ def validate_datetime(cls, v: str) -> str: dates.append(parse_rfc3339(value)) if len(values) > 2: - raise ValueError("Invalid datetime range, must match format (begin_date, end_date)") + raise ValueError( + "Invalid datetime range, must match format (begin_date, end_date)" + ) if not {"..", ""}.intersection(set(values)): if dates[0] > dates[1]: diff --git a/tests/api/test_search.py b/tests/api/test_search.py index ab44566..7bb9a1b 100644 --- a/tests/api/test_search.py +++ b/tests/api/test_search.py @@ -1,5 +1,5 @@ import time -from datetime import datetime, timezone, timedelta +from datetime import datetime, timedelta, timezone import pytest from pydantic import ValidationError @@ -97,7 +97,10 @@ def test_invalid_temporal_search(): t2 = t1 + timedelta(seconds=100) t3 = t2 + timedelta(seconds=100) with pytest.raises(ValidationError): - Search(collections=["collection1"], datetime=f"{t1.strftime(DATETIME_RFC339)}/{t2.strftime(DATETIME_RFC339)}/{t3.strftime(DATETIME_RFC339)}",) + Search( + collections=["collection1"], + datetime=f"{t1.strftime(DATETIME_RFC339)}/{t2.strftime(DATETIME_RFC339)}/{t3.strftime(DATETIME_RFC339)}", + ) # End date is before start date start = datetime.utcnow() diff --git a/tests/conftest.py b/tests/conftest.py index dc52afb..9b3ca20 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,10 @@ from pydantic import BaseModel -def request(url: str, path: List[str] = ["tests", "example_stac"]): +def request(url: str, path: Optional[List[str]] = None): + if path is None: + path = ["tests", "example_stac"] + if url.startswith("http"): r = requests.get(url) r.raise_for_status() @@ -59,8 +62,11 @@ def compare_example( example_url: str, model: Type[BaseModel], fields: Optional[List[str]] = None, - path: List[str] = ["tests", "example_stac"], + path: Optional[List[str]] = None, ) -> None: + if path is None: + path = ["tests", "example_stac"] + example = request(example_url, path) model_dict = json.loads(model(**example).model_dump_json()) diff --git a/tests/test_models.py b/tests/test_models.py index 0a9a22f..5079ae5 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -65,9 +65,9 @@ def test_sar_extensions() -> None: def test_proj_extension() -> None: # The example item uses an invalid band name test_item = request(PROJ_EXTENSION) - test_item["stac_extensions"][ - 1 - ] = "https://raw.githubusercontent.com/stac-extensions/projection/v1.0.0/json-schema/schema.json" + test_item["stac_extensions"][1] = ( + "https://raw.githubusercontent.com/stac-extensions/projection/v1.0.0/json-schema/schema.json" + ) test_item["assets"]["B8"]["eo:bands"][0]["common_name"] = "pan" valid_item = Item.model_validate(test_item).model_dump() @@ -149,7 +149,7 @@ def test_asset_extras() -> None: test_item["assets"][asset]["foo"] = "bar" item = Item(**test_item) - for asset_name, asset in item.assets.items(): + for _, asset in item.assets.items(): assert asset.foo == "bar" diff --git a/tox.ini b/tox.ini index 3f7b0e6..8b3f6b6 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ commands = python -m pytest [testenv:lint] extras = lint description = run linters -commands = SKIP=mypy pre-commit run --all-files +commands = SKIP=mypy pre-commit run --all-files [testenv:type] extras = lint