Skip to content

Commit 4836ba5

Browse files
reorganize code into submodules (#106)
* move api code to submodule * add symlink * fix symlink * fastapi_stac -> stac_fastapi * update symlink * add app routes, update imports * rename * add backend submodule * add symlink * update imports * add extensions submodule, move core extensions from api * add extensions symlink * move bulk transactions to third party extensions * move tiles extension to third_party * backend.client -> backend.core * define third party clients next to the extension * remove third party clients from core * correct package names * create postgres submodule, move the sqlalchemy code * move config * update extension imports * move errors * move openapi * fix tests * add types submodule for shared types, finish moving db code, save progress on seperation of concerns... * lint everything, remove mypy for now * update pytest.ini * Updates to submodules PR (#109) * Consolidate image building to only use docker-compose The makefile was building a named image, which was the same image as the docker-compose app service image, but they were not the same name. This commit consolidates the building and running of the image through the app service of the docker-compose, and absorbes the .env.example file into the env setup in docker-compose.yml * Use docker network instead of external hosts. * Add stac_fastapi_server package. This brings in the logic of stac_api.app into it's own subpackage. This is used by the development environment to spin up stac-fastapi via docker-compose * Read joplin data locally. The remotely hosted joplin data was failing the ingest as they do not have a "links" property, which makes them invalid STAC - the previous server didn't complain, but that was breaking the ingest into stac-fastapi. This adds an empty link properties to the data downloaded from the remote location. * Add fields to ApiSettings. This pulls in the settings fields taken from stac_api.config.ApiSettings. * Don't build separate image for migration. The migration container uses the same image as the app, but before this change a new image was being built off the same docker container. This moves migraitons to both depend on app and use it's image. It also mounts the source into the volume in case the dev is testing changes to migrations or the ingest script. * Pip install subpackages in the Dockerfile * Structure dependencies in setup.py's Restructure the Dockerfile to copy only what it needs as it installs the subpackages. This will help catch dependency errors. * Delete old stac_api package * Add version to stac-fastapi-server * Add a publish script. This will be used by CI to publish on tags. * Add publish GitHub action. This will publish stac-fastapi to pypi on any tag. * Fix broken import * Pull top level package version from api version * Pin stac-pydantic to 1.3.8 * Move search to polygon method out of types. Has dependency on shapely, which can be avoided in the types subproject. * Use Optional in param as it's possible to pass None * Refactor set logic * Pin sqlalchemy to 1.3.23 * Move global settings registry into stac-fastapi.types Settings are used in the types subproject, which is the base layer. This change makes the global instance of the settings that is set by the API to be held in the types subproject to avoid circular dependencies. * Move logic to include query fields in Fields to postgres client. This logic requires settings that are specific to the postgres client. Move this logic into the postgres client subpackage and remove from the pydantic validator chain. * Fixes from pre-commit formatting and linting * lint * rename postgres package to sqlalchemy * rename PostgresSettings to SqlalchemySettings * update readme * relock types pipfile Co-authored-by: Rob Emanuele <rdemanuele@gmail.com>
1 parent bb7ec4b commit 4836ba5

96 files changed

Lines changed: 5151 additions & 706 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.env.example‎

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

‎.github/workflows/publish.yml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
jobs:
9+
release:
10+
name: release
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
15+
- name: Set up Python 3.x
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: "3.x"
19+
20+
- name: Install release dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install setuptools wheel twine
24+
25+
- name: Build and publish package
26+
env:
27+
TWINE_USERNAME: ${{ secrets.PYPI_STACUTILS_USERNAME }}
28+
TWINE_PASSWORD: ${{ secrets.PYPI_STACUTILS_PASSWORD }}
29+
run: |
30+
scripts/publish

‎.pre-commit-config.yaml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ repos:
3434
'--select=D1',
3535
# Don't require docstrings for tests
3636
'--match=(?!test).*\.py']
37-
-
38-
repo: https://github.com/pre-commit/mirrors-mypy
39-
rev: v0.770
40-
hooks:
41-
- id: mypy
42-
language_version: python3.8
43-
args: [--no-strict-optional, --ignore-missing-imports]
37+
# -
38+
# repo: https://github.com/pre-commit/mirrors-mypy
39+
# rev: v0.770
40+
# hooks:
41+
# - id: mypy
42+
# language_version: python3.8
43+
# args: [--no-strict-optional, --ignore-missing-imports]
4444
-
4545
repo: https://github.com/PyCQA/pydocstyle
4646
rev: 5.1.1

‎Dockerfile‎

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,45 @@ ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
1111

1212
ARG install_dev_dependencies=true
1313

14-
WORKDIR /app
14+
RUN mkdir -p /app
15+
16+
# Install stac_fastapi.types
17+
COPY ./stac_fastapi_types /app/stac_fastapi_types
18+
WORKDIR /app/stac_fastapi_types
19+
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
20+
21+
# Install stac_api.extensions.
22+
COPY ./stac_fastapi_extensions /app/stac_fastapi_extensions
23+
WORKDIR /app/stac_fastapi_extensions
24+
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
25+
26+
# Install stac_api.api.
27+
COPY ./stac_fastapi_api /app/stac_fastapi_api
28+
WORKDIR /app/stac_fastapi_api
29+
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
30+
31+
# Install stac_api.sqlalchemy.
32+
COPY ./stac_fastapi_sqlalchemy /app/stac_fastapi_sqlalchemy
33+
WORKDIR /app/stac_fastapi_sqlalchemy
34+
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
1535

16-
COPY Pipfile Pipfile.lock setup.py ./
36+
# Install stac_api.server.
37+
COPY ./stac_fastapi_server /app/stac_fastapi_server
38+
WORKDIR /app/stac_fastapi_server
1739
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
1840

19-
COPY . ./
41+
# Install base package
42+
COPY ./setup.py /app/
43+
COPY ./Pipfile /app/
44+
COPY ./Pipfile.lock /app/
45+
WORKDIR /app
46+
RUN pipenv install --deploy --ignore-pipfile ${install_dev_dependencies:+--dev}
2047

2148
ENV APP_HOST=0.0.0.0
2249
ENV APP_PORT=80
2350
ENV RELOAD="true"
2451

2552
ENTRYPOINT ["pipenv", "run"]
26-
CMD if [ "$RELOAD" ]; then uvicorn stac_api.app:app --host=${APP_HOST} --port=${APP_PORT} --reload ; \
27-
else gunicorn stac_api.app:app --preload -k uvicorn.workers.UvicornWorker --bind ${APP_HOST}:${APP_PORT} --log-level info; fi
53+
54+
CMD if [ "$RELOAD" ]; then uvicorn stac_fastapi.server.app:app --host=${APP_HOST} --port=${APP_PORT} --reload ; \
55+
else gunicorn stac_fastapi.server.app:app --preload -k uvicorn.workers.UvicornWorker --bind ${APP_HOST}:${APP_PORT} --log-level info; fi

‎Makefile‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
APP_HOST ?= 0.0.0.0
33
APP_PORT ?= 8080
44
EXTERNAL_APP_PORT ?= ${APP_PORT}
5-
run_docker = docker run -it --rm \
5+
run_docker = docker-compose run --rm \
66
-p ${EXTERNAL_APP_PORT}:${APP_PORT} \
7-
-v $(shell pwd):/app \
8-
--env APP_HOST=${APP_HOST} \
9-
--env APP_PORT=${APP_PORT} \
10-
--env-file .env.example \
11-
stac-utils/stac-api:latest
7+
-e APP_HOST=${APP_HOST} \
8+
-e APP_PORT=${APP_PORT} \
9+
app
1210

1311
.PHONY: image
1412
image:
15-
docker build -t stac-utils/stac-api:latest .
13+
docker-compose build app
1614

1715
.PHONY: docker-run
1816
docker-run: image

‎README.md‎

Lines changed: 17 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11

22
<p align="center">
3-
<p align="center">Arturo's STAC compliant API implementation.</p>
3+
<p align="center">sta.</p>
44
</p>
55

66
<p align="center">
7-
<a href="https://github.com/stac-utils/arturo-stac-api/actions?query=workflow%3Acicd" target="_blank">
8-
<img src="https://github.com/stac-utils/arturo-stac-api/workflows/arturo-stac-api/badge.svg" alt="Test">
7+
<a href="https://github.com/stac-utils/stac-fastapi/actions?query=workflow%3Acicd" target="_blank">
8+
<img src="https://github.com/stac-utils/stac-fastapi/workflows/stac-fastapi/badge.svg" alt="Test">
99
</a>
10-
<a href="https://codecov.io/gh/stac-utils/arturo-stac-api" target="_blank">
11-
<img src="https://codecov.io/gh/stac-utils/arturo-stac-api/branch/master/graph/badge.svg" alt="Coverage">
10+
<a href="https://pypi.org/project/stac-fastapi" target="_blank">
11+
<img src="https://img.shields.io/pypi/v/stac-fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
1212
</a>
13-
<a href="https://pypi.org/project/arturo-stac-api" target="_blank">
14-
<img src="https://img.shields.io/pypi/v/arturo-stac-api?color=%2334D058&label=pypi%20package" alt="Package version">
15-
</a>
16-
<a href="https://github.com/stac-utils/arturo-stac-api/blob/master/LICENSE" target="_blank">
17-
<img src="https://img.shields.io/github/license/stac-utils/arturo-stac-api.svg" alt="Downloads">
13+
<a href="https://github.com/stac-utils/stac-fastapi/blob/master/LICENSE" target="_blank">
14+
<img src="https://img.shields.io/github/license/stac-utils/stac-fastapi.svg" alt="Downloads">
1815
</a>
1916
</p>
2017

@@ -23,59 +20,24 @@
2320

2421
---
2522

26-
**Documentation**: [https://stac-utils.github.io/arturo-stac-api/](https://stac-utils.github.io/arturo-stac-api/)
23+
**Documentation**: [https://stac-utils.github.io/stac-fastapi/](https://stac-utils.github.io/stac-fastapi/)
2724

28-
**Source Code**: [https://github.com/stac-utils/arturo-stac-api](https://github.com/stac-utils/arturo-stac-api)
25+
**Source Code**: [https://github.com/stac-utils/stac-fastapi](https://github.com/stac-utils/stac-fastapi)
2926

3027
---
3128

32-
Python library for building a STAC compliant FastAPI application. It provides:
33-
- An API layer which enforces the [stac-api spec](https://github.com/radiantearth/stac-api-spec) and allows users
34-
to customize how the API interacts with their data through dependency injection.
35-
- A PostGIS implementation using [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy)/[geoalchemy2](https://geoalchemy-2.readthedocs.io/en/latest/).
29+
Python library for building a STAC compliant FastAPI application. The project is split up into several namespace
30+
packages:
31+
- **stac_fastapi.api**: An API layer which enforces the [stac-api-spec](https://github.com/radiantearth/stac-api-spec).
32+
- **stac_fastapi.extensions**: Abstract base classes for [STAC API extensions](https://github.com/radiantearth/stac-api-spec/blob/master/extensions.md) and third-party extensions.
33+
- **stac_fastapi.server**: Standalone FastAPI server for the application.
34+
- **stac_fastapi.sqlalchemy**: Postgres backend implementation with sqlalchemy.
35+
- **stac_fastapi.types**: Shared types and abstract base classes used by the library.
3636

3737
Initially developed by [arturo-ai](https://github.com/arturo-ai).
3838

3939
```
40-
pip install arturo-stac-api
41-
```
42-
43-
## Usage
44-
```python
45-
from stac_api.api.app import StacApi
46-
from stac_api.clients.postgres.core import CoreCrudClient
47-
from stac_api.clients.postgres.session import Session
48-
from stac_api.config import ApiSettings
49-
50-
settings = ApiSettings()
51-
api = StacApi(
52-
settings=settings,
53-
client=CoreCrudClient(
54-
session=Session(settings.reader_connection_string, settings.writer_connection_string)
55-
),
56-
)
57-
58-
# FastAPI application
59-
app = api.app
60-
```
61-
62-
## Project Structure
63-
```
64-
.
65-
├── alembic # Database migrations
66-
│   └── versions # Migration versions
67-
├── scripts # Scripts for local development
68-
├── stac_api
69-
│   ├── api # API layer
70-
│   ├── clients
71-
│   │   ├── postgres # Postgres CRUD client
72-
│   │   └── tiles # OGC Tiles API client
73-
│   ├── models # Pydantic and ORM models
74-
│   └── utils # Helper functions
75-
└── tests
76-
├── api # Test api creation
77-
├── clients # Test application logic
78-
└── resources # Test api endpoints
40+
pip install stac-fastapi
7941
```
8042

8143
## Local Development

‎alembic/env.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828

2929
def get_connection_url() -> str:
3030
"""
31-
Get connection URL from environment variables (see `.env.example`)
31+
Get connection URL from environment variables
32+
(see environment variables set in docker-compose)
3233
"""
3334
postgres_user = os.environ["POSTGRES_USER"]
3435
postgres_pass = os.environ["POSTGRES_PASS"]

‎docker-compose.yml‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ version: '3'
33
services:
44
app:
55
container_name: stac-api
6+
image: stac-utils/stac-api
67
build:
78
context: .
89
dockerfile: Dockerfile
@@ -14,17 +15,19 @@ services:
1415
- POSTGRES_USER=username
1516
- POSTGRES_PASS=password
1617
- POSTGRES_DBNAME=postgis
17-
- POSTGRES_HOST_READER=host.docker.internal
18-
- POSTGRES_HOST_WRITER=host.docker.internal
19-
- POSTGRES_PORT=5439
18+
- POSTGRES_HOST_READER=database
19+
- POSTGRES_HOST_WRITER=database
20+
- POSTGRES_PORT=5432
21+
- WEB_CONCURRENCY=10
22+
- VSI_CACHE=TRUE
23+
- GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=YES
24+
- GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR
2025
ports:
2126
- "8081:8081"
2227
volumes:
2328
- ./:/app
2429
depends_on:
2530
- database
26-
extra_hosts:
27-
- "host.docker.internal:host-gateway"
2831

2932
database:
3033
container_name: stac-db
@@ -37,19 +40,22 @@ services:
3740
- "5439:5432"
3841

3942
migration:
40-
build:
41-
context: .
42-
dockerfile: Dockerfile
43+
image: stac-utils/stac-api
4344
environment:
4445
- ENVIRONMENT=development
4546
- POSTGRES_USER=username
4647
- POSTGRES_PASS=password
4748
- POSTGRES_DBNAME=postgis
48-
- POSTGRES_HOST=host.docker.internal
49-
- POSTGRES_PORT=5439
49+
- POSTGRES_HOST=database
50+
- POSTGRES_PORT=5432
51+
volumes:
52+
- ./:/app
5053
command: >
5154
bash -c "sleep 10 && alembic upgrade head && python scripts/ingest_joplin.py"
5255
depends_on:
5356
- database
54-
extra_hosts:
55-
- "host.docker.internal:host-gateway"
57+
- app
58+
59+
networks:
60+
default:
61+
name: stac-fastapi-network

‎pytest.ini‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[pytest]
22
testpaths = tests
3-
addopts = -sv --cov=stac_api --cov-fail-under=85 --cov-report=term-missing
3+
addopts = -sv

‎scripts/ingest_joplin.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
"""Ingest sample data during docker-compose"""
2-
2+
import json
3+
import os
34
from urllib.parse import urljoin
45

56
import requests
67

78
bucket = "arturo-stac-api-test-data"
8-
app_host = "http://host.docker.internal:8081"
9+
app_host = "http://app:8081"
10+
911

12+
def ingest_joplin_data(data_dir="/app/tests/data/joplin"):
13+
"""ingest data."""
1014

11-
def ingest_joplin_data():
12-
"""ingest data"""
13-
r = requests.get(f"https://{bucket}.s3.amazonaws.com/joplin/collection.json")
14-
collection = r.json()
15+
with open(os.path.join(data_dir, "collection.json")) as f:
16+
collection = json.load(f)
1517

1618
r = requests.post(urljoin(app_host, "/collections"), json=collection)
1719
if r.status_code not in (200, 409):
1820
r.raise_for_status()
1921

20-
r = requests.get(f"https://{bucket}.s3.amazonaws.com/joplin/index.geojson")
21-
index = r.json()
22+
with open(os.path.join(data_dir, "index.geojson")) as f:
23+
index = json.load(f)
24+
2225
for feat in index["features"]:
2326
del feat["stac_extensions"]
2427
r = requests.post(

0 commit comments

Comments
 (0)