Skip to content

Commit

Permalink
update stac-fastapi to next major version (#108)
Browse files Browse the repository at this point in the history
* 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 <vincent.sarago@gmail.com>

* fix types

---------

Co-authored-by: Vincent Sarago <vincent.sarago@gmail.com>

* update and remove deprecated

* update deprecated methods

* remove context extension

* update changelog

---------

Co-authored-by: jonhealy1 <jonathan.d.healy@gmail.com>
  • Loading branch information
vincentsarago and jonhealy1 authored May 7, 2024
1 parent b6c3410 commit 9163c6b
Show file tree
Hide file tree
Showing 18 changed files with 422 additions and 330 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ repos:
additional_dependencies:
- types-requests
- types-attrs
- pydantic~=1.10
- pydantic~=2.0
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

### Changed

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

## [2.5.0] - 2024-04-25

Expand Down
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
install_requires = [
"attrs",
"orjson",
"pydantic[dotenv]>=1.10.8", # https://github.com/pydantic/pydantic/issues/5821
"stac_pydantic==2.0.*",
"stac-fastapi.types~=2.5.5.post1",
"stac-fastapi.api~=2.5.5.post1",
"stac-fastapi.extensions~=2.5.5.post1",
"pydantic",
"stac_pydantic==3.0.*",
"stac-fastapi.api~=3.0.0a0",
"stac-fastapi.extensions~=3.0.0a0",
"stac-fastapi.types~=3.0.0a0",
"asyncpg",
"buildpg",
"brotli_asgi",
Expand Down
2 changes: 0 additions & 2 deletions stac_fastapi/pgstac/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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 (
ContextExtension,
FieldsExtension,
FilterExtension,
SortExtension,
Expand Down Expand Up @@ -39,7 +38,6 @@
"sort": SortExtension(),
"fields": FieldsExtension(),
"pagination": TokenPaginationExtension(),
"context": ContextExtension(),
"filter": FilterExtension(client=FiltersClient()),
"bulk_transactions": BulkTransactionExtension(client=BulkTransactionsClient()),
}
Expand Down
16 changes: 9 additions & 7 deletions stac_fastapi/pgstac/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from typing import List, Type
from urllib.parse import quote

from pydantic import BaseModel, Extra
from pydantic import BaseModel
from pydantic_settings import SettingsConfigDict
from stac_fastapi.types.config import ApiSettings

from stac_fastapi.pgstac.types.base_item_cache import (
Expand Down Expand Up @@ -33,12 +34,14 @@
]


class ServerSettings(BaseModel, extra=Extra.allow):
class ServerSettings(BaseModel):
"""Server runtime parameters."""

search_path: str = "pgstac,public"
application_name: str = "pgstac"

model_config = SettingsConfigDict(extra="allow")


class Settings(ApiSettings):
"""Postgres-specific API settings.
Expand All @@ -58,7 +61,7 @@ class Settings(ApiSettings):
postgres_pass: str
postgres_host_reader: str
postgres_host_writer: str
postgres_port: str
postgres_port: int
postgres_dbname: str

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

class Config(ApiSettings.Config):
"""Model config."""

env_nested_delimiter = "__"
model_config = SettingsConfigDict(
**{**ApiSettings.model_config, **{"env_nested_delimiter": "__"}}
)
9 changes: 7 additions & 2 deletions stac_fastapi/pgstac/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ async def _search_base( # noqa: C901
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)
search_request_json = search_request.model_dump_json(
exclude_none=True, by_alias=True
)

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

if datetime:
datetime = format_datetime_range(datetime)

base_args = {
"collections": [collection_id],
"bbox": bbox,
Expand Down Expand Up @@ -393,7 +398,7 @@ async def get_search( # noqa: C901
base_args["filter-lang"] = "cql2-json"

if datetime:
base_args["datetime"] = datetime
base_args["datetime"] = format_datetime_range(datetime)

if intersects:
base_args["intersects"] = orjson.loads(unquote_plus(intersects))
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/pgstac/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,6 @@ async def create_pool(self, connection_string: str, settings):
max_queries=settings.db_max_queries,
max_inactive_connection_lifetime=settings.db_max_inactive_conn_lifetime,
init=con_init,
server_settings=settings.server_settings.dict(),
server_settings=settings.server_settings.model_dump(),
)
return pool
13 changes: 7 additions & 6 deletions stac_fastapi/pgstac/extensions/filter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""Get Queryables."""

from typing import Any, Optional
from typing import Any, Dict, Optional

from buildpg import render
from fastapi import Request
from fastapi.responses import JSONResponse
from stac_fastapi.types.core import AsyncBaseFiltersClient
from stac_fastapi.types.errors import NotFoundError

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

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

queryables["$id"] = str(request.url)
headers = {"Content-Type": "application/schema+json"}
return JSONResponse(queryables, headers=headers)
return queryables
2 changes: 1 addition & 1 deletion stac_fastapi/pgstac/extensions/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def operator(self) -> Callable[[Any, Any], bool]:
class QueryExtensionPostRequest(BaseModel):
"""Query Extension POST request model."""

query: Optional[Dict[str, Dict[Operator, Any]]]
query: Optional[Dict[str, Dict[Operator, Any]]] = None


class QueryExtension(QueryExtensionBase):
Expand Down
49 changes: 38 additions & 11 deletions stac_fastapi/pgstac/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)
from stac_fastapi.types import stac as stac_types
from stac_fastapi.types.core import AsyncBaseTransactionsClient
from stac_pydantic import Collection, Item, ItemCollection
from starlette.responses import JSONResponse, Response

from stac_fastapi.pgstac.config import Settings
Expand Down Expand Up @@ -69,11 +70,13 @@ def _validate_item(
async def create_item(
self,
collection_id: str,
item: Union[stac_types.Item, stac_types.ItemCollection],
item: Union[Item, ItemCollection],
request: Request,
**kwargs,
) -> Optional[Union[stac_types.Item, Response]]:
"""Create item."""
item = item.model_dump(mode="json")

if item["type"] == "FeatureCollection":
valid_items = []
for item in item["features"]: # noqa: B020
Expand All @@ -100,6 +103,7 @@ async def create_item(
).get_links(extra_links=item.get("links"))

return stac_types.Item(**item)

else:
raise HTTPException(
status_code=400,
Expand All @@ -111,10 +115,12 @@ async def update_item(
request: Request,
collection_id: str,
item_id: str,
item: stac_types.Item,
item: Item,
**kwargs,
) -> Optional[Union[stac_types.Item, Response]]:
"""Update item."""
item = item.model_dump(mode="json")

self._validate_item(request, item, collection_id, item_id)
item["collection"] = collection_id

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

async def create_collection(
self, collection: stac_types.Collection, request: Request, **kwargs
self,
collection: Collection,
request: Request,
**kwargs,
) -> Optional[Union[stac_types.Collection, Response]]:
"""Create collection."""
collection = collection.model_dump(mode="json")

self._validate_collection(request, collection)

async with request.app.state.get_connection(request, "w") as conn:
await dbfunc(conn, "create_collection", collection)

collection["links"] = await CollectionLinks(
collection_id=collection["id"], request=request
).get_links(extra_links=collection.get("links"))
).get_links(extra_links=collection["links"])

return stac_types.Collection(**collection)

async def update_collection(
self, collection: stac_types.Collection, request: Request, **kwargs
self,
collection: Collection,
request: Request,
**kwargs,
) -> Optional[Union[stac_types.Collection, Response]]:
"""Update collection."""

col = collection.model_dump(mode="json")

async with request.app.state.get_connection(request, "w") as conn:
await dbfunc(conn, "update_collection", collection)
collection["links"] = await CollectionLinks(
collection_id=collection["id"], request=request
).get_links(extra_links=collection.get("links"))
return stac_types.Collection(**collection)
await dbfunc(conn, "update_collection", col)

col["links"] = await CollectionLinks(
collection_id=col["id"], request=request
).get_links(extra_links=col.get("links"))

return stac_types.Collection(**col)

async def delete_item(
self, item_id: str, collection_id: str, request: Request, **kwargs
self,
item_id: str,
collection_id: str,
request: Request,
**kwargs,
) -> Optional[Union[stac_types.Item, Response]]:
"""Delete item."""
q, p = render(
Expand All @@ -164,6 +189,7 @@ async def delete_item(
)
async with request.app.state.get_connection(request, "w") as conn:
await conn.fetchval(q, *p)

return JSONResponse({"deleted item": item_id})

async def delete_collection(
Expand All @@ -172,6 +198,7 @@ async def delete_collection(
"""Delete collection."""
async with request.app.state.get_connection(request, "w") as conn:
await dbfunc(conn, "delete_collection", collection_id)

return JSONResponse({"deleted collection": collection_id})


Expand Down
9 changes: 5 additions & 4 deletions stac_fastapi/pgstac/types/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Dict, Optional

from pydantic import validator
from pydantic import ValidationInfo, field_validator
from stac_fastapi.types.search import BaseSearchPostRequest


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

conf: Optional[Dict] = None

@validator("filter_lang", pre=False, check_fields=False, always=True)
def validate_query_uses_cql(cls, v, values):
@field_validator("filter_lang", check_fields=False)
@classmethod
def validate_query_uses_cql(cls, v: str, info: ValidationInfo):
"""Use of Query Extension is not allowed with cql2."""
if values.get("query", None) is not None and v != "cql-json":
if info.data.get("query", None) is not None and v != "cql-json":
raise ValueError(
"Query extension is not available when using pgstac with cql2"
)
Expand Down
4 changes: 2 additions & 2 deletions stac_fastapi/pgstac/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def dict_deep_update(merge_to: Dict[str, Any], merge_from: Dict[str, Any]) -> No
merge_to[k] = v


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

# Handle a tuple containing datetime objects or None
if isinstance(dt_range, tuple):
elif isinstance(dt_range, tuple):
start, end = dt_range

# Convert start datetime to string if not None, otherwise use ".."
Expand Down
Loading

0 comments on commit 9163c6b

Please sign in to comment.