Skip to content

Commit d79b0e3

Browse files
update pcstac to use stac-fastapi 5.0 and stac-fastapi-pgstac 4.0 (#278)
* update pcstac to use stac-fastapi 5.0 and stac-fastapi-pgstac 4.0 * revert format * update stac-api-validator version * upgrade pypgstac * upgrade nginx and postgres images * remove defunct link * try with latest version * debug log * avoid calling all_collections on landing page --------- Co-authored-by: Gustavo Hidalgo <[email protected]>
1 parent 956963c commit d79b0e3

File tree

15 files changed

+211
-128
lines changed

15 files changed

+211
-128
lines changed

.github/workflows/pr.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
validate:
2020
runs-on: ubuntu-latest
2121
steps:
22-
- uses: actions/checkout@v3
23-
- uses: actions/setup-python@v4
22+
- uses: actions/checkout@v5
23+
- uses: actions/setup-python@v6
2424
with:
25-
python-version: "3.10" # stac-api-validator requires >= 3.10
25+
python-version: "3.13" # stac-api-validator requires >= 3.10
2626
cache: "pip"
2727

2828
- name: API Validator

docker-compose.yml

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
services:
2-
32
stac:
43
platform: linux/amd64
54
image: pc-apis-stac
@@ -17,7 +16,7 @@ services:
1716
- azurite
1817
- redis
1918
command: >
20-
bash -c "pypgstac pgready && uvicorn pcstac.main:app --host 0.0.0.0 --port 8081 --reload --proxy-headers --root-path '/stac'"
19+
bash -c "pypgstac pgready && uvicorn pcstac.main:app --host 0.0.0.0 --port 8081 --reload --proxy-headers --root-path /stac"
2120
2221
tiler:
2322
image: pc-apis-tiler
@@ -29,7 +28,7 @@ services:
2928
dockerfile: pctiler/Dockerfile
3029
env_file: ${PC_TILER_ENV_FILE:-./pc-tiler.dev.env}
3130
environment:
32-
# Allow proxied managed identity requests in dev
31+
# Allow proxied managed identity requests in dev
3332
- IDENTITY_ENDPOINT=http://token-proxy:8086/dev/token
3433
- IMDS_ENDPOINT=active
3534
ports:
@@ -40,7 +39,19 @@ services:
4039
- ./pccommon:/opt/src/pccommon
4140
depends_on:
4241
- database
43-
command: [ "uvicorn", "pctiler.main:app", "--host", "0.0.0.0", "--port", "8082", "--reload", "--proxy-headers", "--root-path", "/data" ]
42+
command:
43+
[
44+
"uvicorn",
45+
"pctiler.main:app",
46+
"--host",
47+
"0.0.0.0",
48+
"--port",
49+
"8082",
50+
"--reload",
51+
"--proxy-headers",
52+
"--root-path",
53+
"/data",
54+
]
4455

4556
funcs:
4657
image: pc-apis-funcs
@@ -58,11 +69,8 @@ services:
5869
- ~/.azure:/home/.azure
5970

6071
nginx:
61-
image: pc-apis-nginx
72+
image: nginx:1.29.4
6273
container_name: pc-apis-nginx
63-
build:
64-
context: ./nginx
65-
dockerfile: Dockerfile
6674
links:
6775
- database
6876
- azurite
@@ -78,14 +86,16 @@ services:
7886

7987
database:
8088
container_name: pc-stac-db
81-
image: pc-apis-stac-db
82-
build:
83-
context: ./pgstac
84-
dockerfile: Dockerfile
89+
platform: linux/amd64
90+
image: postgis/postgis:17-3.5
8591
environment:
8692
- POSTGRES_USER=username
8793
- POSTGRES_PASSWORD=password
8894
- POSTGRES_DB=postgis
95+
- POSTGIS_MAJOR=3
96+
- PGUSER=postgres
97+
- PGDATABASE=postgres
98+
- PGHOST=localhost
8999
ports:
90100
- "5432:5432"
91101
volumes:
@@ -106,7 +116,8 @@ services:
106116
container_name: pcapis-azurite
107117
image: mcr.microsoft.com/azure-storage/azurite:3.35.0
108118
hostname: azurite
109-
command: "azurite --silent --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost
119+
command:
120+
"azurite --silent --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost
110121
0.0.0.0 -l /workspace"
111122
ports:
112123
- "10000:10000" # Blob

nginx/Dockerfile

Lines changed: 0 additions & 4 deletions
This file was deleted.

pc-stac.dev.env

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
APP_ROOT_PATH=/stac
21
APP_HOST=0.0.0.0
32
APP_PORT=8081
43
FORWARDED_ALLOW_IPS=*

pcstac/pcstac/api.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

pcstac/pcstac/client.py

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
from fastapi import HTTPException, Request
99
from stac_fastapi.pgstac.core import CoreCrudClient
1010
from stac_fastapi.types.errors import NotFoundError
11+
from stac_fastapi.types.requests import get_base_url
1112
from stac_fastapi.types.stac import (
1213
Collection,
1314
Collections,
1415
Item,
1516
ItemCollection,
1617
LandingPage,
1718
)
19+
from stac_pydantic.links import Relations
20+
from stac_pydantic.shared import MimeTypes
1821

1922
from pccommon.config import get_all_render_configs, get_render_config
2023
from pccommon.config.collections import DefaultRenderConfig
@@ -220,7 +223,7 @@ async def _fetch() -> ItemCollection:
220223
search_request.collections is None
221224
and "collection=" not in str(request.url)
222225
and '{"property":"collection"}'
223-
not in orjson.dumps(search_request.filter).decode("utf-8")
226+
not in orjson.dumps(search_request.filter_expr).decode("utf-8")
224227
):
225228
raise HTTPException(status_code=422, detail="collection is required")
226229

@@ -238,11 +241,87 @@ async def _fetch() -> ItemCollection:
238241
return await cached_result(_fetch, cache_key, request)
239242

240243
async def landing_page(self, request: Request, **kwargs: Any) -> LandingPage:
241-
_super: CoreCrudClient = super()
244+
"""Landing page."""
242245

243246
async def _fetch() -> LandingPage:
244-
landing = await _super.landing_page(request=request, **kwargs)
245-
return landing
247+
"""Landing page.
248+
249+
NOTE: we need a custom landing page implementation
250+
to avoid the call to `all_collections` method.
251+
252+
TODO: replace this with:
253+
```
254+
_super: CoreCrudClient = super()
255+
256+
async def _fetch() -> LandingPage:
257+
landing = await _super.landing_page(request=request, **kwargs)
258+
return landing
259+
```
260+
when switching to stac-fastapi >=v5.1.
261+
262+
"""
263+
base_url = get_base_url(request)
264+
265+
landing_page = self._landing_page(
266+
base_url=base_url,
267+
conformance_classes=self.conformance_classes(),
268+
extension_schemas=[],
269+
)
270+
271+
# Add Queryables link
272+
if self.extension_is_enabled(
273+
"FilterExtension"
274+
) or self.extension_is_enabled("SearchFilterExtension"):
275+
landing_page["links"].append(
276+
{
277+
"rel": Relations.queryables.value,
278+
"type": MimeTypes.jsonschema.value,
279+
"title": "Queryables available for this Catalog",
280+
"href": urljoin(base_url, "queryables"),
281+
"method": "GET",
282+
}
283+
)
284+
285+
# Add Aggregation links
286+
if self.extension_is_enabled("AggregationExtension"):
287+
landing_page["links"].extend(
288+
[
289+
{
290+
"rel": "aggregate",
291+
"type": "application/json",
292+
"title": "Aggregate",
293+
"href": urljoin(base_url, "aggregate"),
294+
},
295+
{
296+
"rel": "aggregations",
297+
"type": "application/json",
298+
"title": "Aggregations",
299+
"href": urljoin(base_url, "aggregations"),
300+
},
301+
]
302+
)
303+
304+
# Add OpenAPI URL
305+
landing_page["links"].append(
306+
{
307+
"rel": Relations.service_desc.value,
308+
"type": MimeTypes.openapi.value,
309+
"title": "OpenAPI service description",
310+
"href": str(request.url_for("openapi")),
311+
}
312+
)
313+
314+
# Add human readable service-doc
315+
landing_page["links"].append(
316+
{
317+
"rel": Relations.service_doc.value,
318+
"type": MimeTypes.html.value,
319+
"title": "OpenAPI service documentation",
320+
"href": str(request.url_for("swagger_ui_html")),
321+
}
322+
)
323+
324+
return LandingPage(**landing_page)
246325

247326
return await cached_result(_fetch, CACHE_KEY_LANDING_PAGE, request)
248327

@@ -303,6 +382,6 @@ def create(
303382
title=API_TITLE,
304383
description=API_DESCRIPTION,
305384
extra_conformance_classes=extra_conformance_classes,
306-
post_request_model=post_request_model,
385+
pgstac_search_model=post_request_model,
307386
)
308387
return it

pcstac/pcstac/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
client=PCFiltersClient(),
4141
conformance_classes=[
4242
FilterConformanceClasses.FILTER,
43-
FilterConformanceClasses.ITEM_SEARCH_FILTER,
43+
FilterConformanceClasses.SEARCH,
4444
FilterConformanceClasses.BASIC_CQL2,
4545
FilterConformanceClasses.CQL2_JSON,
4646
FilterConformanceClasses.CQL2_TEXT,

pcstac/pcstac/main.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from fastapi.exceptions import RequestValidationError, StarletteHTTPException
1111
from fastapi.openapi.utils import get_openapi
1212
from fastapi.responses import ORJSONResponse
13+
from stac_fastapi.api.app import StacApi as PCStacApi
1314
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES
1415
from stac_fastapi.api.middleware import ProxyHeaderMiddleware
1516
from stac_fastapi.api.models import (
@@ -29,13 +30,13 @@
2930
from pccommon.middleware import TraceMiddleware, add_timeout, http_exception_handler
3031
from pccommon.openapi import fixup_schema
3132
from pccommon.redis import connect_to_redis
32-
from pcstac.api import PCStacApi
3333
from pcstac.client import PCClient
3434
from pcstac.config import (
3535
API_DESCRIPTION,
3636
API_TITLE,
3737
API_VERSION,
3838
EXTENSIONS,
39+
STAC_API_VERSION,
3940
get_settings,
4041
)
4142
from pcstac.errors import PC_DEFAULT_STATUS_CODES
@@ -92,6 +93,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator:
9293
db_min_conn_size=app_settings.db_min_conn_size,
9394
base_item_cache=RedisBaseItemCache,
9495
debug=DEBUG,
96+
root_path=APP_ROOT_PATH,
9597
),
9698
client=PCClient.create(post_request_model=search_post_request_model),
9799
extensions=EXTENSIONS,
@@ -148,9 +150,15 @@ def custom_openapi() -> Dict[str, Any]:
148150
return app.openapi_schema
149151
else:
150152
schema = get_openapi(
151-
title="Planetary Computer STAC API",
153+
title=API_TITLE,
152154
version=app_settings.api_version,
153155
routes=app.routes,
154156
)
155-
app.openapi_schema = fixup_schema(app.root_path, schema)
156-
return schema
157+
fixed_schema = fixup_schema(
158+
app.root_path, schema, tag=f"STAC API {STAC_API_VERSION}"
159+
)
160+
app.openapi_schema = fixed_schema
161+
return fixed_schema
162+
163+
164+
app.openapi = custom_openapi # type: ignore

pcstac/pcstac/search.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from stac_fastapi.api.models import BaseSearchGetRequest, ItemCollectionUri
99
from stac_fastapi.pgstac.types.base_item_cache import BaseItemCache
1010
from stac_fastapi.pgstac.types.search import PgstacSearch
11-
from stac_fastapi.types.rfc3339 import DateTimeType, str_to_interval
11+
from stac_fastapi.types.search import DateTimeQueryType, Limit, _validate_datetime
1212
from starlette.requests import Request
1313
from typing_extensions import Annotated
1414

@@ -71,21 +71,31 @@ async def _fetch() -> Dict[str, Any]:
7171

7272
@attr.s
7373
class PCItemCollectionUri(ItemCollectionUri):
74-
limit: Annotated[Optional[int], Query()] = attr.ib(
75-
default=LEGACY_ITEM_DEFAULT_LIMIT
76-
)
74+
limit: Annotated[
75+
Optional[Limit],
76+
Query(
77+
description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501
78+
),
79+
] = attr.ib(default=LEGACY_ITEM_DEFAULT_LIMIT)
7780

7881

79-
def patch_and_convert(interval: Optional[str]) -> Optional[DateTimeType]:
82+
def patch_and_convert(value: Optional[str]) -> Optional[str]:
8083
"""Patch datetime to add hh-mm-ss and timezone info."""
81-
if interval:
82-
interval = _patch_datetime(interval)
83-
return str_to_interval(interval)
84+
if value:
85+
value = _patch_datetime(value)
86+
return value
8487

8588

8689
@attr.s
8790
class PCSearchGetRequest(BaseSearchGetRequest):
88-
datetime: Annotated[Optional[DateTimeType], Query()] = attr.ib(
89-
default=None, converter=patch_and_convert
91+
datetime: DateTimeQueryType = attr.ib(
92+
default=None,
93+
converter=patch_and_convert,
94+
validator=_validate_datetime,
9095
)
91-
limit: Annotated[Optional[int], Query()] = attr.ib(default=DEFAULT_LIMIT)
96+
limit: Annotated[
97+
Optional[Limit],
98+
Query(
99+
description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501
100+
),
101+
] = attr.ib(default=DEFAULT_LIMIT)

pcstac/pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ requires-python = ">=3.7"
1111
dependencies = [
1212
"idna>=3.7.0",
1313
"orjson==3.10.4",
14-
"pypgstac[psycopg]>=0.8.5,<0.9",
14+
"pypgstac[psycopg]==0.9.6",
1515
"pystac==1.10.1",
16-
"stac-fastapi.api==3.0.0b2",
17-
"stac-fastapi.extensions==3.0.0b2",
18-
"stac-fastapi.pgstac==3.0.0a4",
19-
"stac-fastapi.types==3.0.0b2",
16+
"stac-fastapi.api==5.0.3",
17+
"stac-fastapi.extensions==5.0.3",
18+
"stac-fastapi.pgstac==4.0.4",
19+
"stac-fastapi.types==5.0.3",
2020
"typing_extensions>=4.6.1",
2121
"urllib3>=2.2.2",
2222
]

0 commit comments

Comments
 (0)