diff --git a/CHANGES.md b/CHANGES.md index 2a4475635..ac177a6fc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +## UNRELEASED + +* Upgrade to stac-pydantic 2.0.0 and stac-spec 1.0.0 (https://github.com/stac-utils/stac-fastapi/pull/181) ## 1.1.0 (2021-01-28) diff --git a/docker-compose.yml b/docker-compose.yml index 322686321..17ac9d640 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,7 +69,7 @@ services: database: container_name: stac-db - image: bitner/pgstac:0.2.4 + image: bitner/pgstac:0.2.7 environment: - POSTGRES_USER=username - POSTGRES_PASSWORD=password @@ -92,7 +92,7 @@ services: - ./stac_fastapi:/app/stac_fastapi - ./scripts:/app/scripts command: > - bash -c "sleep 10 && cd stac_fastapi/sqlalchemy && alembic upgrade head && python /app/scripts/ingest_joplin.py http://app-sqlalchemy:8081" + bash -c "./scripts/wait-for-it.sh app-sqlalchemy:8081 && cd stac_fastapi/sqlalchemy && alembic upgrade head && python /app/scripts/ingest_joplin.py http://app-sqlalchemy:8081" depends_on: - database - app-sqlalchemy diff --git a/stac_fastapi/api/setup.py b/stac_fastapi/api/setup.py index 3f2a3ed3e..2c8ede0dd 100644 --- a/stac_fastapi/api/setup.py +++ b/stac_fastapi/api/setup.py @@ -9,11 +9,17 @@ "fastapi", "attrs", "pydantic[dotenv]", - "stac_pydantic==1.3.8", + "stac_pydantic==2.0.0", ] extra_reqs = { - "dev": ["pytest", "pytest-cov", "pytest-asyncio", "pre-commit", "requests"], + "dev": [ + "pytest", + "pytest-cov", + "pytest-asyncio", + "pre-commit", + "requests", + ], "docs": ["mkdocs", "mkdocs-material", "pdocs"], } diff --git a/stac_fastapi/api/stac_fastapi/api/app.py b/stac_fastapi/api/stac_fastapi/api/app.py index 48898ff8f..b59978e3a 100644 --- a/stac_fastapi/api/stac_fastapi/api/app.py +++ b/stac_fastapi/api/stac_fastapi/api/app.py @@ -7,6 +7,7 @@ from fastapi.openapi.utils import get_openapi from stac_pydantic import Collection, Item, ItemCollection from stac_pydantic.api import ConformanceClasses, LandingPage +from stac_pydantic.api.collections import Collections from stac_pydantic.version import STAC_VERSION from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers @@ -148,7 +149,7 @@ def register_core(self): router.add_api_route( name="Get Collections", path="/collections", - response_model=List[Collection], + response_model=Collections, response_model_exclude_unset=True, response_model_exclude_none=True, methods=["GET"], diff --git a/stac_fastapi/api/stac_fastapi/api/config.py b/stac_fastapi/api/stac_fastapi/api/config.py index a5f76615b..7677dab9c 100644 --- a/stac_fastapi/api/stac_fastapi/api/config.py +++ b/stac_fastapi/api/stac_fastapi/api/config.py @@ -3,6 +3,7 @@ # TODO: Move to stac-pydantic +# Does that make sense now? The shift to json schema rather than a well-known enumeration makes that less obvious. class ApiExtensions(enum.Enum): """Enumeration of available stac api extensions. diff --git a/stac_fastapi/api/stac_fastapi/api/errors.py b/stac_fastapi/api/stac_fastapi/api/errors.py index 7f9fdf0db..dd1880fca 100644 --- a/stac_fastapi/api/stac_fastapi/api/errors.py +++ b/stac_fastapi/api/stac_fastapi/api/errors.py @@ -67,7 +67,8 @@ def request_validation_exception_handler( request: Request, exc: RequestValidationError ) -> JSONResponse: return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, content={"detail": exc.errors()} + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": exc.errors()}, ) app.add_exception_handler( diff --git a/stac_fastapi/api/stac_fastapi/api/models.py b/stac_fastapi/api/stac_fastapi/api/models.py index 9a5785ea8..f44ae0b38 100644 --- a/stac_fastapi/api/stac_fastapi/api/models.py +++ b/stac_fastapi/api/stac_fastapi/api/models.py @@ -91,7 +91,11 @@ class ItemCollectionUri(CollectionUri): def kwargs(self) -> Dict: """kwargs.""" - return {"id": self.collectionId, "limit": self.limit, "token": self.token} + return { + "id": self.collectionId, + "limit": self.limit, + "token": self.token, + } @attr.s diff --git a/stac_fastapi/extensions/setup.py b/stac_fastapi/extensions/setup.py index 844f25e91..0ae789680 100644 --- a/stac_fastapi/extensions/setup.py +++ b/stac_fastapi/extensions/setup.py @@ -9,13 +9,19 @@ "fastapi", "attrs", "pydantic[dotenv]", - "stac_pydantic==1.3.8", + "stac_pydantic==2.0.0", "stac-fastapi.types", "stac-fastapi.api", ] extra_reqs = { - "dev": ["pytest", "pytest-cov", "pytest-asyncio", "pre-commit", "requests"], + "dev": [ + "pytest", + "pytest-cov", + "pytest-asyncio", + "pre-commit", + "requests", + ], "docs": ["mkdocs", "mkdocs-material", "pdocs"], "tiles": ["titiler==0.2.*"], } diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/third_party/tiles.py b/stac_fastapi/extensions/stac_fastapi/extensions/third_party/tiles.py index 7fefb8fa8..bf3030914 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/third_party/tiles.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/third_party/tiles.py @@ -7,7 +7,8 @@ from fastapi import FastAPI from pydantic import BaseModel from stac_pydantic.collection import SpatialExtent -from stac_pydantic.shared import Link, MimeTypes, Relations +from stac_pydantic.links import Link, Relations +from stac_pydantic.shared import MimeTypes from starlette.requests import Request from starlette.responses import HTMLResponse, RedirectResponse diff --git a/stac_fastapi/pgstac/setup.py b/stac_fastapi/pgstac/setup.py index 2dd4fed6c..6d7cfe3d8 100644 --- a/stac_fastapi/pgstac/setup.py +++ b/stac_fastapi/pgstac/setup.py @@ -10,7 +10,7 @@ "attrs", "orjson", "pydantic[dotenv]", - "stac_pydantic==1.3.8", + "stac_pydantic==2.0.0", "stac-fastapi.types", "stac-fastapi.api", "stac-fastapi.extensions", diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index c4eeffdb2..8bc0a43e8 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -10,7 +10,9 @@ from fastapi.responses import ORJSONResponse from stac_pydantic import Collection, Item, ItemCollection from stac_pydantic.api import ConformanceClasses, LandingPage -from stac_pydantic.shared import Link, MimeTypes, Relations +from stac_pydantic.api.collections import Collections +from stac_pydantic.links import Link, Relations +from stac_pydantic.shared import MimeTypes from stac_fastapi.pgstac.models.links import CollectionLinks, ItemLinks, PagingLinks from stac_fastapi.pgstac.types.search import PgstacSearch @@ -24,6 +26,16 @@ class CoreCrudClient(BaseCoreClient): """Client for core endpoints defined by stac.""" + landing_page_id: str = attr.ib(default="stac-api") + title: str = attr.ib(default="Arturo STAC API") + description: str = attr.ib(default="Arturo raster datastore") + conformance_classes: List[str] = attr.ib( + factory=lambda: [ + "https://stacspec.org/STAC-api.html", + "http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#ats_geojson", + ] + ) + async def landing_page(self, **kwargs) -> ORJSONResponse: """Landing page. @@ -35,8 +47,10 @@ async def landing_page(self, **kwargs) -> ORJSONResponse: request = kwargs["request"] base_url = str(request.base_url) landing_page = LandingPage( - title="Arturo STAC API", - description="Arturo raster datastore", + id=self.landing_page_id, + title=self.title, + description=self.description, + conformsTo=self.conformance_classes, links=[ Link( rel=Relations.self, @@ -47,24 +61,24 @@ async def landing_page(self, **kwargs) -> ORJSONResponse: rel=Relations.docs, type=MimeTypes.html, title="OpenAPI docs", - href=urljoin(base_url, "/docs"), + href=urljoin(base_url, "docs"), ), Link( rel=Relations.conformance, type=MimeTypes.json, title="STAC/WFS3 conformance classes implemented by this server", - href=urljoin(base_url, "/conformance"), + href=urljoin(base_url, "conformance"), ), Link( rel=Relations.search, type=MimeTypes.geojson, title="STAC search", - href=urljoin(base_url, "/search"), + href=urljoin(base_url, "search"), ), Link( rel="data", type=MimeTypes.json, - href=urljoin(base_url, "/collections"), + href=urljoin(base_url, "collections"), ), ], ) @@ -81,14 +95,9 @@ async def landing_page(self, **kwargs) -> ORJSONResponse: async def conformance(self, **kwargs) -> ConformanceClasses: """Conformance classes.""" - return ConformanceClasses( - conformsTo=[ - "https://stacspec.org/STAC-api.html", - "http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#ats_geojson", - ] - ) + return ConformanceClasses(conformsTo=self.conformance_classes) - async def _all_collections_func(self, **kwargs) -> List[Dict]: + async def _all_collections_func(self, **kwargs) -> List[Collection]: """Read all collections from the database.""" request = kwargs["request"] pool = request.app.state.readpool @@ -111,10 +120,25 @@ async def _all_collections_func(self, **kwargs) -> List[Dict]: async def all_collections(self, **kwargs) -> ORJSONResponse: """Get all collections.""" + request = kwargs["request"] + base_url = str(request.base_url) + url = str(request.url) collections = await self._all_collections_func(**kwargs) - if collections is None or len(collections) < 1: - return ORJSONResponse([]) - return ORJSONResponse([c.dict(exclude_none=True) for c in collections]) + links = [ + Link(rel=Relations.self, type=MimeTypes.json, href=url), + Link(rel=Relations.parent, type=MimeTypes.json, href=base_url), + Link(rel=Relations.root, type=MimeTypes.json, href=base_url), + ] + if collections is None: + return ORJSONResponse( + Collections(collections=[], links=links).dict(exclude_none=True) + ) + return ORJSONResponse( + Collections( + collections=[c.dict(exclude_none=True) for c in collections], + links=links, + ).dict(exclude_none=True) + ) async def get_collection(self, id: str, **kwargs) -> ORJSONResponse: """Get collection by id. diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/models/links.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/models/links.py index 68ccc7f39..aef02d802 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/models/links.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/models/links.py @@ -4,8 +4,8 @@ from urllib.parse import ParseResult, parse_qs, unquote, urlencode, urljoin, urlparse import attr -from stac_pydantic.api.extensions.paging import PaginationLink -from stac_pydantic.shared import Link, MimeTypes, Relations +from stac_pydantic.links import Link, PaginationLink, Relations +from stac_pydantic.shared import MimeTypes from starlette.requests import Request from stac_fastapi.extensions.third_party.tiles import OGCTileLink diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/models/schemas.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/models/schemas.py index 6c08c4214..4b3e00642 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/models/schemas.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/models/schemas.py @@ -7,8 +7,8 @@ from pydantic import BaseModel from stac_pydantic import Collection as CollectionBase from stac_pydantic import Item as ItemBase -from stac_pydantic.api.search import DATETIME_RFC339 -from stac_pydantic.shared import Link +from stac_pydantic.links import Link +from stac_pydantic.shared import DATETIME_RFC339 # Be careful: https://github.com/samuelcolvin/pydantic/issues/1423#issuecomment-642797287 NumType = Union[float, int] diff --git a/stac_fastapi/pgstac/tests/clients/test_postgres.py b/stac_fastapi/pgstac/tests/clients/test_postgres.py index 009872585..990410c17 100644 --- a/stac_fastapi/pgstac/tests/clients/test_postgres.py +++ b/stac_fastapi/pgstac/tests/clients/test_postgres.py @@ -80,7 +80,7 @@ async def test_update_item(app_client, load_test_collection, load_test_item): item.properties.description = "Update Test" - resp = await app_client.put(f"/collections/{coll.id}/items", json=item.dict()) + resp = await app_client.put(f"/collections/{coll.id}/items", data=item.json()) assert resp.status_code == 200 resp = await app_client.get(f"/collections/{coll.id}/items/{item.id}") @@ -113,7 +113,7 @@ async def test_get_collection_items(app_client, load_test_collection, load_test_ item.id = str(uuid.uuid4()) resp = await app_client.post( f"/collections/{coll.id}/items", - json=item.dict(), + data=item.json(), ) assert resp.status_code == 200 diff --git a/stac_fastapi/pgstac/tests/data/joplin/collection.json b/stac_fastapi/pgstac/tests/data/joplin/collection.json index 697a164d1..af7681601 100644 --- a/stac_fastapi/pgstac/tests/data/joplin/collection.json +++ b/stac_fastapi/pgstac/tests/data/joplin/collection.json @@ -1,9 +1,10 @@ { "id": "joplin", "description": "This imagery was acquired by the NOAA Remote Sensing Division to support NOAA national security and emergency response requirements. In addition, it will be used for ongoing research efforts for testing and developing standards for airborne digital imagery. Individual images have been combined into a larger mosaic and tiled for distribution. The approximate ground sample distance (GSD) for each pixel is 35 cm (1.14 feet).", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "license": "public-domain", "links": [], + "type": "collection", "extent": { "spatial": { "bbox": [ diff --git a/stac_fastapi/pgstac/tests/data/joplin/index.geojson b/stac_fastapi/pgstac/tests/data/joplin/index.geojson index 1fa6c933c..1bc8dde5c 100644 --- a/stac_fastapi/pgstac/tests/data/joplin/index.geojson +++ b/stac_fastapi/pgstac/tests/data/joplin/index.geojson @@ -55,10 +55,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a7e125ba-565d-4aa2-bbf3-c57a9087c2e3", @@ -114,10 +114,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f7f164c9-cfdf-436d-a3f0-69864c38ba2a", @@ -173,10 +173,10 @@ 37.1033841 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "ea0fddf4-56f9-4a16-8a0b-f6b0b123b7cf", @@ -232,10 +232,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "c811e716-ab07-4d80-ac95-6670f8713bc4", @@ -291,10 +291,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d4eccfa2-7d77-4624-9e2a-3f59102285bb", @@ -350,10 +350,10 @@ 37.1033841 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "fe916452-ba6f-4631-9154-c249924a122d", @@ -409,10 +409,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "85f923a5-a81f-4acd-bc7f-96c7c915f357", @@ -468,10 +468,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "29c53e17-d7d1-4394-a80f-36763c8f42dc", @@ -527,10 +527,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "e0a02e4e-aa0c-412e-8f63-6f5344f829df", @@ -586,10 +586,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "047ab5f0-dce1-4166-a00d-425a3dbefe02", @@ -645,10 +645,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "57f88dd2-e4e0-48e6-a2b6-7282d4ab8ea4", @@ -704,10 +704,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "68f2c2b2-4bce-4c40-9a0d-782c1be1f4f2", @@ -763,10 +763,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d8461d8c-3d2b-4e4e-a931-7ae61ca06dbf", @@ -822,10 +822,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "aeedef30-cbdd-4364-8781-dbb42d148c99", @@ -881,10 +881,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "9ef4279f-386c-40c7-ad71-8de5d9543aa4", @@ -940,10 +940,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "70cc6c05-9fe0-436a-a264-a52515f3f242", @@ -999,10 +999,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d191a6fd-7881-4421-805c-e246371e5cc4", @@ -1058,10 +1058,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d144adde-df4a-45e8-bed9-f085f91486a2", @@ -1117,10 +1117,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a4c32abd-9791-422b-87ab-b0f3fa36f053", @@ -1176,10 +1176,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "4610c58e-39f4-4d9d-94ba-ceddbf9ac570", @@ -1235,10 +1235,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "145fa700-16d4-4d34-98e0-7540d5c0885f", @@ -1294,10 +1294,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a89dc7b8-a580-435b-8176-d8e4386d620c", @@ -1353,10 +1353,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "386dfa13-c2b4-4ce6-8e6f-fcac73f4e64e", @@ -1412,10 +1412,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "4d8a8e40-d089-4ca7-92c8-27d810ee07bf", @@ -1471,10 +1471,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f734401c-2df0-4694-a353-cdd3ea760cdc", @@ -1530,10 +1530,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "da6ef938-c58f-4bab-9d4e-89f6ae667da2", @@ -1589,10 +1589,10 @@ 37.1077651 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "ad420ced-b005-472b-a6df-3838c2b74504", @@ -1648,10 +1648,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f490b7af-0019-45e2-854b-3854d07fd063", @@ -1707,10 +1707,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "b853f353-4b72-44d5-aa44-c07dfd307138", @@ -1766,10 +1766,10 @@ 37.1077651 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" } ] } \ No newline at end of file diff --git a/stac_fastapi/pgstac/tests/data/test_collection.json b/stac_fastapi/pgstac/tests/data/test_collection.json index 4189961fa..04d309cb6 100644 --- a/stac_fastapi/pgstac/tests/data/test_collection.json +++ b/stac_fastapi/pgstac/tests/data/test_collection.json @@ -1,8 +1,9 @@ { "id": "test-collection", "stac_extensions": [], + "type": "collection", "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "license": "PDDL-1.0", "summaries": { "platform": ["landsat-8"], diff --git a/stac_fastapi/pgstac/tests/data/test_item.json b/stac_fastapi/pgstac/tests/data/test_item.json index 8113f683a..02edd0ec2 100644 --- a/stac_fastapi/pgstac/tests/data/test_item.json +++ b/stac_fastapi/pgstac/tests/data/test_item.json @@ -2,8 +2,8 @@ "type": "Feature", "id": "test-item", "stac_extensions": [ - "eo", - "view" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "geometry": { "coordinates": [ diff --git a/stac_fastapi/pgstac/tests/resources/test_item.py b/stac_fastapi/pgstac/tests/resources/test_item.py index e2bf7c755..74ef88851 100644 --- a/stac_fastapi/pgstac/tests/resources/test_item.py +++ b/stac_fastapi/pgstac/tests/resources/test_item.py @@ -7,7 +7,7 @@ import pytest from shapely.geometry import Polygon from stac_pydantic import Collection, Item -from stac_pydantic.api.search import DATETIME_RFC339 +from stac_pydantic.shared import DATETIME_RFC339 @pytest.mark.asyncio @@ -87,7 +87,7 @@ async def test_update_item( item.properties.description = "Update Test" - resp = await app_client.put(f"/collections/{coll.id}/items", json=item.dict()) + resp = await app_client.put(f"/collections/{coll.id}/items", data=item.json()) assert resp.status_code == 200 resp = await app_client.get(f"/collections/{coll.id}/items/{item.id}") @@ -122,7 +122,7 @@ async def test_get_collection_items(app_client, load_test_collection, load_test_ item.id = str(uuid.uuid4()) resp = await app_client.post( f"/collections/{coll.id}/items", - json=item.dict(), + data=item.json(), ) assert resp.status_code == 200 @@ -192,7 +192,7 @@ async def test_update_new_item( item = load_test_item item.id = "test-updatenewitem" - resp = await app_client.put(f"/collections/{coll.id}/items", json=item.dict()) + resp = await app_client.put(f"/collections/{coll.id}/items", data=item.json()) assert resp.status_code == 404 @@ -204,7 +204,7 @@ async def test_update_item_missing_collection( item = load_test_item item.collection = None - resp = await app_client.put(f"/collections/{coll.id}/items", json=item.dict()) + resp = await app_client.put(f"/collections/{coll.id}/items", data=item.json()) assert resp.status_code == 424 diff --git a/stac_fastapi/sqlalchemy/alembic/versions/407037cb1636_add_stac_1_0_0_fields.py b/stac_fastapi/sqlalchemy/alembic/versions/407037cb1636_add_stac_1_0_0_fields.py new file mode 100644 index 000000000..fdf15cde6 --- /dev/null +++ b/stac_fastapi/sqlalchemy/alembic/versions/407037cb1636_add_stac_1_0_0_fields.py @@ -0,0 +1,27 @@ +"""add-stac-1.0.0-fields + +Revision ID: 407037cb1636 +Revises: 77c019af60bf +Create Date: 2021-07-07 16:10:03.196942 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "407037cb1636" +down_revision = "77c019af60bf" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "collections", + sa.Column("type", sa.VARCHAR(300), default="collection", nullable=False), + schema="data", + ) + + +def downgrade(): + op.drop_column("collections", "type") diff --git a/stac_fastapi/sqlalchemy/setup.py b/stac_fastapi/sqlalchemy/setup.py index a50fbf0a2..dadfa624d 100644 --- a/stac_fastapi/sqlalchemy/setup.py +++ b/stac_fastapi/sqlalchemy/setup.py @@ -9,7 +9,7 @@ "fastapi", "attrs", "pydantic[dotenv]", - "stac_pydantic==1.3.8", + "stac_pydantic==2.0.0", "stac-fastapi.types", "stac-fastapi.api", "stac-fastapi.extensions", @@ -23,7 +23,13 @@ ] extra_reqs = { - "dev": ["pytest", "pytest-cov", "pytest-asyncio", "pre-commit", "requests"], + "dev": [ + "pytest", + "pytest-cov", + "pytest-asyncio", + "pre-commit", + "requests", + ], "docs": ["mkdocs", "mkdocs-material", "pdocs"], "server": ["uvicorn[standard]>=0.12.0,<0.14.0"], } diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py index 7b3ffb48f..c0b624208 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py @@ -15,8 +15,7 @@ from sqlalchemy.orm import Session as SqlSession from stac_pydantic import ItemCollection from stac_pydantic.api import ConformanceClasses -from stac_pydantic.api.extensions.paging import PaginationLink -from stac_pydantic.shared import Relations +from stac_pydantic.links import PaginationLink, Relations from stac_fastapi.extensions.core import ContextExtension, FieldsExtension from stac_fastapi.sqlalchemy.models import database, schemas @@ -135,7 +134,11 @@ def item_collection( context_obj = None if self.extension_is_enabled(ContextExtension): - context_obj = {"returned": len(page), "limit": limit, "matched": count} + context_obj = { + "returned": len(page), + "limit": limit, + "matched": count, + } return ItemCollection( type="FeatureCollection", @@ -247,7 +250,8 @@ def post_search( if search_request.sortby: sort_fields = [ getattr( - self.item_table.get_field(sort.field), sort.direction.value + self.item_table.get_field(sort.field), + sort.direction.value, )() for sort in search_request.sortby ] @@ -296,20 +300,15 @@ def post_search( # Temporal query if search_request.datetime: # Two tailed query (between) + dts = search_request.datetime.split("/") if ".." not in search_request.datetime: - query = query.filter( - self.item_table.datetime.between(*search_request.datetime) - ) + query = query.filter(self.item_table.datetime.between(*dts)) # All items after the start date - if search_request.datetime[0] != "..": - query = query.filter( - self.item_table.datetime >= search_request.datetime[0] - ) + if dts[0] != "..": + query = query.filter(self.item_table.datetime >= dts[0]) # All items before the end date - if search_request.datetime[1] != "..": - query = query.filter( - self.item_table.datetime <= search_request.datetime[1] - ) + if dts[1] != "..": + query = query.filter(self.item_table.datetime <= dts[1]) # Query fields if search_request.query: diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/database.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/database.py index 49b9e30b4..506f01b92 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/database.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/database.py @@ -49,7 +49,7 @@ class Collection(BaseModel): # type:ignore stac_extensions = sa.Column(sa.ARRAY(sa.VARCHAR(300)), nullable=True) title = sa.Column(sa.VARCHAR(1024)) description = sa.Column(sa.VARCHAR(1024), nullable=False) - keywords = sa.Column(sa.VARCHAR(300)) + keywords = sa.Column(sa.ARRAY(sa.VARCHAR(300))) version = sa.Column(sa.VARCHAR(300)) license = sa.Column(sa.VARCHAR(300), nullable=False) providers = sa.Column(JSONB) @@ -57,6 +57,7 @@ class Collection(BaseModel): # type:ignore extent = sa.Column(JSONB) links = sa.Column(JSONB) children = sa.orm.relationship("Item", lazy="dynamic") + type = sa.Column(sa.VARCHAR(300), nullable=False) @classmethod def get_database_model(cls, schema: schemas.Collection) -> dict: @@ -96,7 +97,7 @@ def get_database_model(cls, schema: schemas.Item) -> dict: for field in Settings.get().indexed_fields: # Use getattr to accommodate extension namespaces field_value = getattr(schema.properties, field) - if field == "datetime": + if field == "datetime" and not isinstance(field_value, datetime): field_value = datetime.strptime(field_value, DATETIME_RFC339) indexed_fields[field.split(":")[-1]] = field_value @@ -105,6 +106,12 @@ def get_database_model(cls, schema: schemas.Item) -> dict: now = datetime.utcnow().strftime(DATETIME_RFC339) if not properties["created"]: properties["created"] = now + else: + # If there isn't a created field initially it is already included in the pydantic model. + # Which means its typed as a datetime.datetime object, but sqlalchemy needs this to be a string. + # Probably don't need the isinstance check here but it's a little safer. + if isinstance(properties["created"], datetime): + properties["created"] = properties["created"].strftime(DATETIME_RFC339) properties["updated"] = now return dict( diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/decompose.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/decompose.py index a9539ebb7..d3ef703c9 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/decompose.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/decompose.py @@ -55,7 +55,9 @@ def __init__(self, obj: Any): properties[field] = field_value # Create inferred links item_links = ItemLinks( - collection_id=obj.collection_id, base_url=obj.base_url, item_id=obj.id + collection_id=obj.collection_id, + base_url=obj.base_url, + item_id=obj.id, ).create_links() # Resolve existing links if obj.links: @@ -106,5 +108,6 @@ def __init__(self, obj: Any): summaries=obj.summaries, extent=obj.extent, links=collection_links, + type="collection", ) super().__init__(db_model) diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/schemas.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/schemas.py index 5dc35a286..daecdcab8 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/schemas.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/models/schemas.py @@ -7,8 +7,8 @@ from pydantic import BaseModel from stac_pydantic import Collection as CollectionBase from stac_pydantic import Item as ItemBase -from stac_pydantic.api.search import DATETIME_RFC339 -from stac_pydantic.shared import Link +from stac_pydantic.links import Link +from stac_pydantic.shared import DATETIME_RFC339 from stac_fastapi.sqlalchemy.models.decompose import CollectionGetter, ItemGetter diff --git a/stac_fastapi/sqlalchemy/tests/data/test_collection.json b/stac_fastapi/sqlalchemy/tests/data/test_collection.json index 4189961fa..04d309cb6 100644 --- a/stac_fastapi/sqlalchemy/tests/data/test_collection.json +++ b/stac_fastapi/sqlalchemy/tests/data/test_collection.json @@ -1,8 +1,9 @@ { "id": "test-collection", "stac_extensions": [], + "type": "collection", "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "license": "PDDL-1.0", "summaries": { "platform": ["landsat-8"], diff --git a/stac_fastapi/sqlalchemy/tests/data/test_item.json b/stac_fastapi/sqlalchemy/tests/data/test_item.json index 8113f683a..02edd0ec2 100644 --- a/stac_fastapi/sqlalchemy/tests/data/test_item.json +++ b/stac_fastapi/sqlalchemy/tests/data/test_item.json @@ -2,8 +2,8 @@ "type": "Feature", "id": "test-item", "stac_extensions": [ - "eo", - "view" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "geometry": { "coordinates": [ diff --git a/stac_fastapi/sqlalchemy/tests/resources/test_item.py b/stac_fastapi/sqlalchemy/tests/resources/test_item.py index f00994d04..86f48dd2b 100644 --- a/stac_fastapi/sqlalchemy/tests/resources/test_item.py +++ b/stac_fastapi/sqlalchemy/tests/resources/test_item.py @@ -2,12 +2,13 @@ import time import uuid from copy import deepcopy -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from random import randint from urllib.parse import parse_qs, urlparse, urlsplit +from pydantic.datetime_parse import parse_datetime from shapely.geometry import Polygon -from stac_pydantic.api.search import DATETIME_RFC339 +from stac_pydantic.shared import DATETIME_RFC339 def test_create_and_delete_item(app_client, load_test_data): @@ -189,16 +190,16 @@ def test_pagination(app_client, load_test_data): def test_item_timestamps(app_client, load_test_data): """Test created and updated timestamps (common metadata)""" test_item = load_test_data("test_item.json") - start_time = datetime.utcnow() + start_time = datetime.now(timezone.utc) time.sleep(2) # Confirm `created` timestamp resp = app_client.post( f"/collections/{test_item['collection']}/items", json=test_item ) item = resp.json() - created_dt = datetime.strptime(item["properties"]["created"], DATETIME_RFC339) + created_dt = parse_datetime(item["properties"]["created"]) assert resp.status_code == 200 - assert start_time < created_dt < datetime.utcnow() + assert start_time < created_dt < datetime.now(timezone.utc) time.sleep(2) # Confirm `updated` timestamp @@ -209,10 +210,7 @@ def test_item_timestamps(app_client, load_test_data): # Created shouldn't change on update assert item["properties"]["created"] == updated_item["properties"]["created"] - assert ( - datetime.strptime(updated_item["properties"]["updated"], DATETIME_RFC339) - > created_dt - ) + assert parse_datetime(updated_item["properties"]["updated"]) > created_dt def test_item_search_by_id_post(app_client, load_test_data): @@ -266,7 +264,7 @@ def test_item_search_temporal_query_post(app_client, load_test_data): params = { "collections": [test_item["collection"]], "intersects": test_item["geometry"], - "datetime": item_date.strftime(DATETIME_RFC339), + "datetime": f"../{item_date.strftime(DATETIME_RFC339)}", } resp = app_client.post("/search", json=params) resp_json = resp.json() @@ -558,7 +556,8 @@ def test_pagination_item_collection(app_client, load_test_data): break query_params = parse_qs(urlparse(next_link[0]["href"]).query) page = app_client.get( - f"/collections/{test_item['collection']}/items", params=query_params + f"/collections/{test_item['collection']}/items", + params=query_params, ) # Our limit is 1 so we expect len(ids) number of requests before we run out of pages diff --git a/stac_fastapi/testdata/joplin/collection.json b/stac_fastapi/testdata/joplin/collection.json index 697a164d1..af7681601 100644 --- a/stac_fastapi/testdata/joplin/collection.json +++ b/stac_fastapi/testdata/joplin/collection.json @@ -1,9 +1,10 @@ { "id": "joplin", "description": "This imagery was acquired by the NOAA Remote Sensing Division to support NOAA national security and emergency response requirements. In addition, it will be used for ongoing research efforts for testing and developing standards for airborne digital imagery. Individual images have been combined into a larger mosaic and tiled for distribution. The approximate ground sample distance (GSD) for each pixel is 35 cm (1.14 feet).", - "stac_version": "1.0.0-beta.2", + "stac_version": "1.0.0", "license": "public-domain", "links": [], + "type": "collection", "extent": { "spatial": { "bbox": [ diff --git a/stac_fastapi/testdata/joplin/index.geojson b/stac_fastapi/testdata/joplin/index.geojson index 1fa6c933c..1bc8dde5c 100644 --- a/stac_fastapi/testdata/joplin/index.geojson +++ b/stac_fastapi/testdata/joplin/index.geojson @@ -55,10 +55,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a7e125ba-565d-4aa2-bbf3-c57a9087c2e3", @@ -114,10 +114,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f7f164c9-cfdf-436d-a3f0-69864c38ba2a", @@ -173,10 +173,10 @@ 37.1033841 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "ea0fddf4-56f9-4a16-8a0b-f6b0b123b7cf", @@ -232,10 +232,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "c811e716-ab07-4d80-ac95-6670f8713bc4", @@ -291,10 +291,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d4eccfa2-7d77-4624-9e2a-3f59102285bb", @@ -350,10 +350,10 @@ 37.1033841 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "fe916452-ba6f-4631-9154-c249924a122d", @@ -409,10 +409,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "85f923a5-a81f-4acd-bc7f-96c7c915f357", @@ -468,10 +468,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "29c53e17-d7d1-4394-a80f-36763c8f42dc", @@ -527,10 +527,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "e0a02e4e-aa0c-412e-8f63-6f5344f829df", @@ -586,10 +586,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "047ab5f0-dce1-4166-a00d-425a3dbefe02", @@ -645,10 +645,10 @@ 37.0814756 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "57f88dd2-e4e0-48e6-a2b6-7282d4ab8ea4", @@ -704,10 +704,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "68f2c2b2-4bce-4c40-9a0d-782c1be1f4f2", @@ -763,10 +763,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d8461d8c-3d2b-4e4e-a931-7ae61ca06dbf", @@ -822,10 +822,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "aeedef30-cbdd-4364-8781-dbb42d148c99", @@ -881,10 +881,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "9ef4279f-386c-40c7-ad71-8de5d9543aa4", @@ -940,10 +940,10 @@ 37.0595608 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "70cc6c05-9fe0-436a-a264-a52515f3f242", @@ -999,10 +999,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d191a6fd-7881-4421-805c-e246371e5cc4", @@ -1058,10 +1058,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "d144adde-df4a-45e8-bed9-f085f91486a2", @@ -1117,10 +1117,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a4c32abd-9791-422b-87ab-b0f3fa36f053", @@ -1176,10 +1176,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "4610c58e-39f4-4d9d-94ba-ceddbf9ac570", @@ -1235,10 +1235,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "145fa700-16d4-4d34-98e0-7540d5c0885f", @@ -1294,10 +1294,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "a89dc7b8-a580-435b-8176-d8e4386d620c", @@ -1353,10 +1353,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "386dfa13-c2b4-4ce6-8e6f-fcac73f4e64e", @@ -1412,10 +1412,10 @@ 37.1055746 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "4d8a8e40-d089-4ca7-92c8-27d810ee07bf", @@ -1471,10 +1471,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f734401c-2df0-4694-a353-cdd3ea760cdc", @@ -1530,10 +1530,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "da6ef938-c58f-4bab-9d4e-89f6ae667da2", @@ -1589,10 +1589,10 @@ 37.1077651 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "ad420ced-b005-472b-a6df-3838c2b74504", @@ -1648,10 +1648,10 @@ 37.0617526 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "f490b7af-0019-45e2-854b-3854d07fd063", @@ -1707,10 +1707,10 @@ 37.0836668 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" }, { "id": "b853f353-4b72-44d5-aa44-c07dfd307138", @@ -1766,10 +1766,10 @@ 37.1077651 ], "stac_extensions": [ - "eo", - "proj" + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], - "stac_version": "1.0.0-beta.2" + "stac_version": "1.0.0" } ] } \ No newline at end of file diff --git a/stac_fastapi/types/setup.py b/stac_fastapi/types/setup.py index 36b54f0a0..926760b05 100644 --- a/stac_fastapi/types/setup.py +++ b/stac_fastapi/types/setup.py @@ -9,11 +9,17 @@ "fastapi", "attrs", "pydantic[dotenv]", - "stac_pydantic==1.3.8", + "stac_pydantic==2.0.0", ] extra_reqs = { - "dev": ["pytest", "pytest-cov", "pytest-asyncio", "pre-commit", "requests"], + "dev": [ + "pytest", + "pytest-cov", + "pytest-asyncio", + "pre-commit", + "requests", + ], "docs": ["mkdocs", "mkdocs-material", "pdocs"], } diff --git a/stac_fastapi/types/stac_fastapi/types/core.py b/stac_fastapi/types/stac_fastapi/types/core.py index 9ee159544..364b7c4d5 100644 --- a/stac_fastapi/types/stac_fastapi/types/core.py +++ b/stac_fastapi/types/stac_fastapi/types/core.py @@ -7,7 +7,8 @@ import attr from stac_pydantic import Collection, Item, ItemCollection from stac_pydantic.api import ConformanceClasses, LandingPage, Search -from stac_pydantic.shared import Link, MimeTypes, Relations +from stac_pydantic.links import Link, Relations +from stac_pydantic.shared import MimeTypes from stac_pydantic.version import STAC_VERSION from stac_fastapi.types.extension import ApiExtension @@ -141,6 +142,10 @@ def landing_page(self, **kwargs) -> LandingPage: id=self.landing_page_id, title=self.title, description=self.description, + conformsTo=[ + "https://stacspec.org/STAC-api.html", + "http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#ats_geojson", + ], stac_version=self.stac_version, links=[ Link( diff --git a/stac_fastapi/types/stac_fastapi/types/links.py b/stac_fastapi/types/stac_fastapi/types/links.py index c39629929..9ab9e26aa 100644 --- a/stac_fastapi/types/stac_fastapi/types/links.py +++ b/stac_fastapi/types/stac_fastapi/types/links.py @@ -4,7 +4,8 @@ from urllib.parse import urljoin import attr -from stac_pydantic.shared import Link, MimeTypes, Relations +from stac_pydantic.links import Link, Relations +from stac_pydantic.shared import MimeTypes # These can be inferred from the item/collection so they aren't included in the database # Instead they are dynamically generated when querying the database using the classes defined below @@ -69,7 +70,8 @@ def self(self) -> Link: rel=Relations.self, type=MimeTypes.geojson, href=urljoin( - self.base_url, f"collections/{self.collection_id}/items/{self.item_id}" + self.base_url, + f"collections/{self.collection_id}/items/{self.item_id}", ), )