Skip to content

Commit 7956293

Browse files
update for stac-fastapi 4.0 (#189)
* update for stac-fastapi 4.0 * update docker images * use 3.10 for docs * update docs * Update CHANGES.md Co-authored-by: Henry Rodman <henry.rodman@gmail.com> * remove format_datetime_range function --------- Co-authored-by: Henry Rodman <henry.rodman@gmail.com>
1 parent 4406e7c commit 7956293

File tree

12 files changed

+49
-71
lines changed

12 files changed

+49
-71
lines changed

.github/workflows/cicd.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ jobs:
1717
- {python: '3.11', pypgstac: '0.8.*'}
1818
- {python: '3.10', pypgstac: '0.8.*'}
1919
- {python: '3.9', pypgstac: '0.8.*'}
20-
- {python: '3.8', pypgstac: '0.8.*'}
2120

2221
timeout-minutes: 20
2322

CHANGES.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,27 @@
22

33
## [Unreleased]
44

5-
- Fix Docker compose file, so example data can be loaded into database (author @zstatmanweil, <https://github.com/stac-utils/stac-fastapi-pgstac/pull/142>)
5+
### Changed
6+
67
- Handle `next` and `dev` tokens now returned as links from pgstac>=0.9.0 (author @zstatmanweil, https://github.com/stac-utils/stac-fastapi-pgstac/pull/140)
78
- Add collection search extension ([#139](https://github.com/stac-utils/stac-fastapi-pgstac/pull/139))
89
- keep `/search` and `/collections` extensions separate ([#158](https://github.com/stac-utils/stac-fastapi-pgstac/pull/158))
9-
- Fix `filter` extension implementation in `CoreCrudClient`
1010
- update `pypgstac` requirement to `>=0.8,<0.10`
1111
- set `pypgstac==0.9.*` for test requirements
12+
- update `stac-fastapi-*` requirement to `~=4.0`
13+
- remove `python 3.8` support
14+
- renamed `post_request_model` attribute to `pgstac_search_model` in `CoreCrudClient` class
15+
- changed `datetime` input type to `string` in GET endpoint methods
16+
- renamed `filter` to `filter_expr` input attributes in GET endpoint methods
17+
- delete `utils.format_datetime_range` function
18+
19+
### Fixed
20+
21+
- Fix Docker compose file, so example data can be loaded into database (author @zstatmanweil, <https://github.com/stac-utils/stac-fastapi-pgstac/pull/142>)
22+
- Fix `filter` extension implementation in `CoreCrudClient`
1223

1324
## [3.0.1] - 2024-11-14
25+
1426
- Enable runtime `CORS` configuration using environment variables (`CORS_ORIGIN="https://...,https://..."`, `CORS_METHODS="PUT,OPTIONS"`) (https://github.com/stac-utils/stac-fastapi-pgstac/pull/168)
1527

1628
## [3.0.0] - 2024-08-02

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG PYTHON_VERSION=3.11
1+
ARG PYTHON_VERSION=3.12
22

33
FROM python:${PYTHON_VERSION}-slim as base
44

Dockerfile.docs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.8-slim
1+
FROM python:3.10-slim
22

33
# build-essential is required to build a wheel for ciso8601
44
RUN apt update && apt install -y build-essential

Dockerfile.tests

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG PYTHON_VERSION=3.11
1+
ARG PYTHON_VERSION=3.12
22

33
FROM python:${PYTHON_VERSION}-slim as base
44

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ PgSTAC stores all collection and item records as jsonb fields exactly as they co
2727
| --| --|
2828
| 2.5 | >=0.7,<0.8 |
2929
| 3.0 | >=0.8,<0.9 |
30+
| 4.0 | >=0.8,<0.10 |
3031

3132
## Usage
3233

setup.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"orjson",
1111
"pydantic",
1212
"stac_pydantic==3.1.*",
13-
"stac-fastapi.api~=3.0.3",
14-
"stac-fastapi.extensions~=3.0.3",
15-
"stac-fastapi.types~=3.0.3",
13+
"stac-fastapi.api~=4.0",
14+
"stac-fastapi.extensions~=4.0",
15+
"stac-fastapi.types~=4.0",
1616
"asyncpg",
1717
"buildpg",
1818
"brotli_asgi",
@@ -46,12 +46,15 @@
4646
description="An implementation of STAC API based on the FastAPI framework and using the pgstac backend.",
4747
long_description=desc,
4848
long_description_content_type="text/markdown",
49-
python_requires=">=3.8",
49+
python_requires=">=3.9",
5050
classifiers=[
5151
"Intended Audience :: Developers",
5252
"Intended Audience :: Information Technology",
5353
"Intended Audience :: Science/Research",
54-
"Programming Language :: Python :: 3.8",
54+
"Programming Language :: Python :: 3.9",
55+
"Programming Language :: Python :: 3.10",
56+
"Programming Language :: Python :: 3.11",
57+
"Programming Language :: Python :: 3.12",
5558
"License :: OSI Approved :: MIT License",
5659
],
5760
keywords="STAC FastAPI COG",

stac_fastapi/pgstac/app.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ async def lifespan(app: FastAPI):
117117
openapi_url=settings.openapi_url,
118118
docs_url=settings.docs_url,
119119
redoc_url=None,
120-
root_path=getattr(settings, "root_path", None),
120+
root_path=settings.root_path,
121121
lifespan=lifespan,
122122
)
123123

@@ -128,7 +128,7 @@ async def lifespan(app: FastAPI):
128128
extensions=extensions + [collection_search_extension]
129129
if collection_search_extension
130130
else extensions,
131-
client=CoreCrudClient(post_request_model=post_request_model), # type: ignore
131+
client=CoreCrudClient(pgstac_search_model=post_request_model),
132132
response_class=ORJSONResponse,
133133
items_get_request_model=items_get_request_model,
134134
search_get_request_model=get_request_model,

stac_fastapi/pgstac/core.py

+18-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44
import re
5-
from typing import Any, Dict, List, Optional, Set, Union
5+
from typing import Any, Dict, List, Optional, Set, Type, Union
66
from urllib.parse import unquote_plus, urljoin
77

88
import attr
@@ -18,7 +18,6 @@
1818
from stac_fastapi.types.core import AsyncBaseCoreClient, Relations
1919
from stac_fastapi.types.errors import InvalidQueryParameter, NotFoundError
2020
from stac_fastapi.types.requests import get_base_url
21-
from stac_fastapi.types.rfc3339 import DateTimeType
2221
from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection
2322
from stac_pydantic.shared import BBox, MimeTypes
2423

@@ -31,7 +30,7 @@
3130
PagingLinks,
3231
)
3332
from stac_fastapi.pgstac.types.search import PgstacSearch
34-
from stac_fastapi.pgstac.utils import filter_fields, format_datetime_range
33+
from stac_fastapi.pgstac.utils import filter_fields
3534

3635
NumType = Union[float, int]
3736

@@ -40,18 +39,20 @@
4039
class CoreCrudClient(AsyncBaseCoreClient):
4140
"""Client for core endpoints defined by stac."""
4241

42+
pgstac_search_model: Type[PgstacSearch] = attr.ib(default=PgstacSearch)
43+
4344
async def all_collections( # noqa: C901
4445
self,
4546
request: Request,
4647
# Extensions
4748
bbox: Optional[BBox] = None,
48-
datetime: Optional[DateTimeType] = None,
49+
datetime: Optional[str] = None,
4950
limit: Optional[int] = None,
5051
offset: Optional[int] = None,
5152
query: Optional[str] = None,
5253
fields: Optional[List[str]] = None,
5354
sortby: Optional[str] = None,
54-
filter: Optional[str] = None,
55+
filter_expr: Optional[str] = None,
5556
filter_lang: Optional[str] = None,
5657
**kwargs,
5758
) -> Collections:
@@ -82,7 +83,7 @@ async def all_collections( # noqa: C901
8283
datetime=datetime,
8384
fields=fields,
8485
sortby=sortby,
85-
filter_query=filter,
86+
filter_query=filter_expr,
8687
filter_lang=filter_lang,
8788
)
8889

@@ -234,9 +235,6 @@ async def _search_base( # noqa: C901
234235

235236
settings: Settings = request.app.state.settings
236237

237-
if search_request.datetime:
238-
search_request.datetime = format_datetime_range(search_request.datetime)
239-
240238
search_request.conf = search_request.conf or {}
241239
search_request.conf["nohydrate"] = settings.use_api_hydrate
242240

@@ -340,7 +338,7 @@ async def item_collection(
340338
collection_id: str,
341339
request: Request,
342340
bbox: Optional[BBox] = None,
343-
datetime: Optional[DateTimeType] = None,
341+
datetime: Optional[str] = None,
344342
limit: Optional[int] = None,
345343
token: Optional[str] = None,
346344
**kwargs,
@@ -360,9 +358,6 @@ async def item_collection(
360358
# If collection does not exist, NotFoundError wil be raised
361359
await self.get_collection(collection_id, request=request)
362360

363-
if datetime:
364-
datetime = format_datetime_range(datetime)
365-
366361
base_args = {
367362
"collections": [collection_id],
368363
"bbox": bbox,
@@ -373,7 +368,7 @@ async def item_collection(
373368

374369
if self.extension_is_enabled("FilterExtension"):
375370
filter_lang = kwargs.get("filter_lang", None)
376-
filter_query = kwargs.get("filter", None)
371+
filter_query = kwargs.get("filter_expr", None)
377372
if filter_query:
378373
if filter_lang == "cql2-text":
379374
filter_query = to_cql2(parse_cql2_text(filter_query))
@@ -387,7 +382,7 @@ async def item_collection(
387382
if v is not None and v != []:
388383
clean[k] = v
389384

390-
search_request = self.post_request_model(**clean)
385+
search_request = self.pgstac_search_model(**clean)
391386
item_collection = await self._search_base(search_request, request=request)
392387

393388
links = await ItemCollectionLinks(
@@ -414,7 +409,7 @@ async def get_item(
414409
# If collection does not exist, NotFoundError wil be raised
415410
await self.get_collection(collection_id, request=request)
416411

417-
search_request = self.post_request_model(
412+
search_request = self.pgstac_search_model(
418413
ids=[item_id], collections=[collection_id], limit=1
419414
)
420415
item_collection = await self._search_base(search_request, request=request)
@@ -456,14 +451,14 @@ async def get_search(
456451
ids: Optional[List[str]] = None,
457452
bbox: Optional[BBox] = None,
458453
intersects: Optional[str] = None,
459-
datetime: Optional[DateTimeType] = None,
454+
datetime: Optional[str] = None,
460455
limit: Optional[int] = None,
461456
# Extensions
462457
query: Optional[str] = None,
463458
token: Optional[str] = None,
464459
fields: Optional[List[str]] = None,
465460
sortby: Optional[str] = None,
466-
filter: Optional[str] = None,
461+
filter_expr: Optional[str] = None,
467462
filter_lang: Optional[str] = None,
468463
**kwargs,
469464
) -> ItemCollection:
@@ -490,13 +485,13 @@ async def get_search(
490485
datetime=datetime,
491486
fields=fields,
492487
sortby=sortby,
493-
filter_query=filter,
488+
filter_query=filter_expr,
494489
filter_lang=filter_lang,
495490
)
496491

497492
# Do the request
498493
try:
499-
search_request = self.post_request_model(**clean)
494+
search_request = self.pgstac_search_model(**clean)
500495
except ValidationError as e:
501496
raise HTTPException(
502497
status_code=400, detail=f"Invalid parameters provided {e}"
@@ -508,7 +503,7 @@ def _clean_search_args( # noqa: C901
508503
self,
509504
base_args: Dict[str, Any],
510505
intersects: Optional[str] = None,
511-
datetime: Optional[DateTimeType] = None,
506+
datetime: Optional[str] = None,
512507
fields: Optional[List[str]] = None,
513508
sortby: Optional[str] = None,
514509
filter_query: Optional[str] = None,
@@ -524,7 +519,7 @@ def _clean_search_args( # noqa: C901
524519
base_args["filter_lang"] = filter_lang
525520

526521
if datetime:
527-
base_args["datetime"] = format_datetime_range(datetime)
522+
base_args["datetime"] = datetime
528523

529524
if intersects:
530525
base_args["intersects"] = orjson.loads(unquote_plus(intersects))
@@ -553,6 +548,7 @@ def _clean_search_args( # noqa: C901
553548
includes.add(field[1:])
554549
else:
555550
includes.add(field)
551+
556552
base_args["fields"] = {"include": includes, "exclude": excludes}
557553

558554
# Remove None values from dict

stac_fastapi/pgstac/utils.py

-33
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
"""stac-fastapi utility methods."""
22

3-
from datetime import datetime
43
from typing import Any, Dict, Optional, Set, Union
54

6-
from stac_fastapi.types.rfc3339 import DateTimeType
75
from stac_fastapi.types.stac import Item
86

97

@@ -114,34 +112,3 @@ def dict_deep_update(merge_to: Dict[str, Any], merge_from: Dict[str, Any]) -> No
114112
dict_deep_update(merge_to[k], merge_from[k])
115113
else:
116114
merge_to[k] = v
117-
118-
119-
def format_datetime_range(dt_range: Union[DateTimeType, str]) -> str:
120-
"""
121-
Convert a datetime object or a tuple of datetime objects to a formatted string for datetime ranges.
122-
123-
Args:
124-
dt_range (DateTimeType): The date interval,
125-
which might be a single datetime or a tuple with one or two datetimes.
126-
127-
Returns:
128-
str: A formatted string like 'YYYY-MM-DDTHH:MM:SSZ/..', 'YYYY-MM-DDTHH:MM:SSZ', or the original string input.
129-
"""
130-
# Handle a single datetime object
131-
if isinstance(dt_range, datetime):
132-
return dt_range.isoformat().replace("+00:00", "Z")
133-
134-
# Handle a tuple containing datetime objects or None
135-
elif isinstance(dt_range, tuple):
136-
start, end = dt_range
137-
138-
# Convert start datetime to string if not None, otherwise use ".."
139-
start_str = start.isoformat().replace("+00:00", "Z") if start else ".."
140-
141-
# Convert end datetime to string if not None, otherwise use ".."
142-
end_str = end.isoformat().replace("+00:00", "Z") if end else ".."
143-
144-
return f"{start_str}/{end_str}"
145-
146-
# Return input as-is if it's not any expected type (fallback)
147-
return dt_range

tests/api/test_api.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ async def get_collection(
738738
)
739739

740740
api = StacApi(
741-
client=Client(post_request_model=post_request_model),
741+
client=Client(pgstac_search_model=post_request_model),
742742
settings=settings,
743743
extensions=extensions,
744744
search_post_request_model=post_request_model,
@@ -794,7 +794,7 @@ async def test_no_extension(
794794
extensions = []
795795
post_request_model = create_post_request_model(extensions, base_model=PgstacSearch)
796796
api = StacApi(
797-
client=CoreCrudClient(post_request_model=post_request_model),
797+
client=CoreCrudClient(pgstac_search_model=post_request_model),
798798
settings=settings,
799799
extensions=extensions,
800800
search_post_request_model=post_request_model,

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def api_client(request, database):
173173
api = StacApi(
174174
settings=api_settings,
175175
extensions=extensions + [collection_search_extension],
176-
client=CoreCrudClient(post_request_model=search_post_request_model),
176+
client=CoreCrudClient(pgstac_search_model=search_post_request_model),
177177
items_get_request_model=items_get_request_model,
178178
search_get_request_model=search_get_request_model,
179179
search_post_request_model=search_post_request_model,

0 commit comments

Comments
 (0)