Skip to content

Commit

Permalink
Release/v4.0.0 (#786)
Browse files Browse the repository at this point in the history
* prepare for 4.0

* Bump version: 3.0.5 → 4.0.0

* Update docs/src/migrations/v4.0.0.md

Co-authored-by: Pete Gadomski <[email protected]>

---------

Co-authored-by: Pete Gadomski <[email protected]>
  • Loading branch information
vincentsarago and gadomski authored Jan 20, 2025
1 parent 2a72400 commit 5ded0c6
Show file tree
Hide file tree
Showing 13 changed files with 208 additions and 47 deletions.
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"

0 comments on commit 5ded0c6

Please sign in to comment.