From 58643d654d62de7ae1b0e56fa32d5b4f04abd638 Mon Sep 17 00:00:00 2001 From: Josh Cole Date: Wed, 9 Oct 2024 13:17:01 -0700 Subject: [PATCH 1/2] Cleanup the project This commit cleans up the project a little. Introducing a LICENSE, removing unused tests, and improving the README. --- LICENSE | 21 ++++++ README.md | 2 +- stapi_fastapi_tle/tests/__init__.py | 0 stapi_fastapi_tle/tests/conftest.py | 62 ---------------- .../tests/datetime_interval_test.py | 74 ------------------- stapi_fastapi_tle/tests/opportunity_test.py | 46 ------------ stapi_fastapi_tle/tests/order_test.py | 66 ----------------- stapi_fastapi_tle/tests/product_test.py | 44 ----------- stapi_fastapi_tle/tests/root_test.py | 49 ------------ stapi_fastapi_tle/tests/utils.py | 2 - stapi_fastapi_tle/tests/warnings.py | 2 - 11 files changed, 22 insertions(+), 346 deletions(-) create mode 100644 LICENSE delete mode 100644 stapi_fastapi_tle/tests/__init__.py delete mode 100644 stapi_fastapi_tle/tests/conftest.py delete mode 100644 stapi_fastapi_tle/tests/datetime_interval_test.py delete mode 100644 stapi_fastapi_tle/tests/opportunity_test.py delete mode 100644 stapi_fastapi_tle/tests/order_test.py delete mode 100644 stapi_fastapi_tle/tests/product_test.py delete mode 100644 stapi_fastapi_tle/tests/root_test.py delete mode 100644 stapi_fastapi_tle/tests/utils.py delete mode 100644 stapi_fastapi_tle/tests/warnings.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7f05f7b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 STAPI FastAPI Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index da5c090..63f514c 100644 --- a/README.md +++ b/README.md @@ -37,5 +37,5 @@ curl http://127.0.0.1:8000/products _POST to opportunities_ ``` -curl -d '{"datetime":"2014-10-01T13:00:00Z/2014-10-10T15:30:00Z","product_id":"mock:standard","geometry":{"type":"Point","coordinates":[-115.06855238269905,31.987811301701587]},"properties":{"off_nadir":{"minimum":0,"maximum":40}},"filter":{}}' -X POST http://127.0.0.1:8000/opportunities +curl -d '{"datetime":"2014-10-01T13:00:00Z/2014-10-10T15:30:00Z","product_id":"mock:standard","geometry":{"type":"Point","coordinates":[-115.06855238269905,31.987811301701587]},"properties":{"off_nadir":{"minimum":0,"maximum":35}},"filter":{}}' -H 'Content-Type: application/json' -X POST http://127.0.0.1:8000/opportunities ``` diff --git a/stapi_fastapi_tle/tests/__init__.py b/stapi_fastapi_tle/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stapi_fastapi_tle/tests/conftest.py b/stapi_fastapi_tle/tests/conftest.py deleted file mode 100644 index e8c1347..0000000 --- a/stapi_fastapi_tle/tests/conftest.py +++ /dev/null @@ -1,62 +0,0 @@ -from datetime import UTC, datetime - -from geojson_pydantic import Point -from pydantic import BaseModel -from pytest import fixture - -from stapi_fastapi.models.opportunity import Opportunity, OpportunityRequest -from stapi_fastapi.models.product import Product, Provider, ProviderRole - - -@fixture -def products(): - class Parameters(BaseModel): - pass - - yield [ - Product( - id="mock:standard", - description="Mock backend's standard product", - license="CC0-1.0", - providers=[ - Provider( - name="ACME", - roles=[ - ProviderRole.licensor, - ProviderRole.producer, - ProviderRole.processor, - ProviderRole.host, - ], - url="http://acme.example.com", - ) - ], - parameters=Parameters, - links=[], - ) - ] - - -@fixture -def opportunities(products: list[Product]): - yield [ - Opportunity( - geometry=Point(type="Point", coordinates=[13.4, 52.5]), - properties={ - "product_id": products[0].id, - "datetime": (datetime.now(UTC), datetime.now(UTC)), - "filter": {}, - }, - ) - ] - - -@fixture -def allowed_payloads(products: list[Product]): - yield [ - OpportunityRequest( - geometry=Point(type="Point", coordinates=[13.4, 52.5]), - product_id=products[0].id, - datetime=(datetime.now(UTC), datetime.now(UTC)), - filter={}, - ), - ] diff --git a/stapi_fastapi_tle/tests/datetime_interval_test.py b/stapi_fastapi_tle/tests/datetime_interval_test.py deleted file mode 100644 index 9267491..0000000 --- a/stapi_fastapi_tle/tests/datetime_interval_test.py +++ /dev/null @@ -1,74 +0,0 @@ -from datetime import UTC, datetime, timedelta -from itertools import product - -from pydantic import BaseModel, ValidationError -from pyrfc3339.utils import timedelta_seconds, timezone -from pytest import mark, raises -from zoneinfo import ZoneInfo - -from stapi_fastapi.types.datetime_interval import DatetimeInterval - -EUROPE_BERLIN = ZoneInfo("Europe/Berlin") - - -class Model(BaseModel): - datetime: DatetimeInterval - - -def rfc3339_strftime(dt: datetime, format: str) -> str: - tds = timedelta_seconds(dt.tzinfo.utcoffset(dt)) - long = timezone(tds) - short = "Z" - - format = format.replace("%z", long).replace("%Z", short if tds == 0 else long) - return dt.strftime(format) - - -@mark.parametrize( - "value", - ( - "", - "2024-01-29/2024-01-30", - "2024-01-29T12:00:00/2024-01-30T12:00:00", - "2024-01-29T12:00:00Z/2024-01-28T12:00:00Z", - ), -) -def test_invalid_values(value: str): - with raises(ValidationError): - Model.model_validate_strings({"datetime": value}) - - -@mark.parametrize( - "tz, format", - product( - (UTC, EUROPE_BERLIN), - ( - "%Y-%m-%dT%H:%M:%S.%f%Z", - "%Y-%m-%dT%H:%M:%S.%f%z", - "%Y-%m-%d %H:%M:%S.%f%Z", - "%Y-%m-%dt%H:%M:%S.%f%Z", - "%Y-%m-%d_%H:%M:%S.%f%Z", - ), - ), -) -def test_deserialization(tz: ZoneInfo, format: str): - start = datetime.now(tz) - end = start + timedelta(hours=1) - value = f"{rfc3339_strftime(start, format)}/{rfc3339_strftime(end, format)}" - - model = Model.model_validate_json(f'{{"datetime":"{value}"}}') - - assert model.datetime == (start, end) - - -@mark.parametrize("tz", (UTC, EUROPE_BERLIN)) -def test_serialize(tz): - start = datetime.now(tz) - end = start + timedelta(hours=1) - model = Model(datetime=(start, end)) - - format = "%Y-%m-%dT%H:%M:%S.%f%z" - expected = f"{rfc3339_strftime(start, format)}/{rfc3339_strftime(end, format)}" - - obj = model.model_dump() - assert obj["datetime"] == expected diff --git a/stapi_fastapi_tle/tests/opportunity_test.py b/stapi_fastapi_tle/tests/opportunity_test.py deleted file mode 100644 index c2126d2..0000000 --- a/stapi_fastapi_tle/tests/opportunity_test.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import UTC, datetime, timedelta - -from fastapi import status -from fastapi.testclient import TestClient - -from stapi_fastapi.models.opportunity import Opportunity, OpportunityCollection -from stapi_fastapi.models.product import Product -from stapi_fastapi_test_backend.backend import TestBackend - - -def test_search_opportunities_response( - products: list[Product], - opportunities: list[Opportunity], - stapi_backend: TestBackend, - stapi_client: TestClient, -): - stapi_backend._products = products - stapi_backend._opportunities = opportunities - - now = datetime.now(UTC) - start = now - end = start + timedelta(days=5) - - res = stapi_client.post( - "/opportunities", - json={ - "geometry": { - "type": "Point", - "coordinates": [0, 0], - }, - "product_id": products[0].id, - "datetime": f"{start.isoformat()}/{end.isoformat()}", - "filter": { - "op": "and", - "args": [ - {"op": ">", "args": [{"property": "off_nadir"}, 0]}, - {"op": "<", "args": [{"property": "off_nadir"}, 45]}, - ], - }, - }, - ) - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/geo+json" - response = OpportunityCollection(**res.json()) - - assert len(response.features) > 0 diff --git a/stapi_fastapi_tle/tests/order_test.py b/stapi_fastapi_tle/tests/order_test.py deleted file mode 100644 index d05960a..0000000 --- a/stapi_fastapi_tle/tests/order_test.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import UTC, datetime, timedelta -from typing import Generator - -from fastapi import status -from fastapi.testclient import TestClient -from httpx import Response -from pytest import fixture - -from stapi_fastapi.models.opportunity import OpportunityRequest -from stapi_fastapi_test_backend.backend import TestBackend - -from .utils import find_link - -NOW = datetime.now(UTC) -START = NOW -END = START + timedelta(days=5) - - -@fixture -def new_order_response( - stapi_backend: TestBackend, - stapi_client: TestClient, - allowed_payloads: list[OpportunityRequest], -) -> Generator[Response, None, None]: - stapi_backend._allowed_payloads = allowed_payloads - - res = stapi_client.post( - "/orders", - json=allowed_payloads[0].model_dump(), - ) - - assert res.status_code == status.HTTP_201_CREATED - assert res.headers["Content-Type"] == "application/geo+json" - yield res - - -def test_new_order_location_header_matches_self_link(new_order_response: Response): - order = new_order_response.json() - assert new_order_response.headers["Location"] == str( - find_link(order["links"], "self")["href"] - ) - - -@fixture -def get_order_response( - stapi_client: TestClient, new_order_response: Response -) -> Generator[Response, None, None]: - order_id = new_order_response.json()["id"] - - res = stapi_client.get(f"/orders/{order_id}") - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/geo+json" - yield res - - -def test_get_order_properties(get_order_response: Response, allowed_payloads): - order = get_order_response.json() - - assert order["geometry"] == { - "type": "Point", - "coordinates": list(allowed_payloads[0].geometry.coordinates), - } - - assert ( - order["properties"]["datetime"] == allowed_payloads[0].model_dump()["datetime"] - ) diff --git a/stapi_fastapi_tle/tests/product_test.py b/stapi_fastapi_tle/tests/product_test.py deleted file mode 100644 index 8e401e8..0000000 --- a/stapi_fastapi_tle/tests/product_test.py +++ /dev/null @@ -1,44 +0,0 @@ -from warnings import warn - -from fastapi import status -from fastapi.testclient import TestClient - -from stapi_fastapi.models.product import Product -from stapi_fastapi_test_backend.backend import TestBackend - -from .utils import find_link -from .warnings import StapiSpecWarning - - -def test_products_response(stapi_client: TestClient): - res = stapi_client.get("/products") - - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/json" - - data = res.json() - - assert data["type"] == "ProductCollection" - assert isinstance(data["products"], list) - - -def test_product_response_self_link( - products: list[Product], - stapi_backend: TestBackend, - stapi_client: TestClient, - url_for, -): - stapi_backend._products = products - - res = stapi_client.get("/products/mock:standard") - - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/json" - - data = res.json() - link = find_link(data["links"], "self") - if link is None: - warn(StapiSpecWarning("GET /products Link[rel=self] should exist")) - else: - assert link["type"] == "application/json" - assert link["href"] == url_for("/products/mock:standard") diff --git a/stapi_fastapi_tle/tests/root_test.py b/stapi_fastapi_tle/tests/root_test.py deleted file mode 100644 index e1257df..0000000 --- a/stapi_fastapi_tle/tests/root_test.py +++ /dev/null @@ -1,49 +0,0 @@ -from warnings import warn - -from fastapi import status -from fastapi.testclient import TestClient -from pytest import fixture - -from .utils import find_link -from .warnings import StapiSpecWarning - - -@fixture -def data(stapi_client: TestClient): - res = stapi_client.get("/") - - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/json" - - yield res.json() - - -def test_root(stapi_client: TestClient, url_for): - res = stapi_client.get("/") - - assert res.status_code == status.HTTP_200_OK - assert res.headers["Content-Type"] == "application/json" - - data = res.json() - - link = find_link(data["links"], "self") - if link is None: - warn(StapiSpecWarning("GET / Link[rel=self] should exist")) - else: - assert link["type"] == "application/json" - assert link["href"] == url_for("/") - - link = find_link(data["links"], "service-description") - if link is None: - warn(StapiSpecWarning("GET / Link[rel=service-description] should exist")) - - else: - assert link["type"] == "application/json" - assert str(link["href"]) == url_for("/openapi.json") - - link = find_link(data["links"], "service-docs") - if link is None: - warn(StapiSpecWarning("GET / Link[rel=service-docs] should exist")) - else: - assert link["type"] == "text/html" - assert str(link["href"]) == url_for("/docs") diff --git a/stapi_fastapi_tle/tests/utils.py b/stapi_fastapi_tle/tests/utils.py deleted file mode 100644 index fdb3342..0000000 --- a/stapi_fastapi_tle/tests/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -def find_link(links: list[dict], rel: str) -> dict | None: - return next((link for link in links if link["rel"] == rel), None) diff --git a/stapi_fastapi_tle/tests/warnings.py b/stapi_fastapi_tle/tests/warnings.py deleted file mode 100644 index a8b7062..0000000 --- a/stapi_fastapi_tle/tests/warnings.py +++ /dev/null @@ -1,2 +0,0 @@ -class StapiSpecWarning(Warning): - pass From f345a98fc0a56f50d602eda1664409437f13fa2a Mon Sep 17 00:00:00 2001 From: Josh Cole Date: Wed, 9 Oct 2024 13:18:31 -0700 Subject: [PATCH 2/2] Improve the readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 63f514c..42ee818 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ This is an example implementation for https://github.com/stapi-spec/stapi-fastapi generating opportunity previews based on a TLE. +## Initial Setup + +You can install all the dependencies with poetry, like this: + +```bash +poetry install +``` + ## Configuration The mock backend uses SQLite/Spatialite as storage, therefore the