Skip to content

Repository files navigation

maping-client

Zero-config RED-metrics client for FastAPI/Starlette services, reporting to a mAPI-ng collector.

Open source. Start free on the hosted service (no card), or self-host the complete MIT stack.

Aggregates per-endpoint rate/errors/duration into a DDSketch client-side and ships batched summaries over the Connect unary protocol, matching the wire contract used by mAPI-ng's Go clients (maping/client). No MAPING_KEY set means the middleware is a no-op: safe to add to any app.

Hosted or self-hosted

This client behaves identically either way: only the env vars you set differ.

  • Hosted, forever free, no card. Sign up at mapi-ng.com, create a key, set MAPING_KEY. That is the whole setup: the client already defaults to the hosted ingest endpoint, so MAPING_ENDPOINT is not needed.
  • Self-hosted. Run the complete MIT stack yourself (make local for dev, make up for prod, see arhuman/maping). Set MAPING_KEY for your own instance and MAPING_ENDPOINT to point at it.

Nothing here is held back to push you toward the hosted plan: the wire contract, the server, and this client are all MIT. Start hosted and move to self-hosting later, or the reverse, at any time. Convenience, not lock-in.

Status

Early development. Wire contract is pinned from arhuman/maping's proto/maping/v1/maping.proto via scripts/sync-proto.sh; see that repo's docs/adr/ for the design rationale behind the wire format (DDSketch, ADR-0001; Connect protocol, ADR-0002).

Quickstart

Set your credentials (pick one; see "Hosted or self-hosted" above):

# Hosted (forever free, no card)export MAPING_KEY="mk_live_..."# Self-hostedexport MAPING_KEY="mk_live_..."export MAPING_ENDPOINT="https://your-collector.internal"

Then the same code either way:

fromcontextlibimportasynccontextmanagerfromfastapiimportFastAPIfrommapingimportRecorderfrommaping.asgiimportMapingMiddlewarerecorder=Recorder() # reads MAPING_KEY, and MAPING_ENDPOINT if self-hosting, from env@asynccontextmanagerasyncdeflifespan(_: FastAPI):
awaitrecorder.start()
yieldawaitrecorder.shutdown() # must run AFTER the server stops accepting requestsfastapi_app=FastAPI(lifespan=lifespan)
# Wrap the WHOLE app -- not app.add_middleware() -- so this sits outside# Starlette's ServerErrorMiddleware and observes the real final status even# after an unhandled exception. See src/maping/asgi.py for why.app=MapingMiddleware(fastapi_app, recorder=recorder)

See examples/fastapi_app.py for a fuller quickstart, including labelled errors, aborted requests, and downstream-call timing.

Running tests

git clone https://github.com/arhuman/maping-python.git
cd maping-python
pip install -e ".[dev,zstd]"
ruff check src/ tests/ examples/
mypy src/
pytest tests/ -v

Same three commands the CI workflow runs on every push and PR (see .github/workflows/_test.yml, the reusable workflow both ci.yml and release.yml call).

Contributing

  • Open an issue or PR against arhuman/maping-python.
  • Keep changes scoped: one concern per PR, with tests. ruff check, mypy, and pytest all have to pass before review; CI enforces this on every PR.
  • The wire contract under src/maping/proto/v1/ is pinned from the core arhuman/maping repo via scripts/sync-proto.sh (see PINNED_REF for the exact pin). Don't hand-edit the generated maping_pb2.py/maping_pb2.pyi; change the pin and re-run the sync script instead.

Fidelity notes

  • RED metrics (rate, errors, duration, DDSketch percentiles) match the Go client's wire contract exactly.
  • InstanceWindow/USE gauges (CPU, memory, GC, goroutine-equivalents) are best-effort approximations of the Go runtime stats the field names were originally defined for; Python's GC/concurrency model has no exact equivalent to Go's runtime introspection. See src/maping/_sampler.py.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages