From 18a1982e09ecd92daa85ec0bcc1d2590d2c61c4f Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Fri, 17 Jan 2025 18:41:33 +0100 Subject: [PATCH 1/3] prepare for 4.0 --- .github/workflows/cicd.yaml | 2 +- CHANGES.md | 7 +- docs/mkdocs.yml | 1 + docs/src/migrations/v4.0.0.md | 190 ++++++++++++++++++ pyproject.toml | 2 +- stac_fastapi/api/setup.py | 5 +- stac_fastapi/extensions/setup.py | 7 +- stac_fastapi/types/setup.py | 3 +- stac_fastapi/types/stac_fastapi/types/core.py | 29 --- 9 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 docs/src/migrations/v4.0.0.md diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml index 5f97b06c2..60e2a16cc 100644 --- a/.github/workflows/cicd.yaml +++ b/.github/workflows/cicd.yaml @@ -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: diff --git a/CHANGES.md b/CHANGES.md index 4290fa1d5..c78b9e525 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 @@ -524,7 +526,8 @@ Full changelog: https://stac-utils.github.io/stac-fastapi/migrations/v3.0.0/#cha * First PyPi release! -[Unreleased]: +[Unreleased]: +[4.0.0]: [3.0.5]: [3.0.4]: [3.0.3]: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 79af024a0..3296b2531 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -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" diff --git a/docs/src/migrations/v4.0.0.md b/docs/src/migrations/v4.0.0.md new file mode 100644 index 000000000..9bcb5f9ed --- /dev/null +++ b/docs/src/migrations/v4.0.0.md @@ -0,0 +1,190 @@ + +# 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"] +``` + + diff --git a/pyproject.toml b/pyproject.toml index b0f53c4d0..842195cfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -target-version = "py38" # minimum supported version +target-version = "py39" # minimum supported version line-length = 90 [tool.ruff.lint] diff --git a/stac_fastapi/api/setup.py b/stac_fastapi/api/setup.py index 14b515e30..11d959fee 100644 --- a/stac_fastapi/api/setup.py +++ b/stac_fastapi/api/setup.py @@ -7,7 +7,7 @@ install_requires = [ "brotli_asgi", - "stac-fastapi.types~=3.0", + "stac-fastapi.types~=4.0", ] extra_reqs = { @@ -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", diff --git a/stac_fastapi/extensions/setup.py b/stac_fastapi/extensions/setup.py index e5b2de5c2..f1ebee73e 100644 --- a/stac_fastapi/extensions/setup.py +++ b/stac_fastapi/extensions/setup.py @@ -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 = { @@ -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", diff --git a/stac_fastapi/types/setup.py b/stac_fastapi/types/setup.py index 2d78ee17c..bab96fc2e 100644 --- a/stac_fastapi/types/setup.py +++ b/stac_fastapi/types/setup.py @@ -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", diff --git a/stac_fastapi/types/stac_fastapi/types/core.py b/stac_fastapi/types/stac_fastapi/types/core.py index d7178781c..a1b65cd12 100644 --- a/stac_fastapi/types/stac_fastapi/types/core.py +++ b/stac_fastapi/types/stac_fastapi/types/core.py @@ -1,7 +1,6 @@ """Base clients.""" import abc -import warnings from typing import Any, Dict, List, Optional, Union from urllib.parse import urljoin @@ -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 @@ -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 From 08c743d0fee15c36e1095879ab9219d876fbe554 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Fri, 17 Jan 2025 18:41:39 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Bump=20version:=203.0.5=20=E2=86=92=204.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- pyproject.toml | 2 +- stac_fastapi/api/stac_fastapi/api/version.py | 2 +- stac_fastapi/extensions/stac_fastapi/extensions/version.py | 2 +- stac_fastapi/types/stac_fastapi/types/version.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index eca690e73..fcdb2e109 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.5 +4.0.0 diff --git a/pyproject.toml b/pyproject.toml index 842195cfa..cdf8a27d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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\\d+)\\. (?P\\d+)\\. diff --git a/stac_fastapi/api/stac_fastapi/api/version.py b/stac_fastapi/api/stac_fastapi/api/version.py index c675263b3..a811649a0 100644 --- a/stac_fastapi/api/stac_fastapi/api/version.py +++ b/stac_fastapi/api/stac_fastapi/api/version.py @@ -1,3 +1,3 @@ """Library version.""" -__version__ = "3.0.5" +__version__ = "4.0.0" diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/version.py b/stac_fastapi/extensions/stac_fastapi/extensions/version.py index c675263b3..a811649a0 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/version.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/version.py @@ -1,3 +1,3 @@ """Library version.""" -__version__ = "3.0.5" +__version__ = "4.0.0" diff --git a/stac_fastapi/types/stac_fastapi/types/version.py b/stac_fastapi/types/stac_fastapi/types/version.py index c675263b3..a811649a0 100644 --- a/stac_fastapi/types/stac_fastapi/types/version.py +++ b/stac_fastapi/types/stac_fastapi/types/version.py @@ -1,3 +1,3 @@ """Library version.""" -__version__ = "3.0.5" +__version__ = "4.0.0" From 0363b612f67d6e5a7068bdb2fc894d3a4c36ed58 Mon Sep 17 00:00:00 2001 From: Vincent Sarago Date: Sat, 18 Jan 2025 00:40:33 +0100 Subject: [PATCH 3/3] Update docs/src/migrations/v4.0.0.md Co-authored-by: Pete Gadomski --- docs/src/migrations/v4.0.0.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/migrations/v4.0.0.md b/docs/src/migrations/v4.0.0.md index 9bcb5f9ed..c8e70eb6f 100644 --- a/docs/src/migrations/v4.0.0.md +++ b/docs/src/migrations/v4.0.0.md @@ -1,4 +1,3 @@ - # stac-fastapi v4.0 Migration Guide This document aims to help you update your application from **stac-fastapi** 3.0 to 4.0