Skip to main content

gazebo

Documentation

Everything needed to build OGC-style APIs, under one roof.

gazebo packages the recurring machinery of OGC-style services so it doesn't get re-implemented per project:

  • Deferred links — a Link model whose href can be a callable resolved at serialization time, so links are built without a request in hand; including RFC 6570 templated links (templated: true) that leave path/query variables for the client to expand.
  • Collection envelopesLinkedCollection[T]: items + links + counts, with a configurable items alias (features, records, ...), plus first-class GeoJSON Feature/FeatureCollection for OGC API Features.
  • Typed injection & state — a small, framework-agnostic DI container (gazebo.di) plus a FastAPI app (GazeboApp) that delivers app- and request-scoped resources as typed parameters, with teardown and parameter-based (not global-mutation) test overrides.
  • Proxy-aware URLs — pure-ASGI middleware that honors X-Forwarded-Proto/Host/Prefix (with pluggable trust), so generated links are correct behind a load balancer.
  • The OGC request/response surface — RFC 7807 problems (with a reusable ProblemType/ProblemRegistry catalog of stable, linkable type URIs, which the framework's own validation/param 400/422 errors can draw from too), landing pages + conformance (a RootRouter that emits service-desc/ service-doc and derives its conformance declaration from the running app), pagination, content negotiation (?f= then Accept), typed OGC query params (bbox/datetime/crs/f) — fully self-documented in OpenAPI and available both as Depends adapters and as field types you fold into your own query model (the closed-set crs/f as StrEnum bases you subclass) — CQL2 filtering + sortby, conditional requests (ETag / 304), RFC 8288 Link: headers, and typed Rel/MediaType constants.
  • A pytest plugin — opt-in helpers that assert the OGC-ness of your service: link/problem assertions and a pagination driver that walks next to exhaustion.

The core (gazebo) depends only on pydantic. Framework integration, GeoJSON, CQL2 filtering, a self-documenting serve CLI, and the test helpers are opt-in extras.

[!NOTE] This is an experiment using AI to refine a number of patterns I've established building out APIs over the years. The current implementation mainly targets use with FastAPI, but I've tried to keep the core abstractions agnostic to the framework, and recognize FastAPI is not the only framework that could value from these things.

I acknowledge the documentation is AI slop and does not clearly express the value of these abstractions, but I think the code, while an early version and subject to change, is mostly solid and solves some key problems in convenient and clever ways. The primary goals are to reduce boilerplate and make implementing more robust patterns easier, and I think those goals are realized here. Some features are more experimental than others, but I think everything in here is potentially useful. If not, let me know why. If problems arise, tell me. Issues and pull requests are excellent vehicles for feedback.

Install

pip install gazebo             # core: pydantic only
pip install 'gazebo[fastapi]'  # + the GazeboApp / FastAPI glue
pip install 'gazebo[cli]'      # + the self-documenting serve CLI toolkit (server-agnostic)
pip install 'gazebo[uvicorn]'  # + a batteries-included uvicorn serve command
pip install 'gazebo[geojson]'  # + GeoJSON Feature / FeatureCollection
pip install 'gazebo[cql2]'     # + CQL2 filtering (cql2-rs engine)
pip install 'gazebo[test]'     # + the pytest plugin

Requires Python 3.12+. Full documentation lives at teotl.dev/gazebo.

Quickstart

from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import Annotated

from fastapi import Request

from gazebo.collection import LinkedCollection
from gazebo.link import Link
from gazebo.ext.fastapi import GazeboApp, GazeboRouter, Overrides, Providers


@dataclass
class Settings:
    dsn: str = 'postgres://localhost/app'

    @classmethod
    def __provide__(cls) -> 'Settings':
        return cls()


class Database:
    def __init__(self, dsn: str) -> None:
        self.dsn = dsn

    @classmethod
    @asynccontextmanager
    async def __provide__(cls, settings: Settings) -> AsyncIterator['Database']:
        db = cls(settings.dsn)
        try:
            yield db          # built once (app scope); teardown on shutdown
        finally:
            ...               # await db.close()


@dataclass
class User:                   # request-scoped; derives from the request
    name: str

    @classmethod
    async def __provide__(cls, request: Request) -> 'User':
        return cls(request.headers.get('authorization', 'anon'))


class Things(LinkedCollection[dict], items_alias='things'):
    pass


router = GazeboRouter()


@router.get('/things', response_model=Things)
async def list_things(db: Database, user: User, limit: int = 10):
    items = [{'id': i, 'owner': user.name} for i in range(limit)]
    return Things(items=items, links=[Link.self_link(), Link.root_link()])


def create_app(overrides: Overrides | None = None) -> GazeboApp:
    providers = Providers()
    providers.app(Settings).app(Database).request(User)
    app = GazeboApp(providers, overrides=overrides)
    app.include_router(router)

    @app.get('/', name='landing')
    async def landing():
        return {'service': 'things'}

    return app


app = create_app()

db and user are injected by type — db once per app, user per request. Tests override by parameter, never by mutating a global:

from fastapi.testclient import TestClient

def test_things():
    overrides = Overrides().set(Settings, Settings(dsn='sqlite://'))
    with TestClient(create_app(overrides)) as client:
        body = client.get('/things?limit=2', headers={'authorization': 'alice'}).json()
        assert body['numberReturned'] == 2

GazeboApp and GazeboRouter are an intended pair, but you can mix in plain or third-party routers, upgrade() an app you didn't construct, and mount a GazeboApp under a root app. The documentation covers composition, injecting external types, content negotiation, conditional requests, and the rest.

Example app

examples/garden/ is Gazebo Gardens — a complete, standalone OGC-style API (a multi-tenant plant catalog) that exercises every feature: injection with app/request scopes and teardown, qualified bindings, deferred + paginated links, collection envelopes, RFC 7807 problems with a ProblemType catalog, CQL2 filtering and sortby, a RootRouter service landing with conformance, proxy-aware URLs, health, and request-id logging. It's its own project with its own pyproject.toml, so:

cd examples/garden
uv run garden          # serve on http://127.0.0.1:8000
uv run pytest          # its test suite

See examples/garden/README.md for a feature map and curl recipes.

Docs

Full documentation — guides, how-tos, and the generated API reference — lives at teotl.dev/gazebo.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gazebo-0.9.3.tar.gz (263.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gazebo-0.9.3-py3-none-any.whl (97.4 kB view details)

Uploaded Python 3

File details

Details for the file gazebo-0.9.3.tar.gz.

File metadata

  • Download URL: gazebo-0.9.3.tar.gz
  • Upload date:
  • Size: 263.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for gazebo-0.9.3.tar.gz
Algorithm Hash digest
SHA256 d2d61c2262185321e9875a9af2264dfc583246bbfb0bbf004ecb48e40fa5d849
MD5 64b34113c0a93bacca89ad0b242492da
BLAKE2b-256 19a95da5b45cc659d8eaa21fe6edb6e80fdf3c41707140d3badafd25d2942713

See more details on using hashes here.

Provenance

The following attestation bundles were made for gazebo-0.9.3.tar.gz:

Publisher: release.yml on jkeifer/gazebo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gazebo-0.9.3-py3-none-any.whl.

File metadata

  • Download URL: gazebo-0.9.3-py3-none-any.whl
  • Upload date:
  • Size: 97.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for gazebo-0.9.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e6745ab4ca2e0ffdfa48ea9c6ed5897020fe6f9ab31239d805d5a9f0d76d6550
MD5 1e3cb6c3bd1288186963f5a4ea2fa4ab
BLAKE2b-256 9dd31abef357412ef5717a87e68345bacddff04a8764669320b3af019f893623

See more details on using hashes here.

Provenance

The following attestation bundles were made for gazebo-0.9.3-py3-none-any.whl:

Publisher: release.yml on jkeifer/gazebo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.10.0

2 files

This release

0.9.3 This release

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page