Skip to content

Commit

Permalink
Fix conformance classes (#39)
Browse files Browse the repository at this point in the history
* Use Extensions conformance classes.

Move to using only the default conformance classes and
extension-provided conformance classes.

Refactor extensions to be located in pcstac.config,
so that tests can share the same extensions.

* Guard against incorrect or duplicate c-classes

Do this by checking that any conformance class coming
from api.stacspec.org has the correct STAC API version.

* Fix STAC API version advertised by docs.

* Update api version to 1.2

The only place this is used is in the API documentation.

* Update CHANGELOG
  • Loading branch information
lossyrob authored Jan 21, 2022
1 parent 80a383f commit accc90f
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 88 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Fixed conformance classes [#39](https://github.com/microsoft/planetary-computer-apis/pull/39)
- Fixed STAC API version in API documentation [#39](https://github.com/microsoft/planetary-computer-apis/pull/39)

## [2022.1.0]

### Changed
Expand Down
3 changes: 2 additions & 1 deletion pcstac/pcstac/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from stac_fastapi.api.app import StacApi

from pccommon.openapi import fixup_schema
from pcstac.config import STAC_API_VERSION

STAC_API_OPENAPI_TAG = "STAC API v1.0.0-beta.2"
STAC_API_OPENAPI_TAG = f"STAC API {STAC_API_VERSION}"


class PCStacApi(StacApi):
Expand Down
38 changes: 28 additions & 10 deletions pcstac/pcstac/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,44 @@
from functools import lru_cache

from pydantic import BaseSettings
from stac_fastapi.extensions.core import (
FieldsExtension,
FilterExtension,
QueryExtension,
SortExtension,
TokenPaginationExtension,
)
from stac_fastapi.extensions.core.filter.filter import FilterConformanceClasses

API_VERSION = "1.1"
API_VERSION = "1.2"
STAC_API_VERSION = "v1.0.0-beta.4"

API_LANDING_PAGE_ID = "microsoft-pc"
API_TITLE = "Microsoft Planetary Computer STAC API"
API_DESCRIPTION = (
"Searchable spatiotemporal metadata describing Earth science datasets "
"hosted by the Microsoft Planetary Computer"
)
API_CONFORMANCE_CLASSES = [
"https://api.stacspec.org/v1.0.0-beta.3/core",
"https://api.stacspec.org/v1.0.0-beta.3/item-search",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#query",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
]

TILER_HREF_ENV_VAR = "TILER_HREF"

EXTENSIONS = [
# STAC API Extensions
QueryExtension(),
SortExtension(),
FieldsExtension(),
FilterExtension(
conformance_classes=[
FilterConformanceClasses.FILTER,
FilterConformanceClasses.ITEM_SEARCH_FILTER,
FilterConformanceClasses.BASIC_CQL,
FilterConformanceClasses.CQL_JSON,
]
),
# stac_fastapi extensions
TokenPaginationExtension(),
]


class Settings(BaseSettings):
"""Class for specifying application parameters
Expand All @@ -43,7 +61,7 @@ class Settings(BaseSettings):
tiler_href: str = os.environ.get("TILER_HREF_ENV_VAR", "")
openapi_url: str = "/openapi.json"
debug: bool = os.getenv("PQE_DEBUG", "False").lower() == "true"
api_version: str = "v1.1"
api_version: str = f"v{API_VERSION}"


@lru_cache
Expand Down
60 changes: 12 additions & 48 deletions pcstac/pcstac/main.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
"""FastAPI application using PGStac."""
import logging
import os
from typing import Any, Awaitable, Callable, Dict, List
from typing import Any, Awaitable, Callable, Dict

from fastapi import FastAPI, Request, Response
from fastapi.exceptions import RequestValidationError, StarletteHTTPException
from fastapi.openapi.utils import get_openapi
from fastapi.responses import ORJSONResponse
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
from stac_fastapi.extensions.core import (
ContextExtension,
FieldsExtension,
FilterExtension,
QueryExtension,
SortExtension,
TokenPaginationExtension,
)
from stac_fastapi.pgstac.config import Settings
from stac_fastapi.pgstac.db import close_db_connection, connect_to_db
from starlette.middleware.cors import CORSMiddleware
Expand All @@ -27,7 +19,13 @@
from pccommon.openapi import fixup_schema
from pcstac.api import PCStacApi
from pcstac.client import PCClient
from pcstac.config import API_DESCRIPTION, API_TITLE, API_VERSION, get_settings
from pcstac.config import (
API_DESCRIPTION,
API_TITLE,
API_VERSION,
EXTENSIONS,
get_settings,
)
from pcstac.errors import PC_DEFAULT_STATUS_CODES
from pcstac.search import PCSearch, PCSearchGetRequest

Expand All @@ -47,53 +45,19 @@
POOL_SIZE = int(os.environ.get("POOL_SIZE", "1"))
logger.info(f"POOL_SIZE: {POOL_SIZE}")

extensions = [
QueryExtension(),
SortExtension(),
FieldsExtension(),
FilterExtension(),
TokenPaginationExtension(),
ContextExtension(),
]

# Planetary Computer conformance classes differ from the default
# stac-fastapi case so they are manually specified
cql_conformance_classes: List[str] = [
"https://api.stacspec.org/v1.0.0-beta.3/item-search/#fields",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#filter",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#filter:cql-json",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#filter:filter",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#filter:item-search-filter",
"https://api.stacspec.org/v1.0.0-beta.3/item-search#filter:basic-spatial-operators",
(
"https://api.stacspec.org/v1.0.0-beta.3/item-search"
"#filter:basic-temporal-operators"
),
"https://api.stacspec.org/v1.0.0-beta.3/item-search/#sort",
"https://api.stacspec.org/v1.0.0-beta.3/item-search/#query",
]

collections_conformance_classes: List[str] = [
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
]

extra_conformance_classes = cql_conformance_classes + collections_conformance_classes

search_get_request_model = create_get_request_model(
extensions, base_model=PCSearchGetRequest
EXTENSIONS, base_model=PCSearchGetRequest
)
search_post_request_model = create_post_request_model(extensions, base_model=PCSearch)
search_post_request_model = create_post_request_model(EXTENSIONS, base_model=PCSearch)

api = PCStacApi(
title=API_TITLE,
description=API_DESCRIPTION,
api_version=API_VERSION,
settings=Settings(debug=DEBUG),
client=PCClient.create(
post_request_model=search_post_request_model,
extra_conformance_classes=extra_conformance_classes,
),
extensions=extensions,
client=PCClient.create(post_request_model=search_post_request_model),
extensions=EXTENSIONS,
app=FastAPI(root_path=APP_ROOT_PATH, default_response_class=ORJSONResponse),
search_get_request_model=search_get_request_model,
search_post_request_model=search_post_request_model,
Expand Down
23 changes: 4 additions & 19 deletions pcstac/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,12 @@
from httpx import AsyncClient
from pypgstac import pypgstac
from stac_fastapi.api.models import create_get_request_model, create_post_request_model
from stac_fastapi.extensions.core import (
ContextExtension,
FieldsExtension,
FilterExtension,
QueryExtension,
SortExtension,
TokenPaginationExtension,
)
from stac_fastapi.pgstac.config import Settings
from stac_fastapi.pgstac.db import close_db_connection, connect_to_db

from pcstac.api import PCStacApi
from pcstac.client import PCClient
from pcstac.config import EXTENSIONS
from pcstac.search import PCSearch, PCSearchGetRequest

DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
Expand Down Expand Up @@ -60,19 +53,11 @@ async def pqe_pg():
@pytest.fixture(scope="session")
def api_client(pqe_pg):
print("creating client with settings", settings, settings.reader_connection_string)
extensions = [
QueryExtension(),
SortExtension(),
FilterExtension(),
FieldsExtension(),
TokenPaginationExtension(),
ContextExtension(),
]
search_get_request_model = create_get_request_model(
extensions, base_model=PCSearchGetRequest
EXTENSIONS, base_model=PCSearchGetRequest
)
search_post_request_model = create_post_request_model(
extensions, base_model=PCSearch
EXTENSIONS, base_model=PCSearch
)
api = PCStacApi(
title="test title",
Expand All @@ -82,7 +67,7 @@ def api_client(pqe_pg):
client=PCClient.create(
post_request_model=search_post_request_model,
),
extensions=extensions,
extensions=EXTENSIONS,
app=FastAPI(default_response_class=ORJSONResponse),
search_get_request_model=search_get_request_model,
search_post_request_model=search_post_request_model,
Expand Down
24 changes: 14 additions & 10 deletions pcstac/tests/resources/test_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import pystac
import pytest

from pcstac.config import STAC_API_VERSION


def remove_root(stac_object: Dict[str, Any]) -> None:
links = []
Expand All @@ -18,12 +20,6 @@ async def test_landing_page(app_client):
resp = await app_client.get("/")
assert resp.status_code == 200
resp_json = resp.json()
# expected = {
# "id", "stac_extensions", "description", "stac_version", "license",
# "summaries", "extent", "links", "title", "keywords", "providers"
# }
# result = set(resp_json)
# assert result == expected

remove_root(resp_json)
pystac.Catalog.from_dict(resp_json).validate()
Expand All @@ -38,8 +34,16 @@ async def test_landing_page(app_client):
assert resp.status_code == 200

# Make sure conformance classes are linked
conf = next(filter(lambda link: link["rel"] == "conformance", resp_json["links"]))[
"href"
]
resp = await app_client.get(conf)
conformance_link = next(
filter(lambda link: link["rel"] == "conformance", resp_json["links"])
)

assert "conformsTo" in resp_json
conforms_to = resp_json["conformsTo"]

# Make sure conformance classes are of the right STAC version
for conformance_class in conforms_to:
if "api.stacspec.org" in conformance_class:
assert STAC_API_VERSION in conformance_class
resp = await app_client.get(conformance_link["href"])
assert resp.status_code == 200

0 comments on commit accc90f

Please sign in to comment.