Skip to main content

OpenFrame Microservice Suite - core SDK. Ports, tracing, telemetry, exceptions, config.

Project description

openframe-core

PyPI version Python versions Tests License Docs

Documentation · Quick Start · PyPI · Changelog


openframe-core is the foundation package of the OpenFrame Microservice Development Suite. It defines the structural contracts, telemetry primitives, and shared utilities that every other package in the ecosystem depends on.

Every package in the OpenFrame ecosystem pins openframe-core>=1.0,<2. The major version is the stability contract for the entire ecosystem.


What's inside

Module Exports
openframe.core.exceptions AdapterError · AdapterConnectionError · AdapterQueryError · AdapterNotFoundError · AdapterConfigurationError · AdapterTimeoutError
openframe.core.config BaseAdapterSettings — Pydantic BaseSettings subclass all adapters inherit
openframe.core.ports BaseRepository[T] · BaseProducer[T] · BaseConsumer[T]runtime_checkable Protocols
openframe.core.health HealthCheckping() and is_ready() Protocol
openframe.core.telemetry setup_telemetry() · get_tracer() · get_meter() · record_lifecycle_event()
openframe.core.tracing TracingProxy — zero-code async telemetry sidecar
openframe.core.middleware TelemetryMiddleware — pure ASGI middleware
openframe.core.middleware.types ASGIScope · ASGIMessage · Receive · Send · ASGIApp

Installation

pip install openframe-core
# With dev dependencies (pytest, pytest-asyncio, httpx)
pip install "openframe-core[dev]"

Quick start

Exceptions — single catch point across all adapters

from openframe.core.exceptions import AdapterError, AdapterNotFoundError, AdapterQueryError

# In an adapter — wrap driver exceptions before they leave
try:
    row = await conn.fetchrow(query, entity_id)
except SomeDriverError as exc:
    raise AdapterQueryError(
        "Query failed",
        adapter="postgres",
        operation="get",
        cause=exc,
    ) from exc

# In a service — one catch point regardless of which adapter is wired in
try:
    entity = await repo.get(entity_id)
except AdapterNotFoundError:
    return None
except AdapterError as exc:
    logger.error("Adapter failure: %s", exc)   # [postgres.get] message — caused by: ...
    raise

Config — env vars validated at startup, not first request

from openframe.core.config import BaseAdapterSettings

class PostgresSettings(BaseAdapterSettings):
    database_url: str     # reads DATABASE_URL — required
    pool_size: int = 10   # reads POOL_SIZE — optional, default 10

# Raises pydantic_core.ValidationError immediately if DATABASE_URL is not set
settings = PostgresSettings()

Ports — structural contracts, no inheritance required

from openframe.core.ports import BaseRepository

class PostgresItemRepository:
    async def get(self, entity_id: str) -> Item | None: ...
    async def list(self, limit: int, offset: int) -> tuple[list[Item], int]: ...
    async def create(self, entity: Item) -> Item: ...
    async def update(self, entity: Item) -> Item | None: ...
    async def delete(self, entity_id: str) -> bool: ...

# Structural — no BaseRepository inheritance needed
assert isinstance(PostgresItemRepository(), BaseRepository)   # True

Telemetry — OTel bootstrap in one call

from contextlib import asynccontextmanager
from openframe.core.telemetry import setup_telemetry, record_lifecycle_event

@asynccontextmanager
async def lifespan(app):
    setup_telemetry()                      # idempotent — safe to call multiple times
    record_lifecycle_event("cold_start")   # platform-agnostic lifecycle counter
    yield

Configure via environment variables — no vendor lock-in:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp.example.com"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64_token>"
export OTEL_SERVICE_NAME="my-service"
export OPENFRAME_ENV="prod"

Modal users: map MODAL_ENVOPENFRAME_ENV in your configure_env_vars() helper.

TracingProxy — spans without touching service code

from openframe.core.tracing import TracingProxy

raw_repo = PostgresItemRepository(settings)
traced_repo = TracingProxy(raw_repo, prefix="repository.item")

# traced_repo.get(...)    → span "repository.item.get"
# traced_repo.create(...) → span "repository.item.create"
# The service layer never knows telemetry exists.

Safe for reconnecting adapters — the wrapped method is resolved fresh on every call, never snapshotted.

Middleware — pure ASGI, framework-agnostic

from openframe.core.middleware import TelemetryMiddleware

# FastAPI
app = FastAPI(lifespan=lifespan)
app.add_middleware(TelemetryMiddleware)

# Bare ASGI
app = TelemetryMiddleware(my_asgi_app)

Important: do not call setup_telemetry() inside the middleware. Call it once in your lifespan handler. The middleware uses get_tracer() and get_meter() directly against whatever providers are currently set.

Instruments five HTTP metrics per request: http.server.request.count, http.server.request.duration (s), http.server.active_requests, http.server.error.count, http.server.response.size. Injects x-session-id on every response.


Environment variables

Variable Default Description
OTEL_EXPORTER_OTLP_ENDPOINT OTLP base URL. When absent, no-op providers are used — no errors, no export.
OTEL_EXPORTER_OTLP_HEADERS Auth headers. Format: Authorization=Basic <token>
OTEL_SERVICE_NAME openframe Service name tag on all telemetry
OTEL_SERVICE_VERSION 1.0.0 Service version tag
OTEL_METRIC_EXPORT_INTERVAL_MS 15000 Metric export interval in milliseconds
OPENFRAME_ENV dev Deployment environment: dev · feat · prod

Running tests

pip install -e ".[dev]"
pytest tests/ -v

112 tests across 8 modules. All run in under 1 second — no network calls, no external services.

# Smoke test
python -c "from openframe.core.ports import BaseRepository; print('openframe-core OK')"

Design decisions

Why Adapter prefix on exceptions? ConnectionError and TimeoutError are Python stdlib built-ins. Using the same names would shadow them silently. AdapterConnectionError and AdapterTimeoutError are unambiguous.

Why super().__init__(message) only? Exception.__str__ returns a tuple repr when multiple args are passed. Passing only the message keeps str(exc) readable: [postgres.get] entity not found.

Why does TelemetryMiddleware not call setup_telemetry()? The OTel SDK's set_tracer_provider() is protected by a once-guard — subsequent calls are silently rejected. Calling setup_telemetry() inside the middleware overwrites the provider set at startup (including test fixture providers), causing spans to be dropped silently.

Why is TracingProxy safe for reconnecting adapters? The closure resolves the wrapped method via getattr(wrapped, name) on every invocation — never from a snapshot captured at first access. When a Postgres pool or Redis client replaces its own methods after reconnect, the proxy calls the new methods automatically.


Ecosystem

Package Install Description
openframe-adapters pip install openframe-adapters[postgres] DB + queue adapters — Postgres, Redis, Mongo, Kafka, NATS, and more
openframe-protocol pip install openframe-protocol[sse] WebSocket · SSE · gRPC · MCP · webhooks
openframe-infra pip install openframe-infra[storage] Storage · auth · secrets · observability · feature flags
openframe-ai pip install openframe-ai[serving] LangChain · LlamaIndex · CrewAI · model serving · training
openframe-suite pip install openframe-suite[all] Full platform — installs everything

All packages pin openframe-core>=1.0,<2.


Documentation

Full documentation — architecture, module reference, runbooks, developer guide — at:

furious-meteors.github.io/openframe-core

Section Contents
System Overview Seven-module model, dependency order, key design properties
Package Journey How a request flows from template wiring through adapter to response
ADRs Five architectural decision records
Module Reference Every public class, method, parameter, return type, and raises
Runbooks Six operational failure scenarios with recovery steps
Developer Guide Quick start, how it works, first code change, debugging

License

MIT — see LICENSE.

Project details


Download files

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

Source Distribution

openframe_core-1.0.1.tar.gz (529.3 kB view details)

Uploaded Source

Built Distribution

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

openframe_core-1.0.1-py3-none-any.whl (29.9 kB view details)

Uploaded Python 3

File details

Details for the file openframe_core-1.0.1.tar.gz.

File metadata

  • Download URL: openframe_core-1.0.1.tar.gz
  • Upload date:
  • Size: 529.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openframe_core-1.0.1.tar.gz
Algorithm Hash digest
SHA256 2a8f4de8caf7fec318e6fe3af7942981629d7e532d87f4d3397347e94d45c246
MD5 40424a4ec2e50e57980190beb4a9baae
BLAKE2b-256 4314284efb1a18204b0868eb3296215c2e782c60ca1024d1ece615d9e93b1f3a

See more details on using hashes here.

File details

Details for the file openframe_core-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: openframe_core-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 29.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for openframe_core-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5ba70976c403453fee0e45a3360541abdefc4577464262ad1e623446decec108
MD5 7dc16d881765164ea8555858491a4794
BLAKE2b-256 2327077764b73b7d0042c18644a3bade469a264b150f9bdf861f55083802308c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page