Skip to content

Commit

Permalink
Merge pull request #109 from philvarner/pv/build-improvements
Browse files Browse the repository at this point in the history
build improvements
  • Loading branch information
moradology authored May 3, 2022
2 parents 4cd7376 + 7e62410 commit 86f7303
Show file tree
Hide file tree
Showing 27 changed files with 210 additions and 185 deletions.
7 changes: 1 addition & 6 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
17 changes: 13 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@ 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]
additional_dependencies: ["pydantic", "types-jsonschema", "types-setuptools", "types-requests"]
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"shapely",
"dictdiffer",
"jsonschema",
"types-requests",
"types-jsonschema",
],
}

Expand All @@ -28,6 +30,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",
Expand Down
1 change: 1 addition & 0 deletions stac_pydantic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa: F401
from .catalog import Catalog
from .collection import Collection
from .item import Item, ItemCollection, ItemProperties
1 change: 1 addition & 0 deletions stac_pydantic/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa: F401
from .collections import Collections
from .conformance import ConformanceClasses
from .landing import LandingPage
Expand Down
6 changes: 3 additions & 3 deletions stac_pydantic/api/collections.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import Any, Dict, List

from pydantic import BaseModel

Expand All @@ -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)
4 changes: 2 additions & 2 deletions stac_pydantic/api/extensions/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Any, Dict, Optional

from pydantic import BaseModel, validator

Expand All @@ -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"
Expand Down
8 changes: 4 additions & 4 deletions stac_pydantic/api/extensions/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,15 +29,15 @@ 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
certain keys.
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)
Expand All @@ -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()),
}
4 changes: 2 additions & 2 deletions stac_pydantic/api/extensions/sort.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import auto

from pydantic import BaseModel, constr
from pydantic import BaseModel, Field

from stac_pydantic.utils import AutoValueEnum

Expand All @@ -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
10 changes: 5 additions & 5 deletions stac_pydantic/api/landing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional, Union
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
Expand All @@ -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)
69 changes: 33 additions & 36 deletions stac_pydantic/api/search.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 (
from geojson_pydantic.geometries import ( # type: ignore
GeometryCollection,
LineString,
MultiLineString,
Expand All @@ -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):
"""
Expand All @@ -30,52 +40,48 @@ 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

@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] == "":
return None
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] == "":
return None
return parse_datetime(values[1])

@validator("intersects")
def validate_spatial(cls, v, values):
if v and values["bbox"]:
def validate_spatial(
cls,
v: Intersection,
values: Dict[str, Any],
) -> Intersection:
if v and values["bbox"] is not None:
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:
xmin, ymin, xmax, ymax = v
xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], v)
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
)
if max_elev < min_elev:
raise ValueError(
"Maximum elevation must greater than minimum elevation"
Expand All @@ -98,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:
Expand Down Expand Up @@ -129,20 +135,11 @@ 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
return
else:
return None


class ExtendedSearch(Search):
Expand Down
Loading

0 comments on commit 86f7303

Please sign in to comment.