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 envelopes — LinkedCollection[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, with every negotiated media type auto-documented in OpenAPI from one representation list), 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.

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.

Metadata

Release files for gazebo 0.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gazebo 0.10.0
File Size Uploaded
gazebo-0.10.0.tar.gz 270.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gazebo 0.10.0
File Interpreter ABI Platform
gazebo-0.10.0-py3-none-any.whl Python 3 none any Details

Total release size: 370.5 kB

Release files / gazebo-0.10.0.tar.gz

Download URL gazebo-0.10.0.tar.gz
Size 270.5 kB
Tags Source
SHA-256 checksum
How to use checksums
570df8a4cb8c3f5b599d5032a542d7292c8a41a142543e5d0c27bbde9b13b085
BLAKE2b-256 checksum
How to use checksums
771183b57fa1ff7351a8bcae9bd52d238989fc5392efd9d900320c34642bb21f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release files / gazebo-0.10.0-py3-none-any.whl

Download URL gazebo-0.10.0-py3-none-any.whl
Size 99.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
92cee7b20b8c0c3e27bd6703e9fce85bd1d192aec2e55ccfd204239023858a93
BLAKE2b-256 checksum
How to use checksums
f0c9099e24d1573c74f5e63588d42d6a68764a612d5af8896872ce5b3935ce5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.0 This release

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release 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