Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reorganize code into submodules #106

Merged
merged 33 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8a78e3e
move api code to submodule
geospatial-jeff Mar 3, 2021
c7bb70f
add symlink
geospatial-jeff Mar 3, 2021
07d83f9
fix symlink
geospatial-jeff Mar 3, 2021
8d3904e
fastapi_stac -> stac_fastapi
geospatial-jeff Mar 3, 2021
a8c6db1
update symlink
geospatial-jeff Mar 3, 2021
944383b
add app routes, update imports
geospatial-jeff Mar 3, 2021
02f14ff
rename
geospatial-jeff Mar 3, 2021
7dc6836
add backend submodule
geospatial-jeff Mar 3, 2021
5713a5e
add symlink
geospatial-jeff Mar 3, 2021
a4e6133
update imports
geospatial-jeff Mar 3, 2021
39452be
add extensions submodule, move core extensions from api
geospatial-jeff Mar 3, 2021
2eec4ad
add extensions symlink
geospatial-jeff Mar 3, 2021
cbc839e
move bulk transactions to third party extensions
geospatial-jeff Mar 3, 2021
67cc8a8
move tiles extension to third_party
geospatial-jeff Mar 3, 2021
4e48d07
backend.client -> backend.core
geospatial-jeff Mar 3, 2021
66f80ac
define third party clients next to the extension
geospatial-jeff Mar 3, 2021
ed9ebad
remove third party clients from core
geospatial-jeff Mar 3, 2021
d46da52
correct package names
geospatial-jeff Mar 3, 2021
778f0fa
create postgres submodule, move the sqlalchemy code
geospatial-jeff Mar 9, 2021
a702e0e
move config
geospatial-jeff Mar 9, 2021
7bcc6e8
update extension imports
geospatial-jeff Mar 9, 2021
1ce6f3d
move errors
geospatial-jeff Mar 9, 2021
52304f8
move openapi
geospatial-jeff Mar 9, 2021
be1d38e
fix tests
geospatial-jeff Mar 16, 2021
9cafb60
add types submodule for shared types, finish moving db code, save pro…
geospatial-jeff Mar 16, 2021
a07e5a8
lint everything, remove mypy for now
geospatial-jeff Mar 16, 2021
57d15a4
update pytest.ini
geospatial-jeff Mar 16, 2021
8f41986
Updates to submodules PR (#109)
lossyrob Mar 24, 2021
4c86cee
lint
geospatial-jeff Mar 24, 2021
7af256e
rename postgres package to sqlalchemy
geospatial-jeff Mar 24, 2021
c2f4ae1
rename PostgresSettings to SqlalchemySettings
geospatial-jeff Mar 24, 2021
bb211c2
update readme
geospatial-jeff Mar 25, 2021
6fec90d
relock types pipfile
geospatial-jeff Mar 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ repos:
'--select=D1',
# Don't require docstrings for tests
'--match=(?!test).*\.py']
-
repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.770
hooks:
- id: mypy
language_version: python3.8
args: [--no-strict-optional, --ignore-missing-imports]
# -
# repo: https://github.com/pre-commit/mirrors-mypy
# rev: v0.770
# hooks:
# - id: mypy
# language_version: python3.8
# args: [--no-strict-optional, --ignore-missing-imports]
-
repo: https://github.com/PyCQA/pydocstyle
rev: 5.1.1
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[pytest]
testpaths = tests
addopts = -sv --cov=stac_api --cov-fail-under=85 --cov-report=term-missing
addopts = -sv
4 changes: 3 additions & 1 deletion stac_api/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
from sqlalchemy.ext.declarative import declarative_base
from stac_pydantic.shared import DATETIME_RFC339

from stac_api import config
from stac_api.models import schemas

# from stac_api import config
from stac_fastapi.api import config

BaseModel = declarative_base()


Expand Down
4 changes: 3 additions & 1 deletion stac_api/models/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from pydantic.utils import GetterDict
from stac_pydantic.shared import DATETIME_RFC339

from stac_api import config
from stac_api.errors import DatabaseError
from stac_api.models.links import CollectionLinks, ItemLinks, filter_links

# from stac_api import config
from stac_fastapi.api import config


def resolve_links(links: list, base_url: str) -> List[Dict]:
"""Convert relative links to absolute links."""
Expand Down
4 changes: 3 additions & 1 deletion stac_api/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
from stac_pydantic.shared import Link
from stac_pydantic.utils import AutoValueEnum

from stac_api import config
from stac_api.models.decompose import CollectionGetter, ItemGetter

# from stac_api import config
from stac_fastapi.api import config

# Be careful: https://github.com/samuelcolvin/pydantic/issues/1423#issuecomment-642797287
NumType = Union[float, int]

Expand Down
1 change: 1 addition & 0 deletions stac_fastapi/api
1 change: 1 addition & 0 deletions stac_fastapi/extensions
1 change: 1 addition & 0 deletions stac_fastapi/postgres
1 change: 1 addition & 0 deletions stac_fastapi/types
17 changes: 17 additions & 0 deletions stac_fastapi_api/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = "true"

[packages]
stac-fastapi-api = {editable = "true", path = "."}

[dev-packages]
pytest = "*"
pytest-cov = "*"
pytest-asyncio = "*"
requests = "*"
pre-commit = "*"

[requires]
python_version = "3.8"
442 changes: 442 additions & 0 deletions stac_fastapi_api/Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions stac_fastapi_api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# fastapi-stac api
46 changes: 46 additions & 0 deletions stac_fastapi_api/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""stac-fastapi api submodule."""
import os
from imp import load_source

from setuptools import find_namespace_packages, setup

name = "stac-fastapi-api"
description = (
"API subpackage of fastapi-stac, a STAC compliant API layer build with FastAPI."
)

__version__ = load_source(
"stac_fastapi.api.version",
os.path.join(os.path.dirname(__file__), "stac_fastapi/api/version.py"),
).__version__ # type:ignore

install_requires = [
"attrs",
"fastapi",
"fastapi-utils",
"pydantic[dotenv]",
"stac-pydantic",
]

with open(
os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md")
) as readme_file:
readme = readme_file.read()

setup(
name=name,
python_requires=">=3.8",
description=description,
version=__version__,
long_description=readme,
long_description_content_type="text/markdown",
author=u"Arturo Engineering",
author_email="engineering@arturo.ai",
url="https://github.com/stac-utils/fastapi-stac.git",
packages=find_namespace_packages(),
# py_modules=[splitext(basename(path))[0] for path in glob("fastapi_stac/*.py")],
include_package_data=False,
install_requires=install_requires,
license="MIT",
keywords=["stac", "fastapi", "imagery", "raster", "catalog", "STAC"],
)
1 change: 1 addition & 0 deletions stac_fastapi_api/stac_fastapi/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""api submodule."""
240 changes: 240 additions & 0 deletions stac_fastapi_api/stac_fastapi/api/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
"""fastapi app creation."""
from typing import Any, Dict, List, Optional, Type

import attr
from fastapi import APIRouter, FastAPI
from fastapi.openapi.utils import get_openapi
from stac_pydantic import Collection, Item, ItemCollection
from stac_pydantic.api import ConformanceClasses, LandingPage

from stac_fastapi.api.config import inject_settings
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers
from stac_fastapi.api.models import (
CollectionUri,
EmptyRequest,
ItemCollectionUri,
ItemUri,
SearchGetRequest,
_create_request_model,
)
from stac_fastapi.api.routes import (
create_endpoint_from_model,
create_endpoint_with_depends,
)

# TODO: make this module not depend on `stac_fastapi.extensions`
from stac_fastapi.extensions.core import FieldsExtension
from stac_fastapi.types.config import ApiSettings
from stac_fastapi.types.core import BaseCoreClient
from stac_fastapi.types.extension import ApiExtension
from stac_fastapi.types.search import STACSearch


@attr.s
class StacApi:
"""StacApi factory.

Factory for creating a STAC-compliant FastAPI application. After instantation, the application is accessible from
the `StacApi.app` attribute.

Attributes:
settings:
API settings and configuration, potentially using environment variables.
See https://pydantic-docs.helpmanual.io/usage/settings/.
client:
A subclass of `stac_api.clients.BaseCoreClient`. Defines the application logic which is injected
into the API.
extensions:
API extensions to include with the application. This may include official STAC extensions as well as
third-party add ons.
exceptions:
Defines a global mapping between exceptions and status codes, allowing configuration of response behavior on
certain exceptions (https://fastapi.tiangolo.com/tutorial/handling-errors/#install-custom-exception-handlers).
app:
The FastAPI application, defaults to a fresh application.
"""

settings: ApiSettings = attr.ib()
client: BaseCoreClient = attr.ib()
extensions: List[ApiExtension] = attr.ib(default=attr.Factory(list))
exceptions: Dict[Type[Exception], int] = attr.ib(
default=attr.Factory(lambda: DEFAULT_STATUS_CODES)
)
app: FastAPI = attr.ib(default=attr.Factory(FastAPI))

def get_extension(self, extension: Type[ApiExtension]) -> Optional[ApiExtension]:
"""Get an extension.

Args:
extension: extension to check for.

Returns:
The extension instance, if it exists.
"""
for ext in self.extensions:
if isinstance(ext, extension):
return ext
return None

def register_core(self):
"""Register core STAC endpoints.

GET /
GET /conformance
GET /collections
GET /collections/{collectionId}
GET /collections/{collectionId}/items
GET /collection/{collectionId}/items/{itemId}
GET /search
POST /search

Injects application logic (StacApi.client) into the API layer.

Returns:
None
"""
search_request_model = _create_request_model(STACSearch)
fields_ext = self.get_extension(FieldsExtension)
router = APIRouter()
router.add_api_route(
name="Landing Page",
path="/",
response_model=LandingPage,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.landing_page, EmptyRequest
),
)
router.add_api_route(
name="Conformance Classes",
path="/conformance",
response_model=ConformanceClasses,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.conformance, EmptyRequest
),
)
router.add_api_route(
name="Get Item",
path="/collections/{collectionId}/items/{itemId}",
response_model=Item,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(self.client.get_item, ItemUri),
)
router.add_api_route(
name="Search",
path="/search",
response_model=ItemCollection if not fields_ext else None,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["POST"],
endpoint=create_endpoint_from_model(
self.client.post_search, search_request_model
),
),
router.add_api_route(
name="Search",
path="/search",
response_model=ItemCollection if not fields_ext else None,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.get_search, SearchGetRequest
),
)
router.add_api_route(
name="Get Collections",
path="/collections",
response_model=List[Collection],
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.all_collections, EmptyRequest
),
)
router.add_api_route(
name="Get Collection",
path="/collections/{collectionId}",
response_model=Collection,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.get_collection, CollectionUri
),
)
router.add_api_route(
name="Get ItemCollection",
path="/collections/{collectionId}/items",
response_model=ItemCollection,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_endpoint_with_depends(
self.client.item_collection, ItemCollectionUri
),
)
self.app.include_router(router)

def customize_openapi(self) -> Optional[Dict[str, Any]]:
"""Customize openapi schema."""
if self.app.openapi_schema:
return self.app.openapi_schema

# TODO: parametrize
openapi_schema = get_openapi(
title="Arturo STAC API", version="0.1", routes=self.app.routes
)

self.app.openapi_schema = openapi_schema
return self.app.openapi_schema

def add_health_check(self):
"""Add a health check."""
mgmt_router = APIRouter()

@mgmt_router.get("/_mgmt/ping")
async def ping():
"""Liveliness/readiness probe."""
return {"message": "PONG"}

self.app.include_router(mgmt_router, tags=["Liveliness/Readiness"])

def __attrs_post_init__(self):
"""Post-init hook.

Responsible for setting up the application upon instantiation of the class.

Returns:
None
"""
# inject settings
self.client.extensions = self.extensions

fields_ext = self.get_extension(FieldsExtension)
if fields_ext:
self.settings.default_includes = fields_ext.default_includes

inject_settings(self.settings)

self.register_core()
# register extensions
for ext in self.extensions:
ext.register(self.app)

# add health check
self.add_health_check()

# register exception handlers
add_exception_handlers(self.app, status_codes=self.exceptions)

# customize openapi
self.app.openapi = self.customize_openapi
Loading