Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
timeout-minutes: 20

steps:
Expand Down
7 changes: 5 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Changelog

## [Unreleased]
## [4.0.0] - 2025-01-17

### Changed

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

### Fixed

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

* First PyPi release!

[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.5..main>
[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/4.0.0..main>
[4.0.0]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.5..4.0.0>
[3.0.5]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.4..3.0.5>
[3.0.4]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.3..3.0.4>
[3.0.3]: <https://github.com/stac-utils/stac-fastapi/compare/3.0.2..3.0.3>
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.5
4.0.0
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ nav:
- version: api/stac_fastapi/types/version.md
- Migration Guides:
- v2.5 -> v3.0: migrations/v3.0.0.md
- v3.0 -> v4.0: migrations/v4.0.0.md
- Performance Benchmarks: benchmarks.html
- Development - Contributing: "contributing.md"
- Release Notes: "release-notes.md"
Expand Down
189 changes: 189 additions & 0 deletions docs/src/migrations/v4.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# stac-fastapi v4.0 Migration Guide

This document aims to help you update your application from **stac-fastapi** 3.0 to 4.0

## CHANGELOG
### Changed

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

### Fixed

* Support multiple proxy servers in the `forwarded` header in `ProxyHeaderMiddleware` ([#782](https://github.com/stac-utils/stac-fastapi/pull/782))

## Datetime type in GET request models

While the POST request models are created using stac-pydantic, the GET request models are python `attrs` classes (~dataclasses).
In 4.0, we've decided to change how the `datetime` attribute was defined in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` models to match
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).

```python
from starlette.testclient import TestClient
from stac_fastapi.api.app import StacApi
from stac_fastapi.types.config import ApiSettings
from stac_fastapi.types.core import BaseCoreClient

class DummyCoreClient(BaseCoreClient):
def all_collections(self, *args, **kwargs):
raise NotImplementedError

def get_collection(self, *args, **kwargs):
raise NotImplementedError

def get_item(self, *args, **kwargs):
raise NotImplementedError

def get_search(self, *args, datetime = None, **kwargs):
# Return True if datetime is a string
return isinstance(datetime, str)

def post_search(self, *args, **kwargs):
raise NotImplementedError

def item_collection(self, *args, **kwargs):
raise NotImplementedError

api = StacApi(
settings=ApiSettings(enable_response_models=False),
client=DummyCoreClient(),
extensions=[],
)


# before
with TestClient(api.app) as client:
response = client.get(
"/search",
params={
"datetime": "2020-01-01T00:00:00.00001Z",
},
)
assert response.json() == False

# now
with TestClient(api.app) as client:
response = client.get(
"/search",
params={
"datetime": "2020-01-01T00:00:00.00001Z",
},
)
assert response.json() == True
```

#### Start/End dates

Following stac-pydantic's `Search` model, we've added class attributes to easily retrieve the `parsed` dates:

```python
from stac_fastapi.types.search import BaseSearchGetRequest

# Interval
search = BaseSearchGetRequest(datetime="2020-01-01T00:00:00.00001Z/2020-01-02T00:00:00.00001Z")

search.parse_datetime()
>>> (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))

search.start_date
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)

search.end_date
>>> datetime.datetime(2020, 1, 2, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)

# Single date
search = BaseSearchGetRequest(datetime="2020-01-01T00:00:00.00001Z")

search.parse_datetime()
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)

search.start_date
>>> datetime.datetime(2020, 1, 1, 0, 0, 0, 10, tzinfo=datetime.timezone.utc)

search.end_date
>>> None
```

## Filter extension

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.

Note: This change does not affect the `input` because we use `aliases`.

```python
from starlette.testclient import TestClient
from stac_fastapi.api.app import StacApi
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
from stac_fastapi.extensions.core import FilterExtension
from stac_fastapi.types.config import ApiSettings
from stac_fastapi.types.core import BaseCoreClient

class DummyCoreClient(BaseCoreClient):
def all_collections(self, *args, **kwargs):
raise NotImplementedError

def get_collection(self, *args, **kwargs):
raise NotImplementedError

def get_item(self, *args, **kwargs):
raise NotImplementedError

def get_search(self, *args, **kwargs):
return kwargs

def post_search(self, *args, **kwargs):
return args[0].model_dump()

def item_collection(self, *args, **kwargs):
raise NotImplementedError

extensions = [FilterExtension()]
api = StacApi(
settings=ApiSettings(enable_response_models=False),
client=DummyCoreClient(),
extensions=extensions,
search_get_request_model=create_get_request_model(extensions),
search_post_request_model=create_post_request_model(extensions),
)


# before
with TestClient(api.app) as client:
response = client.post(
"/search",
json={
"filter": {"op": "=", "args": [{"property": "test_property"}, "test-value"]},
},
)
assert response.json()["filter"]

response = client.get(
"/search",
params={
"filter": "id='item_id' AND collection='collection_id'",
},
)
assert response.json()["filter"]

# now
with TestClient(api.app) as client:
response = client.post(
"/search",
json={
"filter": {"op": "=", "args": [{"property": "test_property"}, "test-value"]},
},
)
assert response.json()["filter_expr"]

response = client.get(
"/search",
params={
"filter": "id='item_id' AND collection='collection_id'",
},
)
assert response.json()["filter_expr"]
```


4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.ruff]
target-version = "py38" # minimum supported version
target-version = "py39" # minimum supported version
line-length = 90

[tool.ruff.lint]
Expand All @@ -24,7 +24,7 @@ section-order = ["future", "standard-library", "third-party", "first-party", "lo
quote-style = "double"

[tool.bumpversion]
current_version = "3.0.5"
current_version = "4.0.0"
parse = """(?x)
(?P<major>\\d+)\\.
(?P<minor>\\d+)\\.
Expand Down
5 changes: 2 additions & 3 deletions stac_fastapi/api/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

install_requires = [
"brotli_asgi",
"stac-fastapi.types~=3.0",
"stac-fastapi.types~=4.0",
]

extra_reqs = {
Expand All @@ -31,12 +31,11 @@
description="An implementation of STAC API based on the FastAPI framework.",
long_description=desc,
long_description_content_type="text/markdown",
python_requires=">=3.8",
python_requires=">=3.9",
classifiers=[
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/api/stac_fastapi/api/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Library version."""

__version__ = "3.0.5"
__version__ = "4.0.0"
7 changes: 3 additions & 4 deletions stac_fastapi/extensions/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
desc = f.read()

install_requires = [
"stac-fastapi.types~=3.0",
"stac-fastapi.api~=3.0",
"stac-fastapi.types~=4.0",
"stac-fastapi.api~=4.0",
]

extra_reqs = {
Expand All @@ -28,12 +28,11 @@
description="An implementation of STAC API based on the FastAPI framework.",
long_description=desc,
long_description_content_type="text/markdown",
python_requires=">=3.8",
python_requires=">=3.9",
classifiers=[
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/extensions/stac_fastapi/extensions/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Library version."""

__version__ = "3.0.5"
__version__ = "4.0.0"
3 changes: 1 addition & 2 deletions stac_fastapi/types/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@
description="An implementation of STAC API based on the FastAPI framework.",
long_description=desc,
long_description_content_type="text/markdown",
python_requires=">=3.8",
python_requires=">=3.9",
classifiers=[
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down
29 changes: 0 additions & 29 deletions stac_fastapi/types/stac_fastapi/types/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Base clients."""

import abc
import warnings
from typing import Any, Dict, List, Optional, Union
from urllib.parse import urljoin

Expand Down Expand Up @@ -341,20 +340,6 @@ class BaseCoreClient(LandingPageMixin, abc.ABC):
factory=lambda: BASE_CONFORMANCE_CLASSES
)
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list))
post_request_model = attr.ib(default=None)

@post_request_model.validator
def _deprecate_post_model(self, attribute, value):
"""Check and raise warning if `post_request_model` is set."""
if value is not None:
warnings.warn(
"`post_request_model` attribute is deprecated and will be removed in 3.1",
DeprecationWarning,
)

def __attrs_post_init__(self):
"""Set default value for post_request_model."""
self.post_request_model = self.post_request_model or BaseSearchPostRequest

def conformance_classes(self) -> List[str]:
"""Generate conformance classes by adding extension conformance to base
Expand Down Expand Up @@ -586,20 +571,6 @@ class AsyncBaseCoreClient(LandingPageMixin, abc.ABC):
factory=lambda: BASE_CONFORMANCE_CLASSES
)
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list))
post_request_model = attr.ib(default=None)

@post_request_model.validator
def _deprecate_post_model(self, attribute, value):
"""Check and raise warning if `post_request_model` is set."""
if value is not None:
warnings.warn(
"`post_request_model` attribute is deprecated and will be removed in 3.1",
DeprecationWarning,
)

def __attrs_post_init__(self):
"""Set default value for post_request_model."""
self.post_request_model = self.post_request_model or BaseSearchPostRequest

def conformance_classes(self) -> List[str]:
"""Generate conformance classes by adding extension conformance to base
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/types/stac_fastapi/types/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Library version."""

__version__ = "3.0.5"
__version__ = "4.0.0"
Loading