Skip to main content

Platform shared library – DDD/Hexagonal building blocks for Python services

Project description

mp-commons

CI codecov PyPI version Python versions License: MIT

Platform shared library for Python microservices.

mp-commons delivers the "senior engineer pillars" as importable building blocks so every service starts from a solid foundation without re-inventing the wheel:

  • DDD / Hexagonal Architecture — Entities, Value Objects, Aggregates, Domain Events, Repositories, Unit of Work
  • CQRS — Command / Query buses with an in-process default
  • Resilience — Retry (exponential + jitter), Circuit Breaker, Bulkhead, Timeouts
  • Outbox / Inbox / Idempotency — at-least-once delivery with duplicate suppression
  • Observability — structured logging, metrics, tracing (all via ports)
  • Security — Principal, Policy Engine, PII redaction, OIDC/Keycloak adapter
  • 12-factor Config — env-var loader, Kubernetes secrets, Vault
  • Contract Testing — OpenAPI / AsyncAPI compatibility assertions
  • Testing Support — in-memory fakes, pytest fixtures, chaos injection

Installation

# Core only (no third-party runtime deps)
pip install mp-commons

# With specific adapters
pip install "mp-commons[fastapi,sqlalchemy,redis,otel]"

# Everything
pip install "mp-commons[all-adapters]"

Available extras: fastapi, sqlalchemy, redis, kafka, nats, rabbitmq, httpx, keycloak, vault, otel, pydantic, structlog, tenacity, crypto, dotenv.


Quick Start

1. Define a Domain

from dataclasses import dataclass
from mp_commons.kernel.ddd import AggregateRoot, DomainEvent, Invariant
from mp_commons.kernel.types import EntityId

@dataclass(frozen=True)
class OrderPlaced(DomainEvent):
    order_id: str
    event_type: str = "order.placed"

@dataclass
class Order(AggregateRoot):
    customer_id: str
    total_cents: int

    def place(self) -> None:
        Invariant.require(self.total_cents > 0, "Total must be positive")
        self._raise_event(OrderPlaced(order_id=str(self.id)))

2. Write a Use Case with CQRS

from mp_commons.application.cqrs import Command, CommandHandler, InProcessCommandBus

class PlaceOrderCommand(Command):
    def __init__(self, customer_id: str, total_cents: int) -> None:
        self.customer_id = customer_id
        self.total_cents = total_cents

class PlaceOrderHandler(CommandHandler[PlaceOrderCommand]):
    async def handle(self, cmd: PlaceOrderCommand) -> None:
        order = Order(id=EntityId.generate(), customer_id=cmd.customer_id, total_cents=cmd.total_cents)
        order.place()

bus = InProcessCommandBus()
bus.register(PlaceOrderCommand, PlaceOrderHandler())
await bus.dispatch(PlaceOrderCommand("cust-1", 1999))

3. Add Resilience

from mp_commons.resilience.retry import RetryPolicy, ExponentialBackoff, FullJitter
from mp_commons.resilience.circuit_breaker import CircuitBreaker, CircuitBreakerPolicy

retry = RetryPolicy(max_attempts=3, backoff=ExponentialBackoff(base=0.1), jitter=FullJitter())
cb = CircuitBreaker("payments", CircuitBreakerPolicy(failure_threshold=5))

result = await retry.execute_async(lambda: cb.call(call_payment_service))

4. Structured Logging with Correlation

from mp_commons.observability.logging import JsonLoggerFactory
from mp_commons.observability.correlation import CorrelationContext, RequestContext

JsonLoggerFactory.configure(service="orders-service", environment="production")
ctx = RequestContext.new(tenant_id=TenantId("acme"))
CorrelationContext.set(ctx)

import structlog
log = structlog.get_logger()
log.info("order_placed", order_id="ord-1")

5. FastAPI Integration

from fastapi import FastAPI
from mp_commons.adapters.fastapi import (
    FastAPICorrelationIdMiddleware,
    FastAPIExceptionMapper,
    FastAPIHealthRouter,
)

app = FastAPI()
app.add_middleware(FastAPICorrelationIdMiddleware)
FastAPIExceptionMapper.register(app)
app.include_router(FastAPIHealthRouter())

6. Testing with Fakes

from mp_commons.testing.fakes import FakeClock, InMemoryMessageBus, FakePolicyEngine
from mp_commons.kernel.security import PolicyDecision

clock = FakeClock()
bus = InMemoryMessageBus()
policy = FakePolicyEngine(default=PolicyDecision.ALLOW)

await bus.publish(some_message)
assert len(bus.of_topic("orders")) == 1
clock.advance(hours=2)

Module Map

mp_commons/
├── kernel/          # Zero external deps — pure Python
│   ├── errors/      # Error hierarchy
│   ├── types/       # EntityId, Money, Email, Result, Option, …
│   ├── ddd/         # Entity, Aggregate, ValueObject, DomainEvent, Specification, …
│   ├── messaging/   # Message, Outbox, Inbox, Idempotency ports
│   ├── contracts/   # CompatibilityMode, ContractRegistry port
│   ├── time/        # Clock protocol, SystemClock, FrozenClock
│   └── security/    # Principal, PolicyEngine, CryptoProvider, PIIRedactor
├── application/     # Use-case layer
│   ├── cqrs/        # Command/Query buses
│   ├── pipeline/    # Middleware pipeline + 9 built-in middlewares
│   ├── pagination/  # Page, PageRequest, Cursor
│   ├── rate_limit/  # RateLimiter port
│   ├── feature_flags/ # FeatureFlagProvider port
│   └── uow/         # TransactionManager, @transactional
├── resilience/
│   ├── retry/       # RetryPolicy, backoff & jitter strategies
│   ├── circuit_breaker/
│   ├── bulkhead/
│   └── timeouts/
├── observability/
│   ├── correlation/ # RequestContext, CorrelationContext (ContextVar)
│   ├── logging/     # Logger port, SensitiveFieldsFilter, JsonLoggerFactory
│   ├── metrics/     # Counter/Histogram/Gauge ports, NoopMetrics
│   └── tracing/     # Tracer/Span ports, NoopTracer
├── config/
│   ├── settings/    # Settings dataclass, EnvSettingsLoader, DotenvSettingsLoader
│   ├── secrets/     # SecretStore port, KubernetesSecretStore
│   └── validation/  # ConfigError, MissingRequiredSettingError
├── adapters/        # Concrete implementations (all lazy-imported)
│   ├── fastapi/
│   ├── sqlalchemy/
│   ├── redis/
│   ├── kafka/
│   ├── nats/
│   ├── rabbitmq/
│   ├── opentelemetry/
│   ├── http/        # httpx-based HTTP client with retry & circuit-breaker
│   ├── keycloak/    # OIDC token verification
│   └── vault/       # HashiCorp Vault secrets
└── testing/
    ├── fakes/       # In-memory doubles for all kernel ports
    ├── fixtures/    # pytest fixtures
    ├── contracts/   # OpenAPI/AsyncAPI contract test helpers
    ├── generators/  # Domain object factories
    └── chaos/       # Latency & failure injection

Architecture Principles

  1. No framework leakagekernel.* imports stdlib only. See ADR-0001.
  2. Ports & Adapters — every infrastructure concern is a Protocol/ABC in the kernel; concrete implementations live in adapters/.
  3. Optional extras — no adapter is a hard dependency.
  4. Idiomatic Python 3.12+type aliases, match, dataclasses, asyncio-native.

Development

uv sync --extra dev   # install dev deps
pytest                # run tests
ruff check src tests  # lint
mypy src              # type-check
pre-commit run --all-files

Contributing

  1. Fork and create a feature branch.
  2. Write tests first.
  3. Ensure pytest, ruff, and mypy all pass.
  4. Update CHANGELOG.md.
  5. Open a PR — CI must be green.

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

mp_commons-0.2.0.tar.gz (602.5 kB view details)

Uploaded Source

Built Distribution

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

mp_commons-0.2.0-py3-none-any.whl (357.5 kB view details)

Uploaded Python 3

File details

Details for the file mp_commons-0.2.0.tar.gz.

File metadata

  • Download URL: mp_commons-0.2.0.tar.gz
  • Upload date:
  • Size: 602.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mp_commons-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6d83170c196831b2a6cced50f0d752129366a87de0cfb8e7bc28a74c0b929ed6
MD5 f6da14243e37c204a3616d31442e2471
BLAKE2b-256 7e58f68efbfba5584bb8b11c878ef4c455a03a5cb64e8e9a2ecd07ae75fe0d89

See more details on using hashes here.

Provenance

The following attestation bundles were made for mp_commons-0.2.0.tar.gz:

Publisher: ci.yml on marcusPrado02/python-commons

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

File details

Details for the file mp_commons-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: mp_commons-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 357.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mp_commons-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e506cfb6e92b30a98b1ed213a202ab34065d7c2a66de1e48a0d4d3c4f1b2f835
MD5 dab8816d89dd123557c516c9d4b007ca
BLAKE2b-256 481185077a7aa88da86b0cb3c372ae9925bda1d29c3162929d4da1fdf81fe0e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for mp_commons-0.2.0-py3-none-any.whl:

Publisher: ci.yml on marcusPrado02/python-commons

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

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