Skip to content

Commit a1d076a

Browse files
jonhealy1pedro-cf
andauthored
Update for stac-fastapi v3.0.0a release (#234)
**Related Issue(s):** - #238 - #247 - #249 - stac-utils/stac-fastapi-pgstac#108 - stac-utils/stac-fastapi#685 - stac-utils/stac-fastapi#687 - stac-utils/stac-fastapi#690 - **Description:** Update stac-fastapi parent libraries to v3.0.0a. There are quite a few changes made in this pr. **PR Checklist:** - [x] Code is formatted and linted (run `pre-commit run --all-files`) - [x] Tests pass (run `make test`) - [x] Documentation has been updated to reflect changes, if applicable - [x] Changes are added to the changelog --------- Co-authored-by: pedro-cf <[email protected]>
1 parent 55dd87e commit a1d076a

File tree

22 files changed

+551
-460
lines changed

22 files changed

+551
-460
lines changed

.github/workflows/cicd.yml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
- 9202:9202
6666
strategy:
6767
matrix:
68-
python-version: [ "3.8", "3.9", "3.10", "3.11"]
68+
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12"]
6969

7070
name: Python ${{ matrix.python-version }} testing
7171

@@ -78,13 +78,21 @@ jobs:
7878
uses: actions/setup-python@v5
7979
with:
8080
python-version: ${{ matrix.python-version }}
81+
8182
- name: Lint code
82-
uses: pre-commit/[email protected]
83+
if: ${{ matrix.python-version == 3.11 }}
84+
run: |
85+
python -m pip install pre-commit
86+
pre-commit run --all-files
8387
8488
- name: Install pipenv
8589
run: |
8690
python -m pip install --upgrade pipenv wheel
8791
92+
- name: Install core library stac-fastapi
93+
run: |
94+
pip install ./stac_fastapi/core
95+
8896
- name: Install elasticsearch stac-fastapi
8997
run: |
9098
pip install ./stac_fastapi/elasticsearch[dev,server]
@@ -93,16 +101,12 @@ jobs:
93101
run: |
94102
pip install ./stac_fastapi/opensearch[dev,server]
95103
96-
- name: Install core library stac-fastapi
97-
run: |
98-
pip install ./stac_fastapi/core
99-
100104
- name: Run test suite against Elasticsearch 7.x
101105
run: |
102106
pipenv run pytest -svvv
103107
env:
104108
ENVIRONMENT: testing
105-
ES_PORT: 9200
109+
ES_PORT: 9400
106110
ES_HOST: 172.17.0.1
107111
ES_USE_SSL: false
108112
ES_VERIFY_CERTS: false
@@ -113,7 +117,7 @@ jobs:
113117
pipenv run pytest -svvv
114118
env:
115119
ENVIRONMENT: testing
116-
ES_PORT: 9400
120+
ES_PORT: 9200
117121
ES_HOST: 172.17.0.1
118122
ES_USE_SSL: false
119123
ES_VERIFY_CERTS: false

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Support for Python 3.12 [#234](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/234)
13+
14+
### Changed
15+
16+
- Updated stac-fastapi parent libraries to v3.0.0a0 [#234](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/234)
17+
- Removed pystac dependency [#234](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/234)
18+
19+
### Fixed
20+
21+
- Fixed issue where paginated search queries would return a `next_token` on the last page [#243](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/243)
22+
1023
## [v2.4.1]
1124

1225
### Added
@@ -15,7 +28,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1528

1629
### Fixed
1730

18-
- Fixed issue where paginated search queries would return a `next_token` on the last page [#243](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/243)
1931
- Fixed issue where searches return an empty `links` array [#241](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/241)
2032

2133
## [v2.4.0]

dockerfiles/Dockerfile.dev.es

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM python:3.10-slim
44
# update apt pkgs, and install build-essential for ciso8601
55
RUN apt-get update && \
66
apt-get -y upgrade && \
7-
apt-get install -y build-essential && \
7+
apt-get install -y build-essential git && \
88
apt-get clean && \
99
rm -rf /var/lib/apt/lists/*
1010

stac_fastapi/core/setup.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,18 @@
66
desc = f.read()
77

88
install_requires = [
9-
"fastapi",
10-
"attrs",
11-
"pydantic[dotenv]<2",
12-
"stac_pydantic==2.0.*",
13-
"stac-fastapi.types==2.5.5.post1",
14-
"stac-fastapi.api==2.5.5.post1",
15-
"stac-fastapi.extensions==2.5.5.post1",
16-
"pystac[validation]",
9+
"fastapi-slim",
10+
"attrs>=23.2.0",
11+
"pydantic[dotenv]",
12+
"stac_pydantic>=3",
13+
"stac-fastapi.types==3.0.0a",
14+
"stac-fastapi.api==3.0.0a",
15+
"stac-fastapi.extensions==3.0.0a",
1716
"orjson",
1817
"overrides",
1918
"geojson-pydantic",
2019
"pygeofilter==0.2.1",
21-
"typing_extensions==4.4.0",
20+
"typing_extensions==4.8.0",
2221
]
2322

2423
setup(
@@ -35,6 +34,7 @@
3534
"Programming Language :: Python :: 3.9",
3635
"Programming Language :: Python :: 3.10",
3736
"Programming Language :: Python :: 3.11",
37+
"Programming Language :: Python :: 3.12",
3838
"License :: OSI Approved :: MIT License",
3939
],
4040
url="https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch",

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import re
44
from datetime import datetime as datetime_type
55
from datetime import timezone
6+
from enum import Enum
67
from typing import Any, Dict, List, Optional, Set, Type, Union
78
from urllib.parse import unquote_plus, urljoin
89

@@ -14,6 +15,7 @@
1415
from pydantic import ValidationError
1516
from pygeofilter.backends.cql2_json import to_cql2
1617
from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
18+
from stac_pydantic import Collection, Item, ItemCollection
1719
from stac_pydantic.links import Relations
1820
from stac_pydantic.shared import BBox, MimeTypes
1921
from stac_pydantic.version import STAC_VERSION
@@ -25,7 +27,6 @@
2527
from stac_fastapi.core.session import Session
2628
from stac_fastapi.core.types.core import (
2729
AsyncBaseCoreClient,
28-
AsyncBaseFiltersClient,
2930
AsyncBaseTransactionsClient,
3031
)
3132
from stac_fastapi.extensions.third_party.bulk_transactions import (
@@ -36,11 +37,11 @@
3637
from stac_fastapi.types import stac as stac_types
3738
from stac_fastapi.types.config import Settings
3839
from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
40+
from stac_fastapi.types.core import AsyncBaseFiltersClient
3941
from stac_fastapi.types.extension import ApiExtension
4042
from stac_fastapi.types.requests import get_base_url
4143
from stac_fastapi.types.rfc3339 import DateTimeType
4244
from stac_fastapi.types.search import BaseSearchPostRequest
43-
from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection
4445

4546
logger = logging.getLogger(__name__)
4647

@@ -189,7 +190,7 @@ async def landing_page(self, **kwargs) -> stac_types.LandingPage:
189190

190191
return landing_page
191192

192-
async def all_collections(self, **kwargs) -> Collections:
193+
async def all_collections(self, **kwargs) -> stac_types.Collections:
193194
"""Read all collections from the database.
194195
195196
Args:
@@ -221,9 +222,11 @@ async def all_collections(self, **kwargs) -> Collections:
221222
next_link = PagingLinks(next=next_token, request=request).link_next()
222223
links.append(next_link)
223224

224-
return Collections(collections=collections, links=links)
225+
return stac_types.Collections(collections=collections, links=links)
225226

226-
async def get_collection(self, collection_id: str, **kwargs) -> Collection:
227+
async def get_collection(
228+
self, collection_id: str, **kwargs
229+
) -> stac_types.Collection:
227230
"""Get a collection from the database by its id.
228231
229232
Args:
@@ -250,7 +253,7 @@ async def item_collection(
250253
limit: int = 10,
251254
token: str = None,
252255
**kwargs,
253-
) -> ItemCollection:
256+
) -> stac_types.ItemCollection:
254257
"""Read items from a specific collection in the database.
255258
256259
Args:
@@ -320,14 +323,16 @@ async def item_collection(
320323

321324
links = await PagingLinks(request=request, next=next_token).get_links()
322325

323-
return ItemCollection(
326+
return stac_types.ItemCollection(
324327
type="FeatureCollection",
325328
features=items,
326329
links=links,
327330
context=context_obj,
328331
)
329332

330-
async def get_item(self, item_id: str, collection_id: str, **kwargs) -> Item:
333+
async def get_item(
334+
self, item_id: str, collection_id: str, **kwargs
335+
) -> stac_types.Item:
331336
"""Get an item from the database based on its id and collection id.
332337
333338
Args:
@@ -399,6 +404,24 @@ def _return_date(
399404

400405
return result
401406

407+
def _format_datetime_range(self, date_tuple: DateTimeType) -> str:
408+
"""
409+
Convert a tuple of datetime objects or None into a formatted string for API requests.
410+
411+
Args:
412+
date_tuple (tuple): A tuple containing two elements, each can be a datetime object or None.
413+
414+
Returns:
415+
str: A string formatted as 'YYYY-MM-DDTHH:MM:SS.sssZ/YYYY-MM-DDTHH:MM:SS.sssZ', with '..' used if any element is None.
416+
"""
417+
418+
def format_datetime(dt):
419+
"""Format a single datetime object to the ISO8601 extended format with 'Z'."""
420+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" if dt else ".."
421+
422+
start, end = date_tuple
423+
return f"{format_datetime(start)}/{format_datetime(end)}"
424+
402425
async def get_search(
403426
self,
404427
request: Request,
@@ -415,7 +438,7 @@ async def get_search(
415438
filter: Optional[str] = None,
416439
filter_lang: Optional[str] = None,
417440
**kwargs,
418-
) -> ItemCollection:
441+
) -> stac_types.ItemCollection:
419442
"""Get search results from the database.
420443
421444
Args:
@@ -455,7 +478,7 @@ async def get_search(
455478
filter_lang = match.group(1)
456479

457480
if datetime:
458-
base_args["datetime"] = datetime
481+
base_args["datetime"] = self._format_datetime_range(datetime)
459482

460483
if intersects:
461484
base_args["intersects"] = orjson.loads(unquote_plus(intersects))
@@ -502,7 +525,7 @@ async def get_search(
502525

503526
async def post_search(
504527
self, search_request: BaseSearchPostRequest, request: Request
505-
) -> ItemCollection:
528+
) -> stac_types.ItemCollection:
506529
"""
507530
Perform a POST search on the catalog.
508531
@@ -552,8 +575,10 @@ async def post_search(
552575
for field_name, expr in search_request.query.items():
553576
field = "properties__" + field_name
554577
for op, value in expr.items():
578+
# Convert enum to string
579+
operator = op.value if isinstance(op, Enum) else op
555580
search = self.database.apply_stacql_filter(
556-
search=search, op=op, field=field, value=value
581+
search=search, op=operator, field=field, value=value
557582
)
558583

559584
# only cql2_json is supported here
@@ -619,7 +644,7 @@ async def post_search(
619644

620645
links = await PagingLinks(request=request, next=next_token).get_links()
621646

622-
return ItemCollection(
647+
return stac_types.ItemCollection(
623648
type="FeatureCollection",
624649
features=items,
625650
links=links,
@@ -637,7 +662,7 @@ class TransactionsClient(AsyncBaseTransactionsClient):
637662

638663
@overrides
639664
async def create_item(
640-
self, collection_id: str, item: stac_types.Item, **kwargs
665+
self, collection_id: str, item: Union[Item, ItemCollection], **kwargs
641666
) -> Optional[stac_types.Item]:
642667
"""Create an item in the collection.
643668
@@ -654,6 +679,7 @@ async def create_item(
654679
ConflictError: If the item in the specified collection already exists.
655680
656681
"""
682+
item = item.model_dump(mode="json")
657683
base_url = str(kwargs["request"].base_url)
658684

659685
# If a feature collection is posted
@@ -677,7 +703,7 @@ async def create_item(
677703

678704
@overrides
679705
async def update_item(
680-
self, collection_id: str, item_id: str, item: stac_types.Item, **kwargs
706+
self, collection_id: str, item_id: str, item: Item, **kwargs
681707
) -> stac_types.Item:
682708
"""Update an item in the collection.
683709
@@ -694,13 +720,14 @@ async def update_item(
694720
NotFound: If the specified collection is not found in the database.
695721
696722
"""
723+
item = item.model_dump(mode="json")
697724
base_url = str(kwargs["request"].base_url)
698725
now = datetime_type.now(timezone.utc).isoformat().replace("+00:00", "Z")
699726
item["properties"]["updated"] = now
700727

701728
await self.database.check_collection_exists(collection_id)
702729
await self.delete_item(item_id=item_id, collection_id=collection_id)
703-
await self.create_item(collection_id=collection_id, item=item, **kwargs)
730+
await self.create_item(collection_id=collection_id, item=Item(**item), **kwargs)
704731

705732
return ItemSerializer.db_to_stac(item, base_url)
706733

@@ -722,7 +749,7 @@ async def delete_item(
722749

723750
@overrides
724751
async def create_collection(
725-
self, collection: stac_types.Collection, **kwargs
752+
self, collection: Collection, **kwargs
726753
) -> stac_types.Collection:
727754
"""Create a new collection in the database.
728755
@@ -736,17 +763,17 @@ async def create_collection(
736763
Raises:
737764
ConflictError: If the collection already exists.
738765
"""
766+
collection = collection.model_dump(mode="json")
739767
base_url = str(kwargs["request"].base_url)
740768
collection = self.database.collection_serializer.stac_to_db(
741769
collection, base_url
742770
)
743771
await self.database.create_collection(collection=collection)
744-
745772
return CollectionSerializer.db_to_stac(collection, base_url)
746773

747774
@overrides
748775
async def update_collection(
749-
self, collection: stac_types.Collection, **kwargs
776+
self, collection: Collection, **kwargs
750777
) -> stac_types.Collection:
751778
"""
752779
Update a collection.
@@ -766,6 +793,8 @@ async def update_collection(
766793
A STAC collection that has been updated in the database.
767794
768795
"""
796+
collection = collection.model_dump(mode="json")
797+
769798
base_url = str(kwargs["request"].base_url)
770799

771800
collection_id = kwargs["request"].query_params.get(

0 commit comments

Comments
 (0)