Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ COPY scripts/wait-for-it.sh scripts/wait-for-it.sh
COPY pyproject.toml pyproject.toml
COPY README.md README.md

RUN python -m pip install .[server]
RUN rm -rf stac_fastapi .toml README.md
RUN python -m pip install -e .[server,catalogs]

RUN groupadd -g 1000 user && \
useradd -u 1000 -g user -s /bin/bash -m user
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.tests
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ USER newuser
WORKDIR /app
COPY . /app

RUN python -m pip install . --user --group dev
RUN python -m pip install .[catalogs] --user --group dev
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ run = docker compose run --rm \
-e APP_PORT=${APP_PORT} \
app

runtests = docker compose run --rm tests
runtests = docker compose -f compose-tests.yml run --rm tests

.PHONY: image
image:
Expand All @@ -22,7 +22,7 @@ docker-run: image

.PHONY: docker-run-nginx-proxy
docker-run-nginx-proxy:
docker compose -f docker-compose.yml -f docker-compose.nginx.yml up
docker compose -f compose.yml -f docker-compose.nginx.yml up

.PHONY: docker-shell
docker-shell:
Expand All @@ -32,6 +32,10 @@ docker-shell:
test:
$(runtests) /bin/bash -c 'export && python -m pytest /app/tests/ --log-cli-level $(LOG_LEVEL)'

.PHONY: test-catalogs
test-catalogs:
$(runtests) /bin/bash -c 'export && python -m pytest /app/tests/test_catalogs.py -v --log-cli-level $(LOG_LEVEL)'

.PHONY: run-database
run-database:
docker compose run --rm database
Expand Down
14 changes: 10 additions & 4 deletions docker-compose.yml → compose-tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
services:
app:
image: stac-utils/stac-fastapi-pgstac
restart: always
build: .
environment:
- APP_HOST=0.0.0.0
Expand All @@ -20,15 +21,19 @@ services:
- DB_MAX_CONN_SIZE=1
- USE_API_HYDRATE=${USE_API_HYDRATE:-false}
- ENABLE_TRANSACTIONS_EXTENSIONS=TRUE
ports:
- "8082:8082"
- ENABLE_CATALOGS_ROUTE=TRUE
# ports:
# - "8082:8082"
depends_on:
- database
command: bash -c "scripts/wait-for-it.sh database:5432 && python -m stac_fastapi.pgstac.app"
command: bash -c "scripts/wait-for-it.sh database:5432 && uvicorn stac_fastapi.pgstac.app:app --host 0.0.0.0 --port 8082 --reload"
develop:
watch:
- action: rebuild
- action: sync
path: ./stac_fastapi/pgstac
target: /app/stac_fastapi/pgstac
- action: rebuild
path: ./setup.py

tests:
image: stac-utils/stac-fastapi-pgstac-test
Expand All @@ -40,6 +45,7 @@ services:
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=1
- USE_API_HYDRATE=${USE_API_HYDRATE:-false}
- ENABLE_CATALOGS_ROUTE=TRUE
command: bash -c "python -m pytest -s -vv"

database:
Expand Down
91 changes: 91 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
services:
app:
image: stac-utils/stac-fastapi-pgstac
restart: always
build: .
environment:
- APP_HOST=0.0.0.0
- APP_PORT=8082
- RELOAD=true
- ENVIRONMENT=local
- PGUSER=username
- PGPASSWORD=password
- PGDATABASE=postgis
- PGHOST=database
- PGPORT=5432
- WEB_CONCURRENCY=10
- VSI_CACHE=TRUE
- GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=YES
- GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=1
- USE_API_HYDRATE=${USE_API_HYDRATE:-false}
- ENABLE_TRANSACTIONS_EXTENSIONS=TRUE
- ENABLE_CATALOGS_ROUTE=TRUE
# ports:
# - "8082:8082"
depends_on:
- database
command: bash -c "scripts/wait-for-it.sh database:5432 && uvicorn stac_fastapi.pgstac.app:app --host 0.0.0.0 --port 8082 --reload"
develop:
watch:
- action: sync
path: ./stac_fastapi/pgstac
target: /app/stac_fastapi/pgstac
- action: rebuild
path: ./setup.py

database:
image: ghcr.io/stac-utils/pgstac:v0.9.8
environment:
- POSTGRES_USER=username
- POSTGRES_PASSWORD=password
- POSTGRES_DB=postgis
- PGUSER=username
- PGPASSWORD=password
- PGDATABASE=postgis
ports:
- "5439:5432"
command: postgres -N 500

# Load joplin demo dataset into the PGStac Application
loadjoplin:
image: stac-utils/stac-fastapi-pgstac
environment:
- ENVIRONMENT=development
volumes:
- ./testdata:/tmp/testdata
- ./scripts:/tmp/scripts
command: >
/bin/sh -c "
scripts/wait-for-it.sh -t 60 app:8082 &&
python -m pip install pip -U &&
python -m pip install requests &&
python /tmp/scripts/ingest_joplin.py http://app:8082
"
depends_on:
- database
- app

nginx:
image: nginx
ports:
- ${STAC_FASTAPI_NGINX_PORT:-8080}:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app-nginx
command: [ "nginx-debug", "-g", "daemon off;" ]

app-nginx:
extends:
service: app
command: >
bash -c "
scripts/wait-for-it.sh database:5432 &&
uvicorn stac_fastapi.pgstac.app:app --host 0.0.0.0 --port 8082 --proxy-headers --forwarded-allow-ips=* --root-path=/api/v1/pgstac
"

networks:
default:
name: stac-fastapi-network
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "stac-fastapi-pgstac"
description = "An implementation of STAC API based on the FastAPI framework and using the pgstac backend."
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.12"
license = "MIT"
authors = [
{ name = "David Bitner", email = "david@developmentseed.org" },
Expand Down Expand Up @@ -55,6 +55,9 @@ validation = [
server = [
"uvicorn[standard]==0.38.0"
]
catalogs = [
"stac-fastapi-catalogs-extension>=0.1.2",
]

[dependency-groups]
dev = [
Expand Down
52 changes: 46 additions & 6 deletions stac_fastapi/pgstac/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
If the variable is not set, enables all extensions.
"""

import logging
import os
from contextlib import asynccontextmanager
from typing import cast
Expand Down Expand Up @@ -45,13 +46,35 @@
from stac_fastapi.pgstac.config import Settings
from stac_fastapi.pgstac.core import CoreCrudClient, health_check
from stac_fastapi.pgstac.db import close_db_connection, connect_to_db
from stac_fastapi.pgstac.extensions import FreeTextExtension, QueryExtension
from stac_fastapi.pgstac.extensions import (
DatabaseLogic,
FreeTextExtension,
QueryExtension,
)
from stac_fastapi.pgstac.extensions.catalogs.catalogs_client import CatalogsClient
from stac_fastapi.pgstac.extensions.filter import FiltersClient
from stac_fastapi.pgstac.transactions import BulkTransactionsClient, TransactionsClient
from stac_fastapi.pgstac.types.search import PgstacSearch

logger = logging.getLogger(__name__)

# Optional catalogs extension (optional dependency)
try:
from stac_fastapi_catalogs_extension import CatalogsExtension
except ImportError:
CatalogsExtension = None

settings = Settings()


def _is_env_flag_enabled(name: str) -> bool:
"""Return True if the given env var is enabled.

Accepts common truthy values ("yes", "true", "1") case-insensitively.
"""
return os.environ.get(name, "").lower() in ("yes", "true", "1")


# search extensions
search_extensions_map: dict[str, ApiExtension] = {
"query": QueryExtension(),
Expand Down Expand Up @@ -98,11 +121,7 @@

application_extensions: list[ApiExtension] = []

with_transactions = os.environ.get("ENABLE_TRANSACTIONS_EXTENSIONS", "").lower() in [
"yes",
"true",
"1",
]
with_transactions = _is_env_flag_enabled("ENABLE_TRANSACTIONS_EXTENSIONS")
if with_transactions:
application_extensions.append(
TransactionExtension(
Expand Down Expand Up @@ -158,6 +177,27 @@
collections_get_request_model = collection_search_extension.GET
application_extensions.append(collection_search_extension)

# Optional catalogs route
ENABLE_CATALOGS_ROUTE = _is_env_flag_enabled("ENABLE_CATALOGS_ROUTE")
logger.info("ENABLE_CATALOGS_ROUTE is set to %s", ENABLE_CATALOGS_ROUTE)

if ENABLE_CATALOGS_ROUTE:
if CatalogsExtension is None:
logger.warning(
"ENABLE_CATALOGS_ROUTE is set to true, but the catalogs extension is not installed. "
"Please install it with: pip install stac-fastapi-core[catalogs].",
)
else:
try:
catalogs_extension = CatalogsExtension(
client=CatalogsClient(database=DatabaseLogic()),
enable_transactions=with_transactions,
)
application_extensions.append(catalogs_extension)
print("CatalogsExtension enabled successfully.")
except Exception as e: # pragma: no cover - defensive
logger.warning("Failed to initialize CatalogsExtension: %s", e)


@asynccontextmanager
async def lifespan(app: FastAPI):
Expand Down
10 changes: 9 additions & 1 deletion stac_fastapi/pgstac/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"""pgstac extension customisations."""

from .catalogs.catalogs_client import CatalogsClient
from .catalogs.catalogs_database_logic import DatabaseLogic
from .filter import FiltersClient
from .free_text import FreeTextExtension
from .query import QueryExtension

__all__ = ["QueryExtension", "FiltersClient", "FreeTextExtension"]
__all__ = [
"QueryExtension",
"FiltersClient",
"FreeTextExtension",
"CatalogsClient",
"DatabaseLogic",
]
Loading
Loading