Skip to content

Commit 9163c6b

Browse files
update stac-fastapi to next major version (#108)
* update stac-fastapi to next major version * update transaction endpoint * fix 201 errors * 201 in conftest * use latest stac-fastapi commit * Update tests (#111) * update test item tests * test collection tests * lint * test postgres * test api * more * update item * revert * remove hack * Update stac_fastapi/pgstac/core.py Co-authored-by: Vincent Sarago <[email protected]> * fix types --------- Co-authored-by: Vincent Sarago <[email protected]> * update and remove deprecated * update deprecated methods * remove context extension * update changelog --------- Co-authored-by: jonhealy1 <[email protected]>
1 parent b6c3410 commit 9163c6b

18 files changed

+422
-330
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ repos:
2121
additional_dependencies:
2222
- types-requests
2323
- types-attrs
24-
- pydantic~=1.10
24+
- pydantic~=2.0

CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
### Changed
66

7-
- update pgstac version to `0.8.x`
7+
- Update stac-fastapi libraries to v3.0.0a0 ([#108](https://github.com/stac-utils/stac-fastapi-pgstac/pull/108))
8+
- Update pgstac version to `0.8.x`
89

910
## [2.5.0] - 2024-04-25
1011

setup.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
install_requires = [
99
"attrs",
1010
"orjson",
11-
"pydantic[dotenv]>=1.10.8", # https://github.com/pydantic/pydantic/issues/5821
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",
11+
"pydantic",
12+
"stac_pydantic==3.0.*",
13+
"stac-fastapi.api~=3.0.0a0",
14+
"stac-fastapi.extensions~=3.0.0a0",
15+
"stac-fastapi.types~=3.0.0a0",
1616
"asyncpg",
1717
"buildpg",
1818
"brotli_asgi",

stac_fastapi/pgstac/app.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from stac_fastapi.api.app import StacApi
1212
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
1313
from stac_fastapi.extensions.core import (
14-
ContextExtension,
1514
FieldsExtension,
1615
FilterExtension,
1716
SortExtension,
@@ -39,7 +38,6 @@
3938
"sort": SortExtension(),
4039
"fields": FieldsExtension(),
4140
"pagination": TokenPaginationExtension(),
42-
"context": ContextExtension(),
4341
"filter": FilterExtension(client=FiltersClient()),
4442
"bulk_transactions": BulkTransactionExtension(client=BulkTransactionsClient()),
4543
}

stac_fastapi/pgstac/config.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from typing import List, Type
44
from urllib.parse import quote
55

6-
from pydantic import BaseModel, Extra
6+
from pydantic import BaseModel
7+
from pydantic_settings import SettingsConfigDict
78
from stac_fastapi.types.config import ApiSettings
89

910
from stac_fastapi.pgstac.types.base_item_cache import (
@@ -33,12 +34,14 @@
3334
]
3435

3536

36-
class ServerSettings(BaseModel, extra=Extra.allow):
37+
class ServerSettings(BaseModel):
3738
"""Server runtime parameters."""
3839

3940
search_path: str = "pgstac,public"
4041
application_name: str = "pgstac"
4142

43+
model_config = SettingsConfigDict(extra="allow")
44+
4245

4346
class Settings(ApiSettings):
4447
"""Postgres-specific API settings.
@@ -58,7 +61,7 @@ class Settings(ApiSettings):
5861
postgres_pass: str
5962
postgres_host_reader: str
6063
postgres_host_writer: str
61-
postgres_port: str
64+
postgres_port: int
6265
postgres_dbname: str
6366

6467
db_min_conn_size: int = 10
@@ -89,7 +92,6 @@ def testing_connection_string(self):
8992
"""Create testing psql connection string."""
9093
return f"postgresql://{self.postgres_user}:{quote(self.postgres_pass)}@{self.postgres_host_writer}:{self.postgres_port}/pgstactestdb"
9194

92-
class Config(ApiSettings.Config):
93-
"""Model config."""
94-
95-
env_nested_delimiter = "__"
95+
model_config = SettingsConfigDict(
96+
**{**ApiSettings.model_config, **{"env_nested_delimiter": "__"}}
97+
)

stac_fastapi/pgstac/core.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ async def _search_base( # noqa: C901
162162
search_request.conf = search_request.conf or {}
163163
search_request.conf["nohydrate"] = settings.use_api_hydrate
164164

165-
search_request_json = search_request.json(exclude_none=True, by_alias=True)
165+
search_request_json = search_request.model_dump_json(
166+
exclude_none=True, by_alias=True
167+
)
166168

167169
try:
168170
async with request.app.state.get_connection(request, "r") as conn:
@@ -277,6 +279,9 @@ async def item_collection(
277279
# If collection does not exist, NotFoundError wil be raised
278280
await self.get_collection(collection_id, request=request)
279281

282+
if datetime:
283+
datetime = format_datetime_range(datetime)
284+
280285
base_args = {
281286
"collections": [collection_id],
282287
"bbox": bbox,
@@ -393,7 +398,7 @@ async def get_search( # noqa: C901
393398
base_args["filter-lang"] = "cql2-json"
394399

395400
if datetime:
396-
base_args["datetime"] = datetime
401+
base_args["datetime"] = format_datetime_range(datetime)
397402

398403
if intersects:
399404
base_args["intersects"] = orjson.loads(unquote_plus(intersects))

stac_fastapi/pgstac/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,6 @@ async def create_pool(self, connection_string: str, settings):
142142
max_queries=settings.db_max_queries,
143143
max_inactive_connection_lifetime=settings.db_max_inactive_conn_lifetime,
144144
init=con_init,
145-
server_settings=settings.server_settings.dict(),
145+
server_settings=settings.server_settings.model_dump(),
146146
)
147147
return pool

stac_fastapi/pgstac/extensions/filter.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Get Queryables."""
22

3-
from typing import Any, Optional
3+
from typing import Any, Dict, Optional
44

55
from buildpg import render
66
from fastapi import Request
7-
from fastapi.responses import JSONResponse
87
from stac_fastapi.types.core import AsyncBaseFiltersClient
98
from stac_fastapi.types.errors import NotFoundError
109

@@ -13,8 +12,11 @@ class FiltersClient(AsyncBaseFiltersClient):
1312
"""Defines a pattern for implementing the STAC filter extension."""
1413

1514
async def get_queryables(
16-
self, request: Request, collection_id: Optional[str] = None, **kwargs: Any
17-
) -> JSONResponse:
15+
self,
16+
request: Request,
17+
collection_id: Optional[str] = None,
18+
**kwargs: Any,
19+
) -> Dict[str, Any]:
1820
"""Get the queryables available for the given collection_id.
1921
2022
If collection_id is None, returns the intersection of all
@@ -37,5 +39,4 @@ async def get_queryables(
3739
raise NotFoundError(f"Collection {collection_id} not found")
3840

3941
queryables["$id"] = str(request.url)
40-
headers = {"Content-Type": "application/schema+json"}
41-
return JSONResponse(queryables, headers=headers)
42+
return queryables

stac_fastapi/pgstac/extensions/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def operator(self) -> Callable[[Any, Any], bool]:
3434
class QueryExtensionPostRequest(BaseModel):
3535
"""Query Extension POST request model."""
3636

37-
query: Optional[Dict[str, Dict[Operator, Any]]]
37+
query: Optional[Dict[str, Dict[Operator, Any]]] = None
3838

3939

4040
class QueryExtension(QueryExtensionBase):

stac_fastapi/pgstac/transactions.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from stac_fastapi.types import stac as stac_types
1616
from stac_fastapi.types.core import AsyncBaseTransactionsClient
17+
from stac_pydantic import Collection, Item, ItemCollection
1718
from starlette.responses import JSONResponse, Response
1819

1920
from stac_fastapi.pgstac.config import Settings
@@ -69,11 +70,13 @@ def _validate_item(
6970
async def create_item(
7071
self,
7172
collection_id: str,
72-
item: Union[stac_types.Item, stac_types.ItemCollection],
73+
item: Union[Item, ItemCollection],
7374
request: Request,
7475
**kwargs,
7576
) -> Optional[Union[stac_types.Item, Response]]:
7677
"""Create item."""
78+
item = item.model_dump(mode="json")
79+
7780
if item["type"] == "FeatureCollection":
7881
valid_items = []
7982
for item in item["features"]: # noqa: B020
@@ -100,6 +103,7 @@ async def create_item(
100103
).get_links(extra_links=item.get("links"))
101104

102105
return stac_types.Item(**item)
106+
103107
else:
104108
raise HTTPException(
105109
status_code=400,
@@ -111,10 +115,12 @@ async def update_item(
111115
request: Request,
112116
collection_id: str,
113117
item_id: str,
114-
item: stac_types.Item,
118+
item: Item,
115119
**kwargs,
116120
) -> Optional[Union[stac_types.Item, Response]]:
117121
"""Update item."""
122+
item = item.model_dump(mode="json")
123+
118124
self._validate_item(request, item, collection_id, item_id)
119125
item["collection"] = collection_id
120126

@@ -130,31 +136,50 @@ async def update_item(
130136
return stac_types.Item(**item)
131137

132138
async def create_collection(
133-
self, collection: stac_types.Collection, request: Request, **kwargs
139+
self,
140+
collection: Collection,
141+
request: Request,
142+
**kwargs,
134143
) -> Optional[Union[stac_types.Collection, Response]]:
135144
"""Create collection."""
145+
collection = collection.model_dump(mode="json")
146+
136147
self._validate_collection(request, collection)
148+
137149
async with request.app.state.get_connection(request, "w") as conn:
138150
await dbfunc(conn, "create_collection", collection)
151+
139152
collection["links"] = await CollectionLinks(
140153
collection_id=collection["id"], request=request
141-
).get_links(extra_links=collection.get("links"))
154+
).get_links(extra_links=collection["links"])
142155

143156
return stac_types.Collection(**collection)
144157

145158
async def update_collection(
146-
self, collection: stac_types.Collection, request: Request, **kwargs
159+
self,
160+
collection: Collection,
161+
request: Request,
162+
**kwargs,
147163
) -> Optional[Union[stac_types.Collection, Response]]:
148164
"""Update collection."""
165+
166+
col = collection.model_dump(mode="json")
167+
149168
async with request.app.state.get_connection(request, "w") as conn:
150-
await dbfunc(conn, "update_collection", collection)
151-
collection["links"] = await CollectionLinks(
152-
collection_id=collection["id"], request=request
153-
).get_links(extra_links=collection.get("links"))
154-
return stac_types.Collection(**collection)
169+
await dbfunc(conn, "update_collection", col)
170+
171+
col["links"] = await CollectionLinks(
172+
collection_id=col["id"], request=request
173+
).get_links(extra_links=col.get("links"))
174+
175+
return stac_types.Collection(**col)
155176

156177
async def delete_item(
157-
self, item_id: str, collection_id: str, request: Request, **kwargs
178+
self,
179+
item_id: str,
180+
collection_id: str,
181+
request: Request,
182+
**kwargs,
158183
) -> Optional[Union[stac_types.Item, Response]]:
159184
"""Delete item."""
160185
q, p = render(
@@ -164,6 +189,7 @@ async def delete_item(
164189
)
165190
async with request.app.state.get_connection(request, "w") as conn:
166191
await conn.fetchval(q, *p)
192+
167193
return JSONResponse({"deleted item": item_id})
168194

169195
async def delete_collection(
@@ -172,6 +198,7 @@ async def delete_collection(
172198
"""Delete collection."""
173199
async with request.app.state.get_connection(request, "w") as conn:
174200
await dbfunc(conn, "delete_collection", collection_id)
201+
175202
return JSONResponse({"deleted collection": collection_id})
176203

177204

stac_fastapi/pgstac/types/search.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Dict, Optional
44

5-
from pydantic import validator
5+
from pydantic import ValidationInfo, field_validator
66
from stac_fastapi.types.search import BaseSearchPostRequest
77

88

@@ -14,10 +14,11 @@ class PgstacSearch(BaseSearchPostRequest):
1414

1515
conf: Optional[Dict] = None
1616

17-
@validator("filter_lang", pre=False, check_fields=False, always=True)
18-
def validate_query_uses_cql(cls, v, values):
17+
@field_validator("filter_lang", check_fields=False)
18+
@classmethod
19+
def validate_query_uses_cql(cls, v: str, info: ValidationInfo):
1920
"""Use of Query Extension is not allowed with cql2."""
20-
if values.get("query", None) is not None and v != "cql-json":
21+
if info.data.get("query", None) is not None and v != "cql-json":
2122
raise ValueError(
2223
"Query extension is not available when using pgstac with cql2"
2324
)

stac_fastapi/pgstac/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def dict_deep_update(merge_to: Dict[str, Any], merge_from: Dict[str, Any]) -> No
116116
merge_to[k] = v
117117

118118

119-
def format_datetime_range(dt_range: DateTimeType) -> Union[str, Any]:
119+
def format_datetime_range(dt_range: Union[DateTimeType, str]) -> str:
120120
"""
121121
Convert a datetime object or a tuple of datetime objects to a formatted string for datetime ranges.
122122
@@ -132,7 +132,7 @@ def format_datetime_range(dt_range: DateTimeType) -> Union[str, Any]:
132132
return dt_range.isoformat().replace("+00:00", "Z")
133133

134134
# Handle a tuple containing datetime objects or None
135-
if isinstance(dt_range, tuple):
135+
elif isinstance(dt_range, tuple):
136136
start, end = dt_range
137137

138138
# Convert start datetime to string if not None, otherwise use ".."

0 commit comments

Comments
 (0)