From f6f9fcd05d0b11e575eefc3640fa0e4e764671e6 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Mar 2022 15:35:56 -0500 Subject: [PATCH 01/12] build improvements --- .github/workflows/cicd.yml | 2 +- .pre-commit-config.yaml | 16 +++++++++---- README.md | 43 +++++++++++++++++++++++++++-------- setup.py | 4 +++- stac_pydantic/__init__.py | 1 + stac_pydantic/api/__init__.py | 1 + stac_pydantic/api/landing.py | 2 +- stac_pydantic/catalog.py | 2 +- stac_pydantic/scripts/cli.py | 2 +- stac_pydantic/utils.py | 3 --- tests/conftest.py | 1 - tests/test_models.py | 25 +++++++++++--------- tox.ini | 20 ++++++++++------ 13 files changed, 82 insertions(+), 40 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 1de2131..eddd2f9 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.7, 3.8] + python-version: [3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1fc7cc7..8d2b217 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,12 +7,20 @@ repos: args: ["--safe"] - repo: https://github.com/PyCQA/isort - rev: 5.4.2 + rev: 5.10.1 hooks: - id: isort language_version: python3 - - repo: https://github.com/PyCQA/isort - rev: 5.4.2 + + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - - id: isort + - id: flake8 language_version: python3 + +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v0.931 +# hooks: +# - id: mypy +# exclude: ^tests/ +# args: [--strict] \ No newline at end of file diff --git a/README.md b/README.md index 94a49a6..5f3f770 100644 --- a/README.md +++ b/README.md @@ -11,32 +11,57 @@ For local development: pip install -e .["dev"] ``` -| stac-pydantic | stac | -|-------------------|--------------| -| 1.1.x | 0.9.0 | -| 1.2.x | 1.0.0-beta.1 | -| 1.3.x | 1.0.0-beta.2 | -| 2.0.x | 1.0.0 | +| stac-pydantic | STAC Version | +|---------------|--------------| +| 1.1.x | 0.9.0 | +| 1.2.x | 1.0.0-beta.1 | +| 1.3.x | 1.0.0-beta.2 | +| 2.0.x | 1.0.0 | + +## Development + +Install the [pre-commit](https://pre-commit.com/) hooks: + +```shell +pre-commit install +``` ## Testing -Run the entire test suite: + +Ensure you have all Python versions installed that the tests will be run against. If using pyenv, run: + +```shell +pyenv install 3.7.12 +pyenv install 3.8.12 +pyenv install 3.9.10 +pyenv install 3.10.2 +pyenv local 3.7.12 3.8.12 3.9.10 3.10.2 ``` + +Run the entire test suite: + +```shell tox ``` Run a single test case using the standard pytest convention: -``` + +```shell pytest -v tests/test_models.py::test_item_extensions ``` ## Usage + ### Loading Models + Load data into models with standard pydantic: + ```python from stac_pydantic import Catalog stac_catalog = { - "stac_version": "0.9.0", + "type": "Catalog", + "stac_version": "1.0.0", "id": "sample", "description": "This is a very basic sample catalog.", "links": [ diff --git a/setup.py b/setup.py index 3714982..5312ff6 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,8 @@ "Intended Audience :: Science/Research", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "License :: OSI Approved :: MIT License", ], keywords="stac pydantic validation", @@ -37,7 +39,7 @@ license="MIT", packages=find_packages(exclude=["tests"]), zip_safe=False, - install_requires=["click", "pydantic>=1.6", "geojson-pydantic"], + install_requires=["click", "pydantic>=1.6", "geojson-pydantic", "ciso8601>=2.2"], tests_require=extras["dev"], setup_requires=["pytest-runner"], entry_points={"console_scripts": ["stac-pydantic=stac_pydantic.scripts.cli:app"]}, diff --git a/stac_pydantic/__init__.py b/stac_pydantic/__init__.py index 2c67cf2..872c09d 100644 --- a/stac_pydantic/__init__.py +++ b/stac_pydantic/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa: F401 from .catalog import Catalog from .collection import Collection from .item import Item, ItemCollection, ItemProperties diff --git a/stac_pydantic/api/__init__.py b/stac_pydantic/api/__init__.py index a3321f0..e8b40a2 100644 --- a/stac_pydantic/api/__init__.py +++ b/stac_pydantic/api/__init__.py @@ -1,3 +1,4 @@ +# flake8: noqa: F401 from .collections import Collections from .conformance import ConformanceClasses from .landing import LandingPage diff --git a/stac_pydantic/api/landing.py b/stac_pydantic/api/landing.py index f866273..ba6314b 100644 --- a/stac_pydantic/api/landing.py +++ b/stac_pydantic/api/landing.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Union +from typing import List, Optional from pydantic import AnyUrl, BaseModel, Field, constr diff --git a/stac_pydantic/catalog.py b/stac_pydantic/catalog.py index 8d01c17..7ad8240 100644 --- a/stac_pydantic/catalog.py +++ b/stac_pydantic/catalog.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import AnyUrl, BaseModel, Field, constr, root_validator +from pydantic import AnyUrl, BaseModel, Field, constr from stac_pydantic.links import Links from stac_pydantic.version import STAC_VERSION diff --git a/stac_pydantic/scripts/cli.py b/stac_pydantic/scripts/cli.py index 7b891fc..8509f6c 100644 --- a/stac_pydantic/scripts/cli.py +++ b/stac_pydantic/scripts/cli.py @@ -2,7 +2,7 @@ import requests from pydantic import ValidationError -from stac_pydantic import Catalog, Collection, Item +from stac_pydantic import Item from stac_pydantic.extensions import validate_extensions diff --git a/stac_pydantic/utils.py b/stac_pydantic/utils.py index adeda2f..9f6b41f 100644 --- a/stac_pydantic/utils.py +++ b/stac_pydantic/utils.py @@ -1,7 +1,4 @@ from enum import Enum -from typing import Dict, Optional, Type - -from pydantic import BaseModel class AutoValueEnum(Enum): diff --git a/tests/conftest.py b/tests/conftest.py index b7a3a9d..2f1337e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,6 @@ import json import operator import os -from datetime import datetime, timedelta import arrow import dictdiffer diff --git a/tests/test_models.py b/tests/test_models.py index 5e85610..cece74f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -22,8 +22,10 @@ ITEM_COLLECTION = "itemcollection-sample-full.json" SINGLE_FILE_STAC = "example-search.json" -# ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION}/extensions/asset/examples/example-landsat8.json" -# COLLECTION_ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION}/extensions/collection-assets/examples/example-esm.json" +# ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION}/extensions +# /asset/examples/example-landsat8.json" +# COLLECTION_ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION} +# /extensions/collection-assets/examples/example-esm.json" DATACUBE_EXTENSION = "example-item_datacube-extension.json" EO_EXTENSION = "example-landsat8_eo-extension.json" ITEM_ASSET_EXTENSION = "example-landsat8_item-assets-extension.json" @@ -159,7 +161,7 @@ def test_invalid_geometry(): # Remove the last coordinate test_item["geometry"]["coordinates"][0].pop(-1) - with pytest.raises(ValidationError) as e: + with pytest.raises(ValidationError): Item(**test_item) @@ -232,7 +234,7 @@ def test_api_landing_page_is_catalog(): ) ], ) - catalog = Catalog(**landing_page.dict()) + Catalog(**landing_page.dict()) def test_search(): @@ -286,7 +288,7 @@ def test_temporal_search_single_tailed(): utcnow = datetime.utcnow().replace(microsecond=0, tzinfo=timezone.utc) utcnow_str = utcnow.strftime(DATETIME_RFC339) search = Search(collections=["collection1"], datetime=utcnow_str) - assert search.start_date == None + assert search.start_date is None assert search.end_date == utcnow @@ -299,31 +301,32 @@ def test_temporal_search_two_tailed(): search = Search(collections=["collection1"], datetime=f"{utcnow_str}/..") assert search.start_date == utcnow - assert search.end_date == None + assert search.end_date is None search = Search(collections=["collection1"], datetime=f"{utcnow_str}/") assert search.start_date == utcnow - assert search.end_date == None + assert search.end_date is None def test_temporal_search_open(): # Test open date range search = Search(collections=["collection1"], datetime="../..") - assert search.start_date == search.end_date == None + assert search.start_date is None + assert search.end_date is None def test_invalid_temporal_search(): # Not RFC339 utcnow = datetime.utcnow().strftime("%Y-%m-%d") with pytest.raises(ValidationError): - search = Search(collections=["collection1"], datetime=utcnow) + Search(collections=["collection1"], datetime=utcnow) # End date is before start date start = datetime.utcnow() time.sleep(2) end = datetime.utcnow() with pytest.raises(ValidationError): - search = Search( + Search( collections=["collection1"], datetime=f"{end.strftime(DATETIME_RFC339)}/{start.strftime(DATETIME_RFC339)}", ) @@ -442,7 +445,7 @@ class TestProperties(ItemProperties): class Config: allow_population_by_fieldname = True - alias_generator = lambda field_name: f"test:{field_name}" + alias_generator = lambda field_name: f"test:{field_name}" # noqa: E731 class TestItem(Item): properties: TestProperties diff --git a/tox.ini b/tox.ini index 27ad836..60b2e15 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,23 @@ [tox] -envlist = py37,py38 +envlist = py37,py38,py39,py310 [testenv] -extras = dev -commands= +extras = + dev +deps = + flake8 + black +; mypy +commands = python -m pytest --cov stac_pydantic --cov-report xml --cov-report term-missing -deps= - numpy + flake8 . + black . +; mypy stac_pydantic/ # Lint [flake8] -exclude = .git,__pycache__,docs/source/conf.py,old,build,dist -max-line-length = 90 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.venv,.tox,.idea +max-line-length = 120 [mypy] no_strict_optional = True From aca2643f099b9ac6323a02f56a851a3c8baded88 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Mar 2022 15:40:23 -0500 Subject: [PATCH 02/12] remove ciso8601 library --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5312ff6..a493d5f 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ license="MIT", packages=find_packages(exclude=["tests"]), zip_safe=False, - install_requires=["click", "pydantic>=1.6", "geojson-pydantic", "ciso8601>=2.2"], + install_requires=["click", "pydantic>=1.6", "geojson-pydantic"], tests_require=extras["dev"], setup_requires=["pytest-runner"], entry_points={"console_scripts": ["stac-pydantic=stac_pydantic.scripts.cli:app"]}, From a4cae45abbf06bc7c2e38a7451ec484a40358b32 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Mar 2022 15:43:47 -0500 Subject: [PATCH 03/12] make github python-version values strings --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index eddd2f9..4194ea0 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.7, 3.8, 3.9, 3.10] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 From 1b5b86ebc5316b9cecd4c5d54295ae4400848648 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 1 Mar 2022 16:29:47 -0500 Subject: [PATCH 04/12] configure flake8 to defer to black for some formatting --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 60b2e15..3439ec2 100644 --- a/tox.ini +++ b/tox.ini @@ -14,10 +14,11 @@ commands = black . ; mypy stac_pydantic/ -# Lint [flake8] exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.venv,.tox,.idea -max-line-length = 120 +max-line-length = 88 +select = C,E,F,W,B,B950 +extend-ignore = E203, E501 [mypy] no_strict_optional = True From 1749e5d43ca84c7216fa36dd49e49657c4ddccb1 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 28 Apr 2022 13:49:38 -0500 Subject: [PATCH 05/12] Remove unused variable --- tests/test_models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index cece74f..899b091 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -559,7 +559,5 @@ def test_geometry_null_item(): def test_item_bbox_validation(): test_item = request(LABEL_EXTENSION) test_item["bbox"] = None - with pytest.raises( - ValueError, match="bbox is required if geometry is not null" - ) as e: + with pytest.raises(ValueError, match="bbox is required if geometry is not null"): Item(**test_item) From 676eb2004e0dfd354d68e527e952a573cce544c6 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 28 Apr 2022 17:01:48 -0500 Subject: [PATCH 06/12] Add type hints --- .pre-commit-config.yaml | 12 ++++----- setup.cfg | 3 +++ setup.py | 2 ++ stac_pydantic/api/extensions/fields.py | 4 +-- stac_pydantic/api/extensions/sort.py | 4 +-- stac_pydantic/api/landing.py | 8 +++--- stac_pydantic/api/search.py | 20 +++++++------- stac_pydantic/api/utils/link_factory.py | 6 ++--- stac_pydantic/catalog.py | 10 +++---- stac_pydantic/collection.py | 6 ++--- stac_pydantic/extensions.py | 36 ++++++++----------------- stac_pydantic/item.py | 12 ++++----- stac_pydantic/links.py | 16 +++++------ stac_pydantic/shared.py | 8 +++--- tests/conftest.py | 2 +- tests/test_api_extensions.py | 2 +- tests/test_models.py | 2 +- 17 files changed, 73 insertions(+), 80 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8d2b217..419124e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,9 @@ repos: - id: flake8 language_version: python3 -# - repo: https://github.com/pre-commit/mirrors-mypy -# rev: v0.931 -# hooks: -# - id: mypy -# exclude: ^tests/ -# args: [--strict] \ No newline at end of file + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.931 + hooks: + - id: mypy + exclude: ^tests/ + args: [--strict] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 921a2cd..4241d77 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,6 @@ test=pytest [tool:pytest] addopts=-sv --cov=stac_pydantic --cov-fail-under=95 --cov-report=term-missing + +[mypy] +plugins = pydantic.mypy \ No newline at end of file diff --git a/setup.py b/setup.py index a493d5f..eca5008 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,8 @@ "shapely", "dictdiffer", "jsonschema", + "types-requests", + "types-jsonschema" ], } diff --git a/stac_pydantic/api/extensions/fields.py b/stac_pydantic/api/extensions/fields.py index 5425351..493673d 100644 --- a/stac_pydantic/api/extensions/fields.py +++ b/stac_pydantic/api/extensions/fields.py @@ -37,7 +37,7 @@ def filter(self) -> Dict: Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude """ - include = set() + include: Set[str] = set() # If only include is specified, add fields to the set if self.includes and not self.excludes: include = include.union(self.includes) @@ -46,5 +46,5 @@ def filter(self) -> Dict: include = include.union(self.includes) - self.excludes return { "include": self._get_field_dict(include), - "exclude": self._get_field_dict(self.excludes), + "exclude": self._get_field_dict(self.excludes or Set()), } diff --git a/stac_pydantic/api/extensions/sort.py b/stac_pydantic/api/extensions/sort.py index 2ff76a4..8051fb3 100644 --- a/stac_pydantic/api/extensions/sort.py +++ b/stac_pydantic/api/extensions/sort.py @@ -1,6 +1,6 @@ from enum import auto -from pydantic import BaseModel, constr +from pydantic import BaseModel, Field from stac_pydantic.utils import AutoValueEnum @@ -15,5 +15,5 @@ class SortExtension(BaseModel): https://github.com/radiantearth/stac-api-spec/tree/master/extensions/sort#sort-api-extension """ - field: constr(min_length=1) + field: str = Field(..., alias="field", min_length=1) direction: SortDirections diff --git a/stac_pydantic/api/landing.py b/stac_pydantic/api/landing.py index ba6314b..616eadf 100644 --- a/stac_pydantic/api/landing.py +++ b/stac_pydantic/api/landing.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import AnyUrl, BaseModel, Field, constr +from pydantic import AnyUrl, BaseModel, Field from stac_pydantic.links import Links from stac_pydantic.version import STAC_VERSION @@ -11,11 +11,11 @@ class LandingPage(BaseModel): https://github.com/radiantearth/stac-api-spec/blob/master/api-spec.md#ogc-api---features-endpoints """ - id: constr(min_length=1) - description: constr(min_length=1) + id: str = Field(..., alias="id", min_length=1) + description: str = Field(..., alias="description", min_length=1) title: Optional[str] stac_version: str = Field(STAC_VERSION, const=True) stac_extensions: Optional[List[AnyUrl]] conformsTo: List[AnyUrl] links: Links - type: constr(min_length=1) = Field("Catalog", const=True) + type: str = Field("Catalog", const=True, min_length=1) diff --git a/stac_pydantic/api/search.py b/stac_pydantic/api/search.py index 4079d7a..19598b5 100644 --- a/stac_pydantic/api/search.py +++ b/stac_pydantic/api/search.py @@ -1,5 +1,5 @@ -from datetime import datetime -from typing import Any, Dict, List, Optional, Union +from datetime import datetime as dt +from typing import Any, Dict, List, Optional, Tuple, Union, cast from geojson_pydantic.geometries import ( GeometryCollection, @@ -45,8 +45,8 @@ class Search(BaseModel): limit: int = 10 @property - def start_date(self) -> Optional[datetime]: - values = self.datetime.split("/") + def start_date(self) -> Optional[dt]: + values = (self.datetime or "").split("/") if len(values) == 1: return None if values[0] == ".." or values[0] == "": @@ -54,8 +54,8 @@ def start_date(self) -> Optional[datetime]: return parse_datetime(values[0]) @property - def end_date(self) -> Optional[datetime]: - values = self.datetime.split("/") + def end_date(self) -> Optional[dt]: + values = (self.datetime or "").split("/") if len(values) == 1: return parse_datetime(values[0]) if values[1] == ".." or values[1] == "": @@ -73,9 +73,11 @@ def validate_bbox(cls, v: BBox): if v: # Validate order if len(v) == 4: - xmin, ymin, xmax, ymax = v + xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], (*v, 4)) else: - xmin, ymin, min_elev, xmax, ymax, max_elev = v + xmin, ymin, min_elev, xmax, ymax, max_elev = cast( + Tuple[int, int, int, int, int, int], (*v, 6) + ) if max_elev < min_elev: raise ValueError( "Maximum elevation must greater than minimum elevation" @@ -142,7 +144,7 @@ def spatial_filter(self) -> Optional[_GeometryBase]: ) if self.intersects: return self.intersects - return + return None class ExtendedSearch(Search): diff --git a/stac_pydantic/api/utils/link_factory.py b/stac_pydantic/api/utils/link_factory.py index 1750e26..90d341b 100644 --- a/stac_pydantic/api/utils/link_factory.py +++ b/stac_pydantic/api/utils/link_factory.py @@ -11,7 +11,7 @@ class BaseLinks: """Create inferred links common to collections and items.""" base_url: str - _link_members: ClassVar[Tuple[str]] = ("root",) + _link_members: ClassVar[Tuple[str, ...]] = ("root",) def root(self) -> Link: """Return the catalog root.""" @@ -31,7 +31,7 @@ class CollectionLinks(BaseLinks): """Create inferred links specific to collections.""" collection_id: str - _link_members: ClassVar[Tuple[str]] = ("root", "self", "parent", "item") + _link_members: ClassVar[Tuple[str, ...]] = ("root", "self", "parent", "item") def self(self) -> Link: """Create the `self` link.""" @@ -62,7 +62,7 @@ class ItemLinks(BaseLinks): collection_id: str item_id: str - _link_members: ClassVar[Tuple[str]] = ("root", "self", "parent", "collection") + _link_members: ClassVar[Tuple[str, ...]] = ("root", "self", "parent", "collection") def self(self) -> Link: """Create the `self` link.""" diff --git a/stac_pydantic/catalog.py b/stac_pydantic/catalog.py index 7ad8240..0d320a5 100644 --- a/stac_pydantic/catalog.py +++ b/stac_pydantic/catalog.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import AnyUrl, BaseModel, Field, constr +from pydantic import AnyUrl, BaseModel, Field from stac_pydantic.links import Links from stac_pydantic.version import STAC_VERSION @@ -11,13 +11,13 @@ class Catalog(BaseModel): https://github.com/radiantearth/stac-spec/blob/v1.0.0/catalog-spec/catalog-spec.md """ - id: constr(min_length=1) - description: constr(min_length=1) - stac_version: constr(min_length=1) = Field(STAC_VERSION, const=True) + id: str = Field(..., alias="", min_length=1) + description: str = Field(..., alias="description", min_length=1) + stac_version: str = Field(STAC_VERSION, const=True, min_length=1) links: Links stac_extensions: Optional[List[AnyUrl]] title: Optional[str] - type: constr(min_length=1) = Field("Catalog", const=True) + type: str = Field("Catalog", const=True, min_length=1) class Config: use_enum_values = True diff --git a/stac_pydantic/collection.py b/stac_pydantic/collection.py index b59321b..980f619 100644 --- a/stac_pydantic/collection.py +++ b/stac_pydantic/collection.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field, constr +from pydantic import BaseModel, Field from stac_pydantic.catalog import Catalog from stac_pydantic.shared import Asset, NumType, Provider @@ -46,10 +46,10 @@ class Collection(Catalog): """ assets: Optional[Dict[str, Asset]] - license: constr(min_length=1) + license: str = Field(..., alias="license", min_length=1) extent: Extent title: Optional[str] keywords: Optional[List[str]] providers: Optional[List[Provider]] summaries: Optional[Dict[str, Union[Range, List[Any], Dict[str, Any]]]] - type: constr(min_length=1) = Field("Collection", const=True) + type: str = Field("Collection", const=True, min_length=1) diff --git a/stac_pydantic/extensions.py b/stac_pydantic/extensions.py index f808b3c..ecdb6ba 100644 --- a/stac_pydantic/extensions.py +++ b/stac_pydantic/extensions.py @@ -1,4 +1,4 @@ -from functools import singledispatch +from typing import Union import jsonschema import requests @@ -6,36 +6,22 @@ from stac_pydantic import Catalog, Collection, Item -@singledispatch -def validate_extensions(stac_obj, b): - raise NotImplementedError("Unsupported type") +def validate_extensions( + stac_obj: Union[Item, Collection, Catalog, dict], reraise_exception: bool = False +) -> bool: + if isinstance(stac_obj, dict): + stac_dict = stac_obj + else: + stac_dict = stac_obj.dict() - -@validate_extensions.register -def _(stac_obj: dict, reraise_exception: bool = False, **kwargs) -> bool: try: - if stac_obj["stac_extensions"]: - for ext in stac_obj["stac_extensions"]: + if stac_dict["stac_extensions"]: + for ext in stac_dict["stac_extensions"]: req = requests.get(ext) schema = req.json() - jsonschema.validate(instance=stac_obj, schema=schema) + jsonschema.validate(instance=stac_dict, schema=schema) except Exception: if reraise_exception: raise return False return True - - -@validate_extensions.register -def _(stac_obj: Item, reraise_exception: bool = False, **kwargs) -> bool: - validate_extensions(stac_obj.dict(), reraise_exception) - - -@validate_extensions.register -def _(stac_obj: Collection, reraise_exception: bool = False, **kwargs) -> bool: - validate_extensions(stac_obj.dict(), reraise_exception) - - -@validate_extensions.register -def _(stac_obj: Catalog, reraise_exception: bool = False, **kwargs) -> bool: - validate_extensions(stac_obj.dict(), reraise_exception) diff --git a/stac_pydantic/item.py b/stac_pydantic/item.py index 658076f..a5b14f9 100644 --- a/stac_pydantic/item.py +++ b/stac_pydantic/item.py @@ -2,7 +2,7 @@ from typing import Dict, List, Optional, Union from geojson_pydantic.features import Feature, FeatureCollection -from pydantic import AnyUrl, Field, constr, root_validator, validator +from pydantic import AnyUrl, Field, root_validator, validator from pydantic.datetime_parse import parse_datetime from stac_pydantic.api.extensions.context import ContextExtension @@ -41,9 +41,9 @@ class Item(Feature): https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md """ - id: constr(min_length=1) - stac_version: constr(min_length=1) = Field(STAC_VERSION, const=True) - properties: ItemProperties + id: str = Field(..., alias="id", min_length=1) + stac_version: str = Field(STAC_VERSION, const=True, min_length=1) + properties: ItemProperties # type: ignore assets: Dict[str, Asset] links: Links stac_extensions: Optional[List[AnyUrl]] @@ -67,8 +67,8 @@ class ItemCollection(FeatureCollection): https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/itemcollection-spec.md """ - stac_version: constr(min_length=1) = Field(STAC_VERSION, const=True) - features: List[Item] + stac_version: str = Field(STAC_VERSION, const=True, min_length=1) + features: List[Item] # type: ignore stac_extensions: Optional[List[AnyUrl]] links: Links context: Optional[ContextExtension] diff --git a/stac_pydantic/links.py b/stac_pydantic/links.py index 7d52343..3d7b7b4 100644 --- a/stac_pydantic/links.py +++ b/stac_pydantic/links.py @@ -2,7 +2,7 @@ from typing import Any, Dict, Iterator, List, Optional, Union from urllib.parse import urljoin -from pydantic import BaseModel, Field, constr +from pydantic import BaseModel, Field from stac_pydantic.utils import AutoValueEnum @@ -30,8 +30,8 @@ class Link(BaseModel): https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#link-object """ - href: constr(min_length=1) - rel: constr(min_length=1) + href: str = Field(..., alias="href", min_length=1) + rel: str = Field(..., alias="rel", min_length=1) type: Optional[str] title: Optional[str] # Label extension @@ -59,18 +59,18 @@ class PaginationLink(Link): class Links(BaseModel): __root__: List[Union[PaginationLink, Link]] + def link_iterator(self) -> Iterator[Link]: + """Produce iterator to iterate through links""" + return iter(self.__root__) + def resolve(self, base_url: str): """resolve all links to the given base URL""" - for link in self: + for link in self.link_iterator(): link.resolve(base_url) def append(self, link: Link): self.__root__.append(link) - def __iter__(self) -> Iterator[Link]: - """iterate through links""" - return iter(self.__root__) - def __len__(self): return len(self.__root__) diff --git a/stac_pydantic/shared.py b/stac_pydantic/shared.py index 6630b73..a1f0b79 100644 --- a/stac_pydantic/shared.py +++ b/stac_pydantic/shared.py @@ -2,7 +2,7 @@ from enum import Enum, auto from typing import List, Optional, Tuple, Union -from pydantic import BaseModel, Extra, Field, confloat, constr +from pydantic import BaseModel, Extra, Field from stac_pydantic.utils import AutoValueEnum @@ -66,7 +66,7 @@ class Provider(BaseModel): https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#provider-object """ - name: constr(min_length=1) + name: str = Field(..., alias="name", min_length=1) description: Optional[str] roles: Optional[List[str]] url: Optional[str] @@ -88,7 +88,7 @@ class StacCommonMetadata(BaseModel): constellation: Optional[str] = Field(None, alias="constellation") mission: Optional[str] = Field(None, alias="mission") providers: Optional[List[Provider]] = Field(None, alias="providers") - gsd: Optional[confloat(gt=0)] = Field(None, alias="gsd") + gsd: Optional[float] = Field(None, alias="gsd", gt=0) class Config: json_encoders = {datetime: lambda v: v.strftime(DATETIME_RFC339)} @@ -99,7 +99,7 @@ class Asset(StacCommonMetadata): https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md#asset-object """ - href: constr(min_length=1) + href: str = Field(..., alias="href", min_length=1) type: Optional[str] title: Optional[str] description: Optional[str] diff --git a/tests/conftest.py b/tests/conftest.py index 2f1337e..912cece 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import os import arrow -import dictdiffer +import dictdiffer # type: ignore import pytest import requests from click.testing import CliRunner diff --git a/tests/test_api_extensions.py b/tests/test_api_extensions.py index 4a3e815..014633d 100644 --- a/tests/test_api_extensions.py +++ b/tests/test_api_extensions.py @@ -2,7 +2,7 @@ import pytest from pydantic import ValidationError -from shapely.geometry import Polygon, shape +from shapely.geometry import Polygon, shape # type: ignore from stac_pydantic import Item from stac_pydantic.api.extensions.fields import FieldsExtension diff --git a/tests/test_models.py b/tests/test_models.py index 899b091..56334fe 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,7 +4,7 @@ import pytest from pydantic import Field, ValidationError -from shapely.geometry import shape +from shapely.geometry import shape # type: ignore from stac_pydantic import Catalog, Collection, Item, ItemCollection, ItemProperties from stac_pydantic.api import Collections From 25da4c411d3bd313a24f42975a65bd77888c22df Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 28 Apr 2022 17:04:34 -0500 Subject: [PATCH 07/12] Fix formatting of pre-commit.yaml --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 419124e..9d9df2a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,9 @@ repos: - id: flake8 language_version: python3 - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 - hooks: - - id: mypy - exclude: ^tests/ - args: [--strict] \ No newline at end of file + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.931 + hooks: + - id: mypy + exclude: ^tests/ + args: [--strict] \ No newline at end of file From ef7ce875f87a81a235863d4283cce2c1b1aeb12e Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 28 Apr 2022 17:25:39 -0500 Subject: [PATCH 08/12] Include typehint dependencies for mypy --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d9df2a..3020473 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,4 +23,5 @@ repos: hooks: - id: mypy exclude: ^tests/ - args: [--strict] \ No newline at end of file + args: [--strict] + additional_dependencies: ["pydantic", "types-jsonschema", "types-requests"] \ No newline at end of file From cfd614001e52ceb8ad9aace78bc0a120e7bb7ea3 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Mon, 2 May 2022 14:17:52 -0500 Subject: [PATCH 09/12] Finish mypy fixes --- .pre-commit-config.yaml | 2 +- setup.py | 2 +- stac_pydantic/api/collections.py | 6 ++-- stac_pydantic/api/extensions/context.py | 4 +-- stac_pydantic/api/extensions/fields.py | 4 +-- stac_pydantic/api/search.py | 37 ++++++++++++++----------- stac_pydantic/catalog.py | 6 ++-- stac_pydantic/item.py | 34 +++++++++++------------ stac_pydantic/links.py | 10 +++---- stac_pydantic/utils.py | 5 +++- 10 files changed, 59 insertions(+), 51 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3020473..0775efe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,4 +24,4 @@ repos: - id: mypy exclude: ^tests/ args: [--strict] - additional_dependencies: ["pydantic", "types-jsonschema", "types-requests"] \ No newline at end of file + additional_dependencies: ["pydantic", "types-jsonschema", "types-setuptools", "types-requests"] \ No newline at end of file diff --git a/setup.py b/setup.py index eca5008..d100156 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ "dictdiffer", "jsonschema", "types-requests", - "types-jsonschema" + "types-jsonschema", ], } diff --git a/stac_pydantic/api/collections.py b/stac_pydantic/api/collections.py index d15feba..d0c270d 100644 --- a/stac_pydantic/api/collections.py +++ b/stac_pydantic/api/collections.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Any, Dict, List from pydantic import BaseModel @@ -14,8 +14,8 @@ class Collections(BaseModel): links: List[Link] collections: List[Collection] - def to_dict(self, **kwargs): + def to_dict(self, **kwargs: Any) -> Dict[str, Any]: return self.dict(by_alias=True, exclude_unset=True, **kwargs) - def to_json(self, **kwargs): + def to_json(self, **kwargs: Any) -> str: return self.json(by_alias=True, exclude_unset=True, **kwargs) diff --git a/stac_pydantic/api/extensions/context.py b/stac_pydantic/api/extensions/context.py index 44b46e8..bcda790 100644 --- a/stac_pydantic/api/extensions/context.py +++ b/stac_pydantic/api/extensions/context.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Dict, Optional from pydantic import BaseModel, validator @@ -13,7 +13,7 @@ class ContextExtension(BaseModel): matched: Optional[int] @validator("limit") - def validate_limit(cls, v, values): + def validate_limit(cls, v: Optional[int], values: Dict[str, Any]) -> None: if values["returned"] > v: raise ValueError( "Number of returned items must be less than or equal to the limit" diff --git a/stac_pydantic/api/extensions/fields.py b/stac_pydantic/api/extensions/fields.py index 493673d..7027252 100644 --- a/stac_pydantic/api/extensions/fields.py +++ b/stac_pydantic/api/extensions/fields.py @@ -11,7 +11,7 @@ class FieldsExtension(BaseModel): includes: Optional[Set[str]] excludes: Optional[Set[str]] - def _get_field_dict(self, fields: Set[str]) -> Dict: + def _get_field_dict(self, fields: Set[str]) -> Dict[str, Set[str]]: """Internal method to create a dictionary for advanced include or exclude of pydantic fields on model export Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude @@ -29,7 +29,7 @@ def _get_field_dict(self, fields: Set[str]) -> Dict: return field_dict @property - def filter(self) -> Dict: + def filter(self) -> Dict[str, Dict[str, Set[str]]]: """ Create dictionary of fields to include/exclude on model export based on the included and excluded fields passed to the API. The output of this property may be passed to pydantic's serialization methods to include or exclude diff --git a/stac_pydantic/api/search.py b/stac_pydantic/api/search.py index 19598b5..cbc791d 100644 --- a/stac_pydantic/api/search.py +++ b/stac_pydantic/api/search.py @@ -1,7 +1,7 @@ from datetime import datetime as dt from typing import Any, Dict, List, Optional, Tuple, Union, cast -from geojson_pydantic.geometries import ( +from geojson_pydantic.geometries import ( # type: ignore GeometryCollection, LineString, MultiLineString, @@ -19,6 +19,16 @@ from stac_pydantic.api.extensions.sort import SortExtension from stac_pydantic.shared import BBox +Intersection = Union[ + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon, + GeometryCollection, +] + class Search(BaseModel): """ @@ -30,17 +40,7 @@ class Search(BaseModel): collections: Optional[List[str]] ids: Optional[List[str]] bbox: Optional[BBox] - intersects: Optional[ - Union[ - Point, - MultiPoint, - LineString, - MultiLineString, - Polygon, - MultiPolygon, - GeometryCollection, - ] - ] + intersects: Optional[Intersection] datetime: Optional[str] limit: int = 10 @@ -63,13 +63,17 @@ def end_date(self) -> Optional[dt]: return parse_datetime(values[1]) @validator("intersects") - def validate_spatial(cls, v, values): + def validate_spatial( + cls, + v: Intersection, + values: Dict[str, Any], + ) -> Intersection: if v and values["bbox"]: raise ValueError("intersects and bbox parameters are mutually exclusive") return v @validator("bbox") - def validate_bbox(cls, v: BBox): + def validate_bbox(cls, v: BBox) -> BBox: if v: # Validate order if len(v) == 4: @@ -100,7 +104,7 @@ def validate_bbox(cls, v: BBox): return v @validator("datetime") - def validate_datetime(cls, v): + def validate_datetime(cls, v: str) -> str: if "/" in v: values = v.split("/") else: @@ -144,7 +148,8 @@ def spatial_filter(self) -> Optional[_GeometryBase]: ) if self.intersects: return self.intersects - return None + else: + return None class ExtendedSearch(Search): diff --git a/stac_pydantic/catalog.py b/stac_pydantic/catalog.py index 0d320a5..67eb0fc 100644 --- a/stac_pydantic/catalog.py +++ b/stac_pydantic/catalog.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Any, Dict, List, Optional from pydantic import AnyUrl, BaseModel, Field @@ -23,8 +23,8 @@ class Config: use_enum_values = True extra = "allow" - def to_dict(self, **kwargs): + def to_dict(self: "Catalog", **kwargs: Any) -> Dict[str, Any]: return self.dict(by_alias=True, exclude_unset=True, **kwargs) - def to_json(self, **kwargs): + def to_json(self: "Catalog", **kwargs: Any) -> str: return self.json(by_alias=True, exclude_unset=True, **kwargs) diff --git a/stac_pydantic/item.py b/stac_pydantic/item.py index a5b14f9..8852ea6 100644 --- a/stac_pydantic/item.py +++ b/stac_pydantic/item.py @@ -1,13 +1,13 @@ from datetime import datetime as dt -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union -from geojson_pydantic.features import Feature, FeatureCollection +from geojson_pydantic.features import Feature, FeatureCollection # type: ignore from pydantic import AnyUrl, Field, root_validator, validator from pydantic.datetime_parse import parse_datetime from stac_pydantic.api.extensions.context import ContextExtension from stac_pydantic.links import Links -from stac_pydantic.shared import DATETIME_RFC339, Asset, BBox, StacCommonMetadata +from stac_pydantic.shared import DATETIME_RFC339, Asset, StacCommonMetadata from stac_pydantic.version import STAC_VERSION @@ -19,7 +19,7 @@ class ItemProperties(StacCommonMetadata): datetime: Union[dt, str] = Field(..., alias="datetime") @validator("datetime") - def validate_datetime(cls, v, values): + def validate_datetime(cls, v: Union[dt, str], values: Dict[str, Any]) -> dt: if v == "null": if not values["start_datetime"] and not values["end_datetime"]: raise ValueError( @@ -36,45 +36,45 @@ class Config: json_encoders = {dt: lambda v: v.strftime(DATETIME_RFC339)} -class Item(Feature): +class Item(Feature): # type: ignore """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md """ id: str = Field(..., alias="id", min_length=1) stac_version: str = Field(STAC_VERSION, const=True, min_length=1) - properties: ItemProperties # type: ignore + properties: ItemProperties assets: Dict[str, Asset] links: Links stac_extensions: Optional[List[AnyUrl]] collection: Optional[str] - def to_dict(self, **kwargs): - return self.dict(by_alias=True, exclude_unset=True, **kwargs) + def to_dict(self, **kwargs: Any) -> Dict[str, Any]: + return self.dict(by_alias=True, exclude_unset=True, **kwargs) # type: ignore - def to_json(self, **kwargs): - return self.json(by_alias=True, exclude_unset=True, **kwargs) + def to_json(self, **kwargs: Any) -> str: + return self.json(by_alias=True, exclude_unset=True, **kwargs) # type: ignore @root_validator(pre=True) - def validate_bbox(cls, values): + def validate_bbox(cls, values: Dict[str, Any]) -> Dict[str, Any]: if values.get("geometry") and values.get("bbox") is None: raise ValueError("bbox is required if geometry is not null") return values -class ItemCollection(FeatureCollection): +class ItemCollection(FeatureCollection): # type: ignore """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/itemcollection-spec.md """ stac_version: str = Field(STAC_VERSION, const=True, min_length=1) - features: List[Item] # type: ignore + features: List[Item] stac_extensions: Optional[List[AnyUrl]] links: Links context: Optional[ContextExtension] - def to_dict(self, **kwargs): - return self.dict(by_alias=True, exclude_unset=True, **kwargs) + def to_dict(self, **kwargs: Any) -> Dict[str, Any]: + return self.dict(by_alias=True, exclude_unset=True, **kwargs) # type: ignore - def to_json(self, **kwargs): - return self.json(by_alias=True, exclude_unset=True, **kwargs) + def to_json(self, **kwargs: Any) -> str: + return self.json(by_alias=True, exclude_unset=True, **kwargs) # type: ignore diff --git a/stac_pydantic/links.py b/stac_pydantic/links.py index 3d7b7b4..f847141 100644 --- a/stac_pydantic/links.py +++ b/stac_pydantic/links.py @@ -40,7 +40,7 @@ class Link(BaseModel): class Config: use_enum_values = True - def resolve(self, base_url: str): + def resolve(self, base_url: str) -> None: """resolve a link to the given base URL""" self.href = urljoin(base_url, self.href) @@ -63,18 +63,18 @@ def link_iterator(self) -> Iterator[Link]: """Produce iterator to iterate through links""" return iter(self.__root__) - def resolve(self, base_url: str): + def resolve(self, base_url: str) -> None: """resolve all links to the given base URL""" for link in self.link_iterator(): link.resolve(base_url) - def append(self, link: Link): + def append(self, link: Link) -> None: self.__root__.append(link) - def __len__(self): + def __len__(self) -> int: return len(self.__root__) - def __getitem__(self, idx): + def __getitem__(self, idx: int) -> Union[PaginationLink, Link]: return self.__root__[idx] diff --git a/stac_pydantic/utils.py b/stac_pydantic/utils.py index 9f6b41f..1bdca60 100644 --- a/stac_pydantic/utils.py +++ b/stac_pydantic/utils.py @@ -1,6 +1,9 @@ from enum import Enum +from typing import Any, List class AutoValueEnum(Enum): - def _generate_next_value_(name, start, count, last_values): + def _generate_next_value_( # type: ignore + name: str, start: int, count: int, last_values: List[Any] + ) -> Any: return name From 025b8c034300c98c1fce07b0a5e0be813828d642 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Mon, 2 May 2022 15:59:18 -0500 Subject: [PATCH 10/12] Fix validation of searches --- stac_pydantic/api/search.py | 18 ++++-------------- tests/test_link_factory.py | 4 ++-- tests/test_models.py | 4 ++-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/stac_pydantic/api/search.py b/stac_pydantic/api/search.py index cbc791d..cccd046 100644 --- a/stac_pydantic/api/search.py +++ b/stac_pydantic/api/search.py @@ -68,7 +68,7 @@ def validate_spatial( v: Intersection, values: Dict[str, Any], ) -> Intersection: - if v and values["bbox"]: + if v and values["bbox"] is not None: raise ValueError("intersects and bbox parameters are mutually exclusive") return v @@ -77,10 +77,10 @@ def validate_bbox(cls, v: BBox) -> BBox: if v: # Validate order if len(v) == 4: - xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], (*v, 4)) + xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], v) else: xmin, ymin, min_elev, xmax, ymax, max_elev = cast( - Tuple[int, int, int, int, int, int], (*v, 6) + Tuple[int, int, int, int, int, int], v ) if max_elev < min_elev: raise ValueError( @@ -135,17 +135,7 @@ def spatial_filter(self) -> Optional[_GeometryBase]: Check for both because the ``bbox`` and ``intersects`` parameters are mutually exclusive. """ if self.bbox: - return Polygon( - coordinates=[ - [ - [self.bbox[0], self.bbox[3]], - [self.bbox[2], self.bbox[3]], - [self.bbox[2], self.bbox[1]], - [self.bbox[0], self.bbox[1]], - [self.bbox[0], self.bbox[3]], - ] - ] - ) + return Polygon.from_bounds(*self.bbox) if self.intersects: return self.intersects else: diff --git a/tests/test_link_factory.py b/tests/test_link_factory.py index 97e8e77..a1865e4 100644 --- a/tests/test_link_factory.py +++ b/tests/test_link_factory.py @@ -9,7 +9,7 @@ def test_collection_links(): links = CollectionLinks( collection_id="collection", base_url="http://stac.com" ).create_links() - for link in links: + for link in links.link_iterator(): assert isinstance(link, Link) assert link.rel in CollectionLinks._link_members @@ -18,7 +18,7 @@ def test_item_links(): links = ItemLinks( collection_id="collection", item_id="item", base_url="http://stac.com" ).create_links() - for link in links: + for link in links.link_iterator(): assert isinstance(link, Link) assert link.rel in ItemLinks._link_members diff --git a/tests/test_models.py b/tests/test_models.py index 56334fe..8c27fc6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -528,7 +528,7 @@ def test_resolve_link(): def test_resolve_links(): links = Links.parse_obj([Link(href="/hello/world", type="image/jpeg", rel="test")]) links.resolve(base_url="http://base_url.com") - for link in links: + for link in links.link_iterator(): assert link.href == "http://base_url.com/hello/world" @@ -539,7 +539,7 @@ def test_resolve_pagination_link(): ) links = Links.parse_obj([normal_link, page_link]) links.resolve(base_url="http://base_url.com") - for link in links: + for link in links.link_iterator(): if isinstance(link, PaginationLink): assert link.href == "http://base_url.com/next/page" From 977cc90700b36ce409b7dd87c93468fb2d8c929c Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Mon, 2 May 2022 16:14:18 -0500 Subject: [PATCH 11/12] Deal with py37 mypy issues --- stac_pydantic/scripts/cli.py | 4 ++-- tox.ini | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/stac_pydantic/scripts/cli.py b/stac_pydantic/scripts/cli.py index 8509f6c..a850f4f 100644 --- a/stac_pydantic/scripts/cli.py +++ b/stac_pydantic/scripts/cli.py @@ -7,14 +7,14 @@ @click.group(short_help="Validate STAC") -def app(): +def app() -> None: """stac-pydantic cli group""" pass @app.command(short_help="Validate STAC Item") @click.argument("infile") -def validate_item(infile): +def validate_item(infile: str) -> None: """Validate stac item""" r = requests.get(infile) r.raise_for_status() diff --git a/tox.ini b/tox.ini index 3439ec2..2ff6de5 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,8 @@ extend-ignore = E203, E501 [mypy] no_strict_optional = True ignore_missing_imports = True +implicit_reexport = True +deps = types-click [tool:isort] profile = black From 7e62410489041a60227b018b382977eaf856e228 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Mon, 2 May 2022 16:31:06 -0500 Subject: [PATCH 12/12] Update tox and cicd --- .github/workflows/cicd.yml | 5 ----- tox.ini | 25 ++++++++++--------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 4194ea0..af44edd 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -29,11 +29,6 @@ jobs: python -m pip install --upgrade pip python -m pip install tox codecov pre-commit - # Run pre-commit (only for python-3.7) - - name: run pre-commit - if: matrix.python-version == 3.7 - run: pre-commit run --all-files - # Run tox using the version of Python in `PATH` - name: Run Tox run: tox -e py diff --git a/tox.ini b/tox.ini index 2ff6de5..f0bb71d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,29 +2,24 @@ envlist = py37,py38,py39,py310 [testenv] -extras = - dev -deps = - flake8 - black -; mypy -commands = - python -m pytest --cov stac_pydantic --cov-report xml --cov-report term-missing - flake8 . - black . -; mypy stac_pydantic/ +extras = dev +commands = python -m pytest --cov stac_pydantic --cov-report xml --cov-report term-missing + +[black] +deps = black +commands = black . [flake8] +deps = flake8 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.venv,.tox,.idea max-line-length = 88 select = C,E,F,W,B,B950 extend-ignore = E203, E501 +commands = flake8 . [mypy] -no_strict_optional = True -ignore_missing_imports = True -implicit_reexport = True -deps = types-click +deps = mypy, types-click +commands = mypy --no_strict_optional --ignore_missing_imports --implicit_reexport stac_pydantic/ [tool:isort] profile = black