Metadata-Version: 2.4
Name: mic-struct
Version: 0.0.3
Summary: Model/Interface/Controller boilerplate for Python services — FastAPI/Litestar parity, JWT service auth, SQLModel storage, Prometheus + JSON logs, cookiecutter scaffold
Project-URL: Homepage, https://gitlab.com/mic-struct/mic-struct
Project-URL: Documentation, https://gitlab.com/mic-struct/mic-struct/-/blob/master/README.md
Project-URL: Repository, https://gitlab.com/mic-struct/mic-struct
Project-URL: Issues, https://gitlab.com/mic-struct/mic-struct/-/issues
Project-URL: Changelog, https://gitlab.com/mic-struct/mic-struct/-/blob/master/CHANGELOG.md
Author: mic-struct contributors
License-Expression: MIT
License-File: LICENSE
Keywords: boilerplate,cookiecutter,fastapi,jwt,litestar,mic,model-interface-controller,prometheus,scaffold,sqlmodel
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.14
Requires-Dist: pydantic<3,>=2.10
Provides-Extra: all
Requires-Dist: aiokafka<1,>=0.12; extra == 'all'
Requires-Dist: alembic<2,>=1.13; extra == 'all'
Requires-Dist: fastapi<1,>=0.115; extra == 'all'
Requires-Dist: grpcio-health-checking<2,>=1.66; extra == 'all'
Requires-Dist: grpcio<2,>=1.66; extra == 'all'
Requires-Dist: httpx<1,>=0.27; extra == 'all'
Requires-Dist: litestar<3,>=2.13; extra == 'all'
Requires-Dist: nats-py<3,>=2.7; extra == 'all'
Requires-Dist: neo4j<6,>=5.27; extra == 'all'
Requires-Dist: opentelemetry-api<2,>=1.27; extra == 'all'
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27; extra == 'all'
Requires-Dist: opentelemetry-sdk<2,>=1.27; extra == 'all'
Requires-Dist: prometheus-client<1,>=0.20; extra == 'all'
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'all'
Requires-Dist: pyjwt<3,>=2.8; extra == 'all'
Requires-Dist: pymongo<5,>=4.10; extra == 'all'
Requires-Dist: redis<7,>=5.2; extra == 'all'
Requires-Dist: sentry-sdk<3,>=2.20; extra == 'all'
Requires-Dist: sqlmodel<0.1,>=0.0.38; extra == 'all'
Requires-Dist: starlette<2,>=1.0.1; extra == 'all'
Requires-Dist: urllib3<3,>=2.7; extra == 'all'
Requires-Dist: uvicorn[standard]<1,>=0.30; extra == 'all'
Provides-Extra: auth
Requires-Dist: pyjwt<3,>=2.8; extra == 'auth'
Provides-Extra: cli
Provides-Extra: client
Requires-Dist: httpx<1,>=0.27; extra == 'client'
Provides-Extra: dev
Requires-Dist: bandit>=1.8; extra == 'dev'
Requires-Dist: black>=26.0.0; extra == 'dev'
Requires-Dist: coverage>=7.13; extra == 'dev'
Requires-Dist: grpcio-health-checking<2,>=1.66; extra == 'dev'
Requires-Dist: grpcio<2,>=1.66; extra == 'dev'
Requires-Dist: mutmut<4,>=3.0; extra == 'dev'
Requires-Dist: mypy>=1.20.1; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pre-commit>=4.0; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=1.0; extra == 'dev'
Requires-Dist: pytest-benchmark<6,>=4; extra == 'dev'
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: radon<5.2,>=5.1; extra == 'dev'
Requires-Dist: ruff>=0.15.11; extra == 'dev'
Requires-Dist: urllib3<3,>=2.7; extra == 'dev'
Requires-Dist: wily<2,>=1.25; extra == 'dev'
Requires-Dist: xenon<1,>=0.9; extra == 'dev'
Provides-Extra: document
Requires-Dist: pymongo<5,>=4.10; extra == 'document'
Provides-Extra: fastapi
Requires-Dist: fastapi<1,>=0.115; extra == 'fastapi'
Requires-Dist: starlette<2,>=1.0.1; extra == 'fastapi'
Requires-Dist: uvicorn[standard]<1,>=0.30; extra == 'fastapi'
Provides-Extra: full-observability
Requires-Dist: opentelemetry-api<2,>=1.27; extra == 'full-observability'
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27; extra == 'full-observability'
Requires-Dist: opentelemetry-sdk<2,>=1.27; extra == 'full-observability'
Requires-Dist: prometheus-client<1,>=0.20; extra == 'full-observability'
Requires-Dist: sentry-sdk<3,>=2.20; extra == 'full-observability'
Requires-Dist: urllib3<3,>=2.7; extra == 'full-observability'
Provides-Extra: graph
Requires-Dist: neo4j<6,>=5.27; extra == 'graph'
Provides-Extra: grpc
Requires-Dist: grpcio-health-checking<2,>=1.66; extra == 'grpc'
Requires-Dist: grpcio<2,>=1.66; extra == 'grpc'
Provides-Extra: integration
Requires-Dist: aiokafka<1,>=0.12; extra == 'integration'
Requires-Dist: nats-py<3,>=2.7; extra == 'integration'
Requires-Dist: pytest-asyncio<2,>=1.0; extra == 'integration'
Requires-Dist: testcontainers[kafka,postgres,redis]<5,>=4.8; extra == 'integration'
Provides-Extra: kafka
Requires-Dist: aiokafka<1,>=0.12; extra == 'kafka'
Provides-Extra: litestar
Requires-Dist: litestar<3,>=2.13; extra == 'litestar'
Provides-Extra: messaging
Requires-Dist: aiokafka<1,>=0.12; extra == 'messaging'
Requires-Dist: nats-py<3,>=2.7; extra == 'messaging'
Provides-Extra: mongo
Requires-Dist: pymongo<5,>=4.10; extra == 'mongo'
Provides-Extra: mysql
Requires-Dist: alembic<2,>=1.13; extra == 'mysql'
Requires-Dist: pymysql<2,>=1.1; extra == 'mysql'
Requires-Dist: sqlmodel<0.1,>=0.0.38; extra == 'mysql'
Provides-Extra: nats
Requires-Dist: nats-py<3,>=2.7; extra == 'nats'
Provides-Extra: neo4j
Requires-Dist: neo4j<6,>=5.27; extra == 'neo4j'
Provides-Extra: observability
Requires-Dist: opentelemetry-api<2,>=1.27; extra == 'observability'
Requires-Dist: prometheus-client<1,>=0.20; extra == 'observability'
Provides-Extra: opa
Requires-Dist: httpx<1,>=0.27; extra == 'opa'
Provides-Extra: openfga
Requires-Dist: httpx<1,>=0.27; extra == 'openfga'
Provides-Extra: oracle
Requires-Dist: alembic<2,>=1.13; extra == 'oracle'
Requires-Dist: oracledb<3,>=2.5; extra == 'oracle'
Requires-Dist: sqlmodel<0.1,>=0.0.38; extra == 'oracle'
Provides-Extra: postgres
Requires-Dist: alembic<2,>=1.13; extra == 'postgres'
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'postgres'
Requires-Dist: sqlmodel<0.1,>=0.0.38; extra == 'postgres'
Provides-Extra: redis
Requires-Dist: redis<7,>=5.2; extra == 'redis'
Provides-Extra: release
Requires-Dist: build<2,>=1.2; extra == 'release'
Requires-Dist: twine<7,>=6.0; extra == 'release'
Requires-Dist: urllib3<3,>=2.7; extra == 'release'
Provides-Extra: sentry
Requires-Dist: sentry-sdk<3,>=2.20; extra == 'sentry'
Requires-Dist: urllib3<3,>=2.7; extra == 'sentry'
Provides-Extra: sql
Requires-Dist: alembic<2,>=1.13; extra == 'sql'
Requires-Dist: sqlmodel<0.1,>=0.0.38; extra == 'sql'
Provides-Extra: tracing
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27; extra == 'tracing'
Requires-Dist: opentelemetry-sdk<2,>=1.27; extra == 'tracing'
Provides-Extra: ws
Requires-Dist: pyjwt<3,>=2.8; extra == 'ws'
Requires-Dist: redis<7,>=5.2; extra == 'ws'
Description-Content-Type: text/markdown

# mic-struct

[![PyPI version](https://img.shields.io/pypi/v/mic-struct.svg)](https://pypi.org/project/mic-struct/)
[![Python versions](https://img.shields.io/pypi/pyversions/mic-struct.svg)](https://pypi.org/project/mic-struct/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](#)

**Model / Interface / Controller** boilerplate for Python microservices.
Build production-grade services with strict contracts, framework-agnostic
HTTP, JWT service-to-service auth, pluggable storage, and a cookiecutter
scaffold that lands at 100 % test coverage out of the box.

`mic-struct` ships **25 modules** as composable building blocks — you
assemble what you need, the library has no opinion on your stack. Every
optional integration (Redis, Postgres, Mongo, Neo4j, NATS, Kafka, gRPC, …)
lives behind a `pip` extra, so a minimal install pulls only Pydantic.

## What's inside

### Core — HTTP, contracts, auth

| Module | What it gives you |
|---|---|
| `mic.contracts` | `StrictSchema` (Pydantic v2, `extra=forbid`, `frozen=True`), `HttpSuccessEnvelope[T]`, `wrap_http_success()` |
| `mic.model` | `DomainError(code, message, details)` + `TransientError` (4xx vs 503 mapping) |
| `mic.http` | Framework-agnostic `HttpRouteSpec` / `HttpResponseSpec` / `HttpRequestContext` + adapters for **FastAPI** and **Litestar** (handlers stay identical across both) |
| `mic.auth` | `ServiceAuthSigner` / `ServiceAuthVerifier` — HS256 audience-bound JWT with double-key rotation + issuer whitelist |
| `mic.client` | `ServiceHttpClient` — thin httpx wrapper with auto-signing + canonical error mapping |
| `mic.cli` | `build_parser()` — argparse-based operator CLI scaffold (stdlib only) |

### Data — storage, caching, sharding

| Module | What it gives you |
|---|---|
| `mic.datastorage` | `DataStorage` ABC + `SqlDataStorage` (SQLModel) / `DocumentDataStorage` (MongoDB) / `GraphDataStorage` (Neo4j) / `InMemoryKeyValueDataStorage` / `InMemoryDocumentDataStorage` |
| `mic.cache` | `CacheBackend` ABC + `RedisCache` / `RedisClusterCache` / `InMemoryCache` |
| `mic.response_cache` | ASGI middleware — HTTP response caching with ETag, stampede protection, SHA-256 vary keys |
| `mic.read_models` | CQRS read-model projection helpers |
| `mic.shard` | Application-level Postgres sharding (consistent-hash router) |

### Messaging — events, queues, realtime

| Module | What it gives you |
|---|---|
| `mic.eventbus` | `EventBus` ABC — sync pub/sub (`InMemoryEventBus`, `RedisStreamsEventBus`) |
| `mic.queue` | `AsyncEventBus` ABC — durable async pub/sub (`NATSEventBus`, `KafkaEventBus`, in-memory) |
| `mic.realtime` | WebSocket endpoints + rooms, `RealtimeBackend` ABC (in-memory / Redis Streams) |
| `mic.outbox` | Transactional outbox pattern — `OutboxStore` + `OutboxDispatcher` (at-least-once delivery) |

### Reliability — resilience, rate limiting, idempotency, locking

| Module | What it gives you |
|---|---|
| `mic.resilience` | `CircuitBreaker` (sync + async), with half-open probing |
| `mic.ratelimit` | `RateLimiter` ABC — token bucket (`InMemoryRateLimiter`, `RedisRateLimiter` with server-side clock) |
| `mic.idempotency` | `Idempotency-Key` store with single-flight protection (TOCTOU-safe via a distributed lock) |
| `mic.locking` | `DistributedLock` ABC — `InMemoryDistributedLock` / `RedisDistributedLock` |

### Policy & observability

| Module | What it gives you |
|---|---|
| `mic.authz` | `AuthorizationEngine` ABC — RBAC inline / OPA Rego / OpenFGA ReBAC |
| `mic.observability` | `configure_logging()` (JSON / text, trace-id auto-injection) + Prometheus `counter()` / `gauge()` / `histogram()` |
| `mic.healthcheck` | `ReadinessChecker` + probes — distinguishes `/health` (liveness) from `/ready` (dependency readiness) |

### Transports

| Module | What it gives you |
|---|---|
| `mic.grpc` | Audience-bound gRPC interceptor + client + health-check service |

### Template

| Path | What it gives you |
|---|---|
| `template/` | Cookiecutter scaffold — answer 3 questions, get a complete service that passes `make gate` at 100 % coverage |

## Install

```bash
# Just contracts + model (minimal — only pulls Pydantic)
pip install mic-struct

# Add an HTTP adapter (one of, or both)
pip install "mic-struct[fastapi]"
pip install "mic-struct[litestar]"

# SQL datastorage / auth / HTTP client / observability — pick what you need
pip install "mic-struct[postgres,auth,client,observability]"

# Pub/sub backends
pip install "mic-struct[redis]"        # mic.eventbus (sync) + mic.realtime (WebSocket)
pip install "mic-struct[messaging]"    # mic.queue (NATS + Kafka, async durable)

# Full observability stack (prometheus + sentry + tracing)
pip install "mic-struct[full-observability]"

# Everything
pip install "mic-struct[all]"
```

Voir [`pyproject.toml`](./pyproject.toml) pour la matrice complète des extras.

## Quickstart — define a route, plug into FastAPI

`mic.http` provides the **framework-agnostic primitives** (`HttpRouteSpec`,
`HttpResponseSpec`, `HttpCookieSpec`, `HttpRequestContext`). The
**opt-in** packages `mic.fastapi` and `mic.litestar` ship adapters that
translate those primitives into the corresponding framework — pull
them via the `[fastapi]` / `[litestar]` extras when (and only when)
you need them.

```python
from fastapi import FastAPI
from mic.contracts import StrictSchema, wrap_http_success
from mic.fastapi import build_fastapi_router
from mic.http import HttpResponseSpec, HttpRouteSpec


class CreateItemPayload(StrictSchema):
    name: str
    quantity: int = 0


def _create_item(*, payload: CreateItemPayload) -> HttpResponseSpec:
    return HttpResponseSpec(
        status_code=201,
        body=wrap_http_success({"name": payload.name, "qty": payload.quantity}),
    )


routes = (
    HttpRouteSpec(
        method="POST", path="/items",
        name="items.create", category="items",
        handler=_create_item,
    ),
)

app = FastAPI()
app.include_router(build_fastapi_router(routes=routes))
```

Litestar is symmetric : `from mic.litestar import build_litestar_router`
(via the `[litestar]` extra). A consumer who needs neither framework
imports only `mic.http` primitives and never pulls FastAPI / Litestar
transitively.

Switch to Litestar by replacing `build_fastapi_router` with
`build_litestar_router` — **handlers stay identical**.

## MELT redaction (logs · traces · events · metrics)

PII and secrets must never leak into observability sinks. `mic-struct` ships
**one redaction mechanism per MELT pillar**: you inject the *policy* (which
field → which strategy, derived from your data-classification ADR), the library
applies it consistently. All four reuse the `FieldRedactors` type
(`dict[str, Callable[[Any], Any]]`) and the shared strategies from
`mic.outbox` — `redact_drop` (secrets), `redact_hash` (correlatable, blake2b),
`redact_mask`.

```python
from mic.outbox import redact_drop, redact_hash  # shared strategies
```

**Logs** — `RedactingLogFilter` (stdlib `logging.Filter`), recursive over nested
dicts/lists and exception locals:

```python
from mic.observability import RedactingLogFilter

handler.addFilter(RedactingLogFilter(redactors={
    "email": redact_hash,
    "access_token": redact_drop,
}))
```

**Traces** — `RedactingSpanProcessor` (OTel `SpanProcessor`), scrubs span and
span-event attributes before export (OTLP / Tempo):

```python
from mic.observability import RedactingSpanProcessor

provider.add_span_processor(RedactingSpanProcessor(redactors={
    "db.statement": redact_drop,
    "http.request.body": redact_drop,
    "exception.message": redact_hash,
}))
```

**Events** — `sanitize_payload` + `TopicRedactionPolicy`, applied before the row
hits `outbox_events`, configurable per topic (returns a redacted copy):

```python
from mic.outbox import TopicRedactionPolicy, sanitize_payload

POLICY = TopicRedactionPolicy(
    default={"otp_code": redact_drop},
    per_topic={"application.received": {"email": redact_hash}},
)
safe = sanitize_payload(event, policy=POLICY)
```

**Metrics** — `SanitizingMetricsBackend` decorates any `MetricsBackend`: rejects
sensitive/secret label *names* and caps label cardinality. `strict=True`
(dev/CI) raises `MetricLabelViolation`; `strict=False` (prod) warns, drops the
offending emission and bumps an internal `*_rejected_total` counter:

```python
from mic.observability import SanitizingMetricsBackend

metrics = SanitizingMetricsBackend(
    prometheus_backend, denied_labels={"email", "access_token"}, strict=False,
)
metrics.increment("orders_total", labels={"status_class": "2xx"})  # OK
```

The *mechanism* lives in mic-struct; each service injects its own *policy*.

## Generate a new service from the template

```bash
pip install cookiecutter
cookiecutter https://gitlab.com/mic-struct/mic-struct.git --directory=template

#  service_name        [example-service] : my-service
#  service_description                   : My new microservice
#  http_framework      [fastapi]         : fastapi  # or litestar

cd my-service
make bootstrap-dev && source .venv/bin/activate
make gate     # → green at 100 % coverage from first run
make run      # → uvicorn on :8000, GET /health, GET /metrics
```

The generated service comes with:

- `/health` + `/ready` + `/metrics` endpoints
- An example CRUD module (SQLModel-backed)
- Mirror tests at 100 % coverage
- A `Makefile` with `gate` (= black + ruff + mypy strict + 100 % pytest)

## Quality bar

`mic-struct` itself:

- **1100+ tests**, 100 % line + branch coverage (with all extras installed)
- `mypy --strict` clean, `ruff` + `black` enforced
- `bandit` + `pip-audit` in the gate
- Mutation testing (`mutmut`) and performance benchmarks run nightly
- Cookiecutter template (FastAPI + Litestar variants) generates a
  service that passes `make gate` from the first run

See [CONTRIBUTING.md](CONTRIBUTING.md) for the test-quality rules
(`match=` enforcement, ABC-vs-Protocol convention, fakes-over-mocks),
and [docs/decisions/](docs/decisions/) for the architecture decision
records.

## Versioning

`mic-struct` follows [SemVer 2.0](https://semver.org). See
[CHANGELOG.md](CHANGELOG.md) for the per-release breakdown.

## Contributing

```bash
git clone https://gitlab.com/mic-struct/mic-struct.git && cd mic-struct
make bootstrap-dev
make gate     # must stay green
```

100 % line + branch coverage is **mandatory** on every change — the CI
enforces it. Cf. [CONTRIBUTING.md](CONTRIBUTING.md).

## License

[MIT](LICENSE) — © 2026 mic-struct contributors.

## Links

- 📦 [PyPI](https://pypi.org/project/mic-struct/)
- 📋 [Changelog](CHANGELOG.md)
- 🛠 [Contributing guide](CONTRIBUTING.md)
- 🚢 [Releasing guide](RELEASING.md) (maintainers)
- 🐛 [Issues](https://gitlab.com/mic-struct/mic-struct/-/issues)
