diff --git a/CHANGES.md b/CHANGES.md index 3fc8b416..7fe92ebd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Updated stac-fastapi libraries to v2.5.4 ([#101](https://github.com/stac-utils/stac-fastapi-pgstac/pull/101)) +- ### Added - Ability to configure the database runtime parameters ([#92](https://github.com/stac-utils/stac-fastapi-pgstac/pull/92)) diff --git a/scripts/ingest_joplin.py b/scripts/ingest_joplin.py index 6d273217..0440d094 100644 --- a/scripts/ingest_joplin.py +++ b/scripts/ingest_joplin.py @@ -19,7 +19,7 @@ def post_or_put(url: str, data: dict): """Post or put data to url.""" r = requests.post(url, json=data) if r.status_code == 409: - new_url = url if data["type"] == "Collection" else url + f"/{data['id']}" + new_url = url + f"/{data['id']}" # Exists, so update r = requests.put(new_url, json=data) # Unchanged may throw a 404 diff --git a/setup.py b/setup.py index f83fa4f4..af3669dc 100644 --- a/setup.py +++ b/setup.py @@ -10,9 +10,9 @@ "orjson", "pydantic[dotenv]>=1.10.8", # https://github.com/pydantic/pydantic/issues/5821 "stac_pydantic==2.0.*", - "stac-fastapi.types~=2.4.9", - "stac-fastapi.api~=2.4.9", - "stac-fastapi.extensions~=2.4.9", + "stac-fastapi.types~=2.5.5.post1", + "stac-fastapi.api~=2.5.5.post1", + "stac-fastapi.extensions~=2.5.5.post1", "asyncpg", "buildpg", "brotli_asgi", diff --git a/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/core.py index ef20f25d..e8cf1219 100644 --- a/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/core.py @@ -1,6 +1,5 @@ """Item crud client.""" import re -from datetime import datetime from typing import Any, Dict, List, Optional, Union from urllib.parse import unquote_plus, urljoin @@ -16,9 +15,10 @@ from stac_fastapi.types.core import AsyncBaseCoreClient from stac_fastapi.types.errors import InvalidQueryParameter, NotFoundError from stac_fastapi.types.requests import get_base_url +from stac_fastapi.types.rfc3339 import DateTimeType from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection from stac_pydantic.links import Relations -from stac_pydantic.shared import MimeTypes +from stac_pydantic.shared import BBox, MimeTypes from stac_fastapi.pgstac.config import Settings from stac_fastapi.pgstac.models.links import ( @@ -28,7 +28,7 @@ PagingLinks, ) from stac_fastapi.pgstac.types.search import PgstacSearch -from stac_fastapi.pgstac.utils import filter_fields +from stac_fastapi.pgstac.utils import filter_fields, format_datetime_range NumType = Union[float, int] @@ -155,8 +155,12 @@ async def _search_base( settings: Settings = request.app.state.settings + if search_request.datetime: + search_request.datetime = format_datetime_range(search_request.datetime) + search_request.conf = search_request.conf or {} search_request.conf["nohydrate"] = settings.use_api_hydrate + search_request_json = search_request.json(exclude_none=True, by_alias=True) try: @@ -248,8 +252,8 @@ async def item_collection( self, collection_id: str, request: Request, - bbox: Optional[List[NumType]] = None, - datetime: Optional[Union[str, datetime]] = None, + bbox: Optional[BBox] = None, + datetime: Optional[DateTimeType] = None, limit: Optional[int] = None, token: str = None, **kwargs, @@ -341,8 +345,8 @@ async def get_search( request: Request, collections: Optional[List[str]] = None, ids: Optional[List[str]] = None, - bbox: Optional[List[NumType]] = None, - datetime: Optional[Union[str, datetime]] = None, + bbox: Optional[BBox] = None, + datetime: Optional[DateTimeType] = None, limit: Optional[int] = None, query: Optional[str] = None, token: Optional[str] = None, diff --git a/stac_fastapi/pgstac/utils.py b/stac_fastapi/pgstac/utils.py index 4a0ce4c7..f696ca51 100644 --- a/stac_fastapi/pgstac/utils.py +++ b/stac_fastapi/pgstac/utils.py @@ -1,6 +1,8 @@ """stac-fastapi utility methods.""" +from datetime import datetime from typing import Any, Dict, Optional, Set, Union +from stac_fastapi.types.rfc3339 import DateTimeType from stac_fastapi.types.stac import Item @@ -113,3 +115,34 @@ def dict_deep_update(merge_to: Dict[str, Any], merge_from: Dict[str, Any]) -> No dict_deep_update(merge_to[k], merge_from[k]) else: merge_to[k] = v + + +def format_datetime_range(dt_range: DateTimeType) -> Union[str, Any]: + """ + Convert a datetime object or a tuple of datetime objects to a formatted string for datetime ranges. + + Args: + dt_range (DateTimeType): The date interval, + which might be a single datetime or a tuple with one or two datetimes. + + Returns: + str: A formatted string like 'YYYY-MM-DDTHH:MM:SSZ/..', 'YYYY-MM-DDTHH:MM:SSZ', or the original string input. + """ + # Handle a single datetime object + if isinstance(dt_range, datetime): + return dt_range.isoformat().replace("+00:00", "Z") + + # Handle a tuple containing datetime objects or None + if isinstance(dt_range, tuple): + start, end = dt_range + + # Convert start datetime to string if not None, otherwise use ".." + start_str = start.isoformat().replace("+00:00", "Z") if start else ".." + + # Convert end datetime to string if not None, otherwise use ".." + end_str = end.isoformat().replace("+00:00", "Z") if end else ".." + + return f"{start_str}/{end_str}" + + # Return input as-is if it's not any expected type (fallback) + return dt_range diff --git a/tests/api/test_api.py b/tests/api/test_api.py index 5dcc95d2..cdac75ff 100644 --- a/tests/api/test_api.py +++ b/tests/api/test_api.py @@ -33,7 +33,7 @@ "DELETE /collections/{collection_id}/items/{item_id}", "POST /collections", "POST /collections/{collection_id}/items", - "PUT /collections", + "PUT /collections/{collection_id}", "PUT /collections/{collection_id}/items/{item_id}", ] diff --git a/tests/clients/test_postgres.py b/tests/clients/test_postgres.py index 964cbaf6..0de8f4bb 100644 --- a/tests/clients/test_postgres.py +++ b/tests/clients/test_postgres.py @@ -34,7 +34,7 @@ async def test_update_collection(app_client, load_test_collection): in_coll = load_test_collection in_coll.keywords.append("newkeyword") - resp = await app_client.put("/collections", json=in_coll.dict()) + resp = await app_client.put(f"/collections/{in_coll.id}", json=in_coll.dict()) assert resp.status_code == 200 resp = await app_client.get(f"/collections/{in_coll.id}") diff --git a/tests/resources/test_collection.py b/tests/resources/test_collection.py index 6910f0fe..980428ef 100644 --- a/tests/resources/test_collection.py +++ b/tests/resources/test_collection.py @@ -32,7 +32,7 @@ async def test_update_collection(app_client, load_test_data, load_test_collectio in_coll = load_test_collection in_coll.keywords.append("newkeyword") - resp = await app_client.put("/collections", json=in_coll.dict()) + resp = await app_client.put(f"/collections/{in_coll.id}", json=in_coll.dict()) assert resp.status_code == 200 put_coll = Collection.parse_obj(resp.json()) @@ -88,7 +88,7 @@ async def test_update_new_collection(app_client, load_test_collection): in_coll = load_test_collection in_coll.id = "test-updatenew" - resp = await app_client.put("/collections", json=in_coll.dict()) + resp = await app_client.put(f"/collections/{in_coll.id}", json=in_coll.dict()) assert resp.status_code == 404 diff --git a/tests/resources/test_item.py b/tests/resources/test_item.py index 6929fbf7..5fd4b044 100644 --- a/tests/resources/test_item.py +++ b/tests/resources/test_item.py @@ -39,7 +39,7 @@ async def test_update_collection(app_client, load_test_data, load_test_collectio in_coll = load_test_collection in_coll.keywords.append("newkeyword") - resp = await app_client.put("/collections", json=in_coll.dict()) + resp = await app_client.put(f"/collections/{in_coll.id}", json=in_coll.dict()) assert resp.status_code == 200 resp = await app_client.get(f"/collections/{in_coll.id}")