Skip to content

Commit 86f7303

Browse files
authored
Merge pull request #109 from philvarner/pv/build-improvements
build improvements
2 parents 4cd7376 + 7e62410 commit 86f7303

27 files changed

+210
-185
lines changed

.github/workflows/cicd.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-latest
1515
strategy:
1616
matrix:
17-
python-version: [3.7, 3.8]
17+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1818

1919
steps:
2020
- uses: actions/checkout@v2
@@ -29,11 +29,6 @@ jobs:
2929
python -m pip install --upgrade pip
3030
python -m pip install tox codecov pre-commit
3131
32-
# Run pre-commit (only for python-3.7)
33-
- name: run pre-commit
34-
if: matrix.python-version == 3.7
35-
run: pre-commit run --all-files
36-
3732
# Run tox using the version of Python in `PATH`
3833
- name: Run Tox
3934
run: tox -e py

.pre-commit-config.yaml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ repos:
77
args: ["--safe"]
88

99
- repo: https://github.com/PyCQA/isort
10-
rev: 5.4.2
10+
rev: 5.10.1
1111
hooks:
1212
- id: isort
1313
language_version: python3
14-
- repo: https://github.com/PyCQA/isort
15-
rev: 5.4.2
14+
15+
- repo: https://github.com/PyCQA/flake8
16+
rev: 4.0.1
1617
hooks:
17-
- id: isort
18+
- id: flake8
1819
language_version: python3
20+
21+
- repo: https://github.com/pre-commit/mirrors-mypy
22+
rev: v0.931
23+
hooks:
24+
- id: mypy
25+
exclude: ^tests/
26+
args: [--strict]
27+
additional_dependencies: ["pydantic", "types-jsonschema", "types-setuptools", "types-requests"]

README.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,57 @@ For local development:
1111
pip install -e .["dev"]
1212
```
1313

14-
| stac-pydantic | stac |
15-
|-------------------|--------------|
16-
| 1.1.x | 0.9.0 |
17-
| 1.2.x | 1.0.0-beta.1 |
18-
| 1.3.x | 1.0.0-beta.2 |
19-
| 2.0.x | 1.0.0 |
14+
| stac-pydantic | STAC Version |
15+
|---------------|--------------|
16+
| 1.1.x | 0.9.0 |
17+
| 1.2.x | 1.0.0-beta.1 |
18+
| 1.3.x | 1.0.0-beta.2 |
19+
| 2.0.x | 1.0.0 |
20+
21+
## Development
22+
23+
Install the [pre-commit](https://pre-commit.com/) hooks:
24+
25+
```shell
26+
pre-commit install
27+
```
2028

2129
## Testing
22-
Run the entire test suite:
30+
31+
Ensure you have all Python versions installed that the tests will be run against. If using pyenv, run:
32+
33+
```shell
34+
pyenv install 3.7.12
35+
pyenv install 3.8.12
36+
pyenv install 3.9.10
37+
pyenv install 3.10.2
38+
pyenv local 3.7.12 3.8.12 3.9.10 3.10.2
2339
```
40+
41+
Run the entire test suite:
42+
43+
```shell
2444
tox
2545
```
2646

2747
Run a single test case using the standard pytest convention:
28-
```
48+
49+
```shell
2950
pytest -v tests/test_models.py::test_item_extensions
3051
```
3152

3253
## Usage
54+
3355
### Loading Models
56+
3457
Load data into models with standard pydantic:
58+
3559
```python
3660
from stac_pydantic import Catalog
3761

3862
stac_catalog = {
39-
"stac_version": "0.9.0",
63+
"type": "Catalog",
64+
"stac_version": "1.0.0",
4065
"id": "sample",
4166
"description": "This is a very basic sample catalog.",
4267
"links": [

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ test=pytest
33

44
[tool:pytest]
55
addopts=-sv --cov=stac_pydantic --cov-fail-under=95 --cov-report=term-missing
6+
7+
[mypy]
8+
plugins = pydantic.mypy

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"shapely",
1313
"dictdiffer",
1414
"jsonschema",
15+
"types-requests",
16+
"types-jsonschema",
1517
],
1618
}
1719

@@ -28,6 +30,8 @@
2830
"Intended Audience :: Science/Research",
2931
"Programming Language :: Python :: 3.7",
3032
"Programming Language :: Python :: 3.8",
33+
"Programming Language :: Python :: 3.9",
34+
"Programming Language :: Python :: 3.10",
3135
"License :: OSI Approved :: MIT License",
3236
],
3337
keywords="stac pydantic validation",

stac_pydantic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# flake8: noqa: F401
12
from .catalog import Catalog
23
from .collection import Collection
34
from .item import Item, ItemCollection, ItemProperties

stac_pydantic/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# flake8: noqa: F401
12
from .collections import Collections
23
from .conformance import ConformanceClasses
34
from .landing import LandingPage

stac_pydantic/api/collections.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import Any, Dict, List
22

33
from pydantic import BaseModel
44

@@ -14,8 +14,8 @@ class Collections(BaseModel):
1414
links: List[Link]
1515
collections: List[Collection]
1616

17-
def to_dict(self, **kwargs):
17+
def to_dict(self, **kwargs: Any) -> Dict[str, Any]:
1818
return self.dict(by_alias=True, exclude_unset=True, **kwargs)
1919

20-
def to_json(self, **kwargs):
20+
def to_json(self, **kwargs: Any) -> str:
2121
return self.json(by_alias=True, exclude_unset=True, **kwargs)

stac_pydantic/api/extensions/context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional
1+
from typing import Any, Dict, Optional
22

33
from pydantic import BaseModel, validator
44

@@ -13,7 +13,7 @@ class ContextExtension(BaseModel):
1313
matched: Optional[int]
1414

1515
@validator("limit")
16-
def validate_limit(cls, v, values):
16+
def validate_limit(cls, v: Optional[int], values: Dict[str, Any]) -> None:
1717
if values["returned"] > v:
1818
raise ValueError(
1919
"Number of returned items must be less than or equal to the limit"

stac_pydantic/api/extensions/fields.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class FieldsExtension(BaseModel):
1111
includes: Optional[Set[str]]
1212
excludes: Optional[Set[str]]
1313

14-
def _get_field_dict(self, fields: Set[str]) -> Dict:
14+
def _get_field_dict(self, fields: Set[str]) -> Dict[str, Set[str]]:
1515
"""Internal method to create a dictionary for advanced include or exclude of pydantic fields on model export
1616
1717
Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude
@@ -29,15 +29,15 @@ def _get_field_dict(self, fields: Set[str]) -> Dict:
2929
return field_dict
3030

3131
@property
32-
def filter(self) -> Dict:
32+
def filter(self) -> Dict[str, Dict[str, Set[str]]]:
3333
"""
3434
Create dictionary of fields to include/exclude on model export based on the included and excluded fields passed
3535
to the API. The output of this property may be passed to pydantic's serialization methods to include or exclude
3636
certain keys.
3737
3838
Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude
3939
"""
40-
include = set()
40+
include: Set[str] = set()
4141
# If only include is specified, add fields to the set
4242
if self.includes and not self.excludes:
4343
include = include.union(self.includes)
@@ -46,5 +46,5 @@ def filter(self) -> Dict:
4646
include = include.union(self.includes) - self.excludes
4747
return {
4848
"include": self._get_field_dict(include),
49-
"exclude": self._get_field_dict(self.excludes),
49+
"exclude": self._get_field_dict(self.excludes or Set()),
5050
}

stac_pydantic/api/extensions/sort.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from enum import auto
22

3-
from pydantic import BaseModel, constr
3+
from pydantic import BaseModel, Field
44

55
from stac_pydantic.utils import AutoValueEnum
66

@@ -15,5 +15,5 @@ class SortExtension(BaseModel):
1515
https://github.com/radiantearth/stac-api-spec/tree/master/extensions/sort#sort-api-extension
1616
"""
1717

18-
field: constr(min_length=1)
18+
field: str = Field(..., alias="field", min_length=1)
1919
direction: SortDirections

stac_pydantic/api/landing.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import List, Optional, Union
1+
from typing import List, Optional
22

3-
from pydantic import AnyUrl, BaseModel, Field, constr
3+
from pydantic import AnyUrl, BaseModel, Field
44

55
from stac_pydantic.links import Links
66
from stac_pydantic.version import STAC_VERSION
@@ -11,11 +11,11 @@ class LandingPage(BaseModel):
1111
https://github.com/radiantearth/stac-api-spec/blob/master/api-spec.md#ogc-api---features-endpoints
1212
"""
1313

14-
id: constr(min_length=1)
15-
description: constr(min_length=1)
14+
id: str = Field(..., alias="id", min_length=1)
15+
description: str = Field(..., alias="description", min_length=1)
1616
title: Optional[str]
1717
stac_version: str = Field(STAC_VERSION, const=True)
1818
stac_extensions: Optional[List[AnyUrl]]
1919
conformsTo: List[AnyUrl]
2020
links: Links
21-
type: constr(min_length=1) = Field("Catalog", const=True)
21+
type: str = Field("Catalog", const=True, min_length=1)

stac_pydantic/api/search.py

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from datetime import datetime
2-
from typing import Any, Dict, List, Optional, Union
1+
from datetime import datetime as dt
2+
from typing import Any, Dict, List, Optional, Tuple, Union, cast
33

4-
from geojson_pydantic.geometries import (
4+
from geojson_pydantic.geometries import ( # type: ignore
55
GeometryCollection,
66
LineString,
77
MultiLineString,
@@ -19,6 +19,16 @@
1919
from stac_pydantic.api.extensions.sort import SortExtension
2020
from stac_pydantic.shared import BBox
2121

22+
Intersection = Union[
23+
Point,
24+
MultiPoint,
25+
LineString,
26+
MultiLineString,
27+
Polygon,
28+
MultiPolygon,
29+
GeometryCollection,
30+
]
31+
2232

2333
class Search(BaseModel):
2434
"""
@@ -30,52 +40,48 @@ class Search(BaseModel):
3040
collections: Optional[List[str]]
3141
ids: Optional[List[str]]
3242
bbox: Optional[BBox]
33-
intersects: Optional[
34-
Union[
35-
Point,
36-
MultiPoint,
37-
LineString,
38-
MultiLineString,
39-
Polygon,
40-
MultiPolygon,
41-
GeometryCollection,
42-
]
43-
]
43+
intersects: Optional[Intersection]
4444
datetime: Optional[str]
4545
limit: int = 10
4646

4747
@property
48-
def start_date(self) -> Optional[datetime]:
49-
values = self.datetime.split("/")
48+
def start_date(self) -> Optional[dt]:
49+
values = (self.datetime or "").split("/")
5050
if len(values) == 1:
5151
return None
5252
if values[0] == ".." or values[0] == "":
5353
return None
5454
return parse_datetime(values[0])
5555

5656
@property
57-
def end_date(self) -> Optional[datetime]:
58-
values = self.datetime.split("/")
57+
def end_date(self) -> Optional[dt]:
58+
values = (self.datetime or "").split("/")
5959
if len(values) == 1:
6060
return parse_datetime(values[0])
6161
if values[1] == ".." or values[1] == "":
6262
return None
6363
return parse_datetime(values[1])
6464

6565
@validator("intersects")
66-
def validate_spatial(cls, v, values):
67-
if v and values["bbox"]:
66+
def validate_spatial(
67+
cls,
68+
v: Intersection,
69+
values: Dict[str, Any],
70+
) -> Intersection:
71+
if v and values["bbox"] is not None:
6872
raise ValueError("intersects and bbox parameters are mutually exclusive")
6973
return v
7074

7175
@validator("bbox")
72-
def validate_bbox(cls, v: BBox):
76+
def validate_bbox(cls, v: BBox) -> BBox:
7377
if v:
7478
# Validate order
7579
if len(v) == 4:
76-
xmin, ymin, xmax, ymax = v
80+
xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], v)
7781
else:
78-
xmin, ymin, min_elev, xmax, ymax, max_elev = v
82+
xmin, ymin, min_elev, xmax, ymax, max_elev = cast(
83+
Tuple[int, int, int, int, int, int], v
84+
)
7985
if max_elev < min_elev:
8086
raise ValueError(
8187
"Maximum elevation must greater than minimum elevation"
@@ -98,7 +104,7 @@ def validate_bbox(cls, v: BBox):
98104
return v
99105

100106
@validator("datetime")
101-
def validate_datetime(cls, v):
107+
def validate_datetime(cls, v: str) -> str:
102108
if "/" in v:
103109
values = v.split("/")
104110
else:
@@ -129,20 +135,11 @@ def spatial_filter(self) -> Optional[_GeometryBase]:
129135
Check for both because the ``bbox`` and ``intersects`` parameters are mutually exclusive.
130136
"""
131137
if self.bbox:
132-
return Polygon(
133-
coordinates=[
134-
[
135-
[self.bbox[0], self.bbox[3]],
136-
[self.bbox[2], self.bbox[3]],
137-
[self.bbox[2], self.bbox[1]],
138-
[self.bbox[0], self.bbox[1]],
139-
[self.bbox[0], self.bbox[3]],
140-
]
141-
]
142-
)
138+
return Polygon.from_bounds(*self.bbox)
143139
if self.intersects:
144140
return self.intersects
145-
return
141+
else:
142+
return None
146143

147144

148145
class ExtendedSearch(Search):

0 commit comments

Comments
 (0)