Skip to content

Commit 18a1982

Browse files
committed
prepare for 4.0
1 parent 2a72400 commit 18a1982

File tree

9 files changed

+204
-42
lines changed

9 files changed

+204
-42
lines changed

.github/workflows/cicd.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
13+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1414
timeout-minutes: 20
1515

1616
steps:

CHANGES.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [4.0.0] - 2025-01-17
44

55
### Changed
66

77
* use `string` type instead of python `datetime.datetime` for datetime parameter in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` GET models
88
* rename `filter` to `filter_expr` for `FilterExtensionGetRequest` and `FilterExtensionPostRequest` attributes to avoid conflict with python filter method
9+
* remove deprecated `post_request_model` attribute in `BaseCoreClient` and `AsyncBaseCoreClient`
10+
* remove `python3.8` support
911

1012
### Fixed
1113

@@ -524,7 +526,8 @@ Full changelog: https://stac-utils.github.io/stac-fastapi/migrations/v3.0.0/#cha
524526

525527
* First PyPi release!
526528

527-
[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.5..main>
529+
[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/4.0.0..main>
530+
[4.0.0]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.5..4.0.0>
528531
[3.0.5]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.4..3.0.5>
529532
[3.0.4]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.3..3.0.4>
530533
[3.0.3]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.2..3.0.3>

docs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ nav:
8080
- version: api/stac_fastapi/types/version.md
8181
- Migration Guides:
8282
- v2.5 -> v3.0: migrations/v3.0.0.md
83+
- v3.0 -> v4.0: migrations/v4.0.0.md
8384
- Performance Benchmarks: benchmarks.html
8485
- Development - Contributing: "contributing.md"
8586
- Release Notes: "release-notes.md"

docs/src/migrations/v4.0.0.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
2+
# stac-fastapi v4.0 Migration Guide
3+
4+
This document aims to help you update your application from **stac-fastapi** 3.0 to 4.0
5+
6+
## CHANGELOG
7+
### Changed
8+
9+
* use `string` type instead of python `datetime.datetime` for datetime parameter in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` GET models
10+
* rename `filter` to `filter_expr` for `FilterExtensionGetRequest` and `FilterExtensionPostRequest` attributes to avoid conflict with python filter method
11+
* remove `post_request_model` attribute in `BaseCoreClient` and `AsyncBaseCoreClient`
12+
* remove `python3.8` support
13+
14+
### Fixed
15+
16+
* Support multiple proxy servers in the `forwarded` header in `ProxyHeaderMiddleware` ([#782](https://github.com/stac-utils/stac-fastapi/pull/782))
17+
18+
## Datetime type in GET request models
19+
20+
While the POST request models are created using stac-pydantic, the GET request models are python `attrs` classes (~dataclasses).
21+
In 4.0, we've decided to change how the `datetime` attribute was defined in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` models to match
22+
the `datetime` definition/validation done by the pydantic model. This mostly mean that the datetime attribute forwarded to the GET endpoints will now be of type string (forwarded from the user input).
23+
24+
```python
25+
from starlette.testclient import TestClient
26+
from stac_fastapi.api.app import StacApi
27+
from stac_fastapi.types.config import ApiSettings
28+
from stac_fastapi.types.core import BaseCoreClient
29+
30+
class DummyCoreClient(BaseCoreClient):
31+
def all_collections(self, *args, **kwargs):
32+
raise NotImplementedError
33+
34+
def get_collection(self, *args, **kwargs):
35+
raise NotImplementedError
36+
37+
def get_item(self, *args, **kwargs):
38+
raise NotImplementedError
39+
40+
def get_search(self, *args, datetime = None, **kwargs):
41+
# Return True if datetime is a string
42+
return isinstance(datetime, str)
43+
44+
def post_search(self, *args, **kwargs):
45+
raise NotImplementedError
46+
47+
def item_collection(self, *args, **kwargs):
48+
raise NotImplementedError
49+
50+
api = StacApi(
51+
settings=ApiSettings(enable_response_models=False),
52+
client=DummyCoreClient(),
53+
extensions=[],
54+
)
55+
56+
57+
# before
58+
with TestClient(api.app) as client:
59+
response = client.get(
60+
"/search",
61+
params={
62+
"datetime": "2020-01-01T00:00:00.00001Z",
63+
},
64+
)
65+
assert response.json() == False
66+
67+
# now
68+
with TestClient(api.app) as client:
69+
response = client.get(
70+
"/search",
71+
params={
72+
"datetime": "2020-01-01T00:00:00.00001Z",
73+
},
74+
)
75+
assert response.json() == True
76+
```
77+
78+
#### Start/End dates
79+
80+
Following stac-pydantic's `Search` model, we've added class attributes to easily retrieve the `parsed` dates:
81+
82+
```python
83+
from stac_fastapi.types.search import BaseSearchGetRequest
84+
85+
# Interval
86+
search = BaseSearchGetRequest(datetime="2020-01-01T00:00:00.00001Z/2020-01-02T00:00:00.00001Z")
87+
88+
search.parse_datetime()
89+
>>> (datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc), datetime.datetime(2020, 1, 2, 0, 0, 0, 10, tzinfo=datetime.timezone.utc))
90+
91+
search.start_date
92+
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)
93+
94+
search.end_date
95+
>>> datetime.datetime(2020, 1, 2, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)
96+
97+
# Single date
98+
search = BaseSearchGetRequest(datetime="2020-01-01T00:00:00.00001Z")
99+
100+
search.parse_datetime()
101+
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)
102+
103+
search.start_date
104+
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)
105+
106+
search.end_date
107+
>>> None
108+
```
109+
110+
## Filter extension
111+
112+
We've renamed the `filter` attribute to `filter_expr` in the `FilterExtensionGetRequest` and `FilterExtensionPostRequest` models to avoid any conflict with python `filter` method. This change means GET endpoints with the filter extension enabled will receive `filter_expr=` option instead of `filter=`. Same for POST endpoints where the `body` will now have a `.filter_expr` instead of a `filter` attribute.
113+
114+
Note: This change does not affect the `input` because we use `aliases`.
115+
116+
```python
117+
from starlette.testclient import TestClient
118+
from stac_fastapi.api.app import StacApi
119+
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
120+
from stac_fastapi.extensions.core import FilterExtension
121+
from stac_fastapi.types.config import ApiSettings
122+
from stac_fastapi.types.core import BaseCoreClient
123+
124+
class DummyCoreClient(BaseCoreClient):
125+
def all_collections(self, *args, **kwargs):
126+
raise NotImplementedError
127+
128+
def get_collection(self, *args, **kwargs):
129+
raise NotImplementedError
130+
131+
def get_item(self, *args, **kwargs):
132+
raise NotImplementedError
133+
134+
def get_search(self, *args, **kwargs):
135+
return kwargs
136+
137+
def post_search(self, *args, **kwargs):
138+
return args[0].model_dump()
139+
140+
def item_collection(self, *args, **kwargs):
141+
raise NotImplementedError
142+
143+
extensions = [FilterExtension()]
144+
api = StacApi(
145+
settings=ApiSettings(enable_response_models=False),
146+
client=DummyCoreClient(),
147+
extensions=extensions,
148+
search_get_request_model=create_get_request_model(extensions),
149+
search_post_request_model=create_post_request_model(extensions),
150+
)
151+
152+
153+
# before
154+
with TestClient(api.app) as client:
155+
response = client.post(
156+
"/search",
157+
json={
158+
"filter": {"op": "=", "args": [{"property": "test_property"}, "test-value"]},
159+
},
160+
)
161+
assert response.json()["filter"]
162+
163+
response = client.get(
164+
"/search",
165+
params={
166+
"filter": "id='item_id' AND collection='collection_id'",
167+
},
168+
)
169+
assert response.json()["filter"]
170+
171+
# now
172+
with TestClient(api.app) as client:
173+
response = client.post(
174+
"/search",
175+
json={
176+
"filter": {"op": "=", "args": [{"property": "test_property"}, "test-value"]},
177+
},
178+
)
179+
assert response.json()["filter_expr"]
180+
181+
response = client.get(
182+
"/search",
183+
params={
184+
"filter": "id='item_id' AND collection='collection_id'",
185+
},
186+
)
187+
assert response.json()["filter_expr"]
188+
```
189+
190+

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tool.ruff]
2-
target-version = "py38" # minimum supported version
2+
target-version = "py39" # minimum supported version
33
line-length = 90
44

55
[tool.ruff.lint]

stac_fastapi/api/setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
install_requires = [
99
"brotli_asgi",
10-
"stac-fastapi.types~=3.0",
10+
"stac-fastapi.types~=4.0",
1111
]
1212

1313
extra_reqs = {
@@ -31,12 +31,11 @@
3131
description="An implementation of STAC API based on the FastAPI framework.",
3232
long_description=desc,
3333
long_description_content_type="text/markdown",
34-
python_requires=">=3.8",
34+
python_requires=">=3.9",
3535
classifiers=[
3636
"Intended Audience :: Developers",
3737
"Intended Audience :: Information Technology",
3838
"Intended Audience :: Science/Research",
39-
"Programming Language :: Python :: 3.8",
4039
"Programming Language :: Python :: 3.9",
4140
"Programming Language :: Python :: 3.10",
4241
"Programming Language :: Python :: 3.11",

stac_fastapi/extensions/setup.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
desc = f.read()
88

99
install_requires = [
10-
"stac-fastapi.types~=3.0",
11-
"stac-fastapi.api~=3.0",
10+
"stac-fastapi.types~=4.0",
11+
"stac-fastapi.api~=4.0",
1212
]
1313

1414
extra_reqs = {
@@ -28,12 +28,11 @@
2828
description="An implementation of STAC API based on the FastAPI framework.",
2929
long_description=desc,
3030
long_description_content_type="text/markdown",
31-
python_requires=">=3.8",
31+
python_requires=">=3.9",
3232
classifiers=[
3333
"Intended Audience :: Developers",
3434
"Intended Audience :: Information Technology",
3535
"Intended Audience :: Science/Research",
36-
"Programming Language :: Python :: 3.8",
3736
"Programming Language :: Python :: 3.9",
3837
"Programming Language :: Python :: 3.10",
3938
"Programming Language :: Python :: 3.11",

stac_fastapi/types/setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@
3030
description="An implementation of STAC API based on the FastAPI framework.",
3131
long_description=desc,
3232
long_description_content_type="text/markdown",
33-
python_requires=">=3.8",
33+
python_requires=">=3.9",
3434
classifiers=[
3535
"Intended Audience :: Developers",
3636
"Intended Audience :: Information Technology",
3737
"Intended Audience :: Science/Research",
38-
"Programming Language :: Python :: 3.8",
3938
"Programming Language :: Python :: 3.9",
4039
"Programming Language :: Python :: 3.10",
4140
"Programming Language :: Python :: 3.11",

stac_fastapi/types/stac_fastapi/types/core.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Base clients."""
22

33
import abc
4-
import warnings
54
from typing import Any, Dict, List, Optional, Union
65
from urllib.parse import urljoin
76

@@ -341,20 +340,6 @@ class BaseCoreClient(LandingPageMixin, abc.ABC):
341340
factory=lambda: BASE_CONFORMANCE_CLASSES
342341
)
343342
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list))
344-
post_request_model = attr.ib(default=None)
345-
346-
@post_request_model.validator
347-
def _deprecate_post_model(self, attribute, value):
348-
"""Check and raise warning if `post_request_model` is set."""
349-
if value is not None:
350-
warnings.warn(
351-
"`post_request_model` attribute is deprecated and will be removed in 3.1",
352-
DeprecationWarning,
353-
)
354-
355-
def __attrs_post_init__(self):
356-
"""Set default value for post_request_model."""
357-
self.post_request_model = self.post_request_model or BaseSearchPostRequest
358343

359344
def conformance_classes(self) -> List[str]:
360345
"""Generate conformance classes by adding extension conformance to base
@@ -586,20 +571,6 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
586571
factory=lambda: BASE_CONFORMANCE_CLASSES
587572
)
588573
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list))
589-
post_request_model = attr.ib(default=None)
590-
591-
@post_request_model.validator
592-
def _deprecate_post_model(self, attribute, value):
593-
"""Check and raise warning if `post_request_model` is set."""
594-
if value is not None:
595-
warnings.warn(
596-
"`post_request_model` attribute is deprecated and will be removed in 3.1",
597-
DeprecationWarning,
598-
)
599-
600-
def __attrs_post_init__(self):
601-
"""Set default value for post_request_model."""
602-
self.post_request_model = self.post_request_model or BaseSearchPostRequest
603574

604575
def conformance_classes(self) -> List[str]:
605576
"""Generate conformance classes by adding extension conformance to base

0 commit comments

Comments
 (0)