Skip to main content

mixin-suite

Composable Python mixins for production services: structured logging with automatic correlation-ID propagation, sensitive-data masking, retry logic, and latency measurement.

PyPI version CI License Python Versions

This distribution includes five composable roots:

  • mixin_logging: End-to-end correlation-ID propagation, LoggingMixin, ambient logging, FlushOnWarningHandler
  • mixin_sensitivity: Sensitivity classification and dataclass repr-masking via SensitiveRepr
  • mixin_retry: Exponential backoff retry logic via RetryPolicy/RetryExecutor (capability contracts)
  • mixin_latency: High-precision elapsed-time measurement via LatencyClock
  • mixin_notifications: Event dispatch and suppression across multi-step workflows

All packages retain their original import roots and can be used independently or together.

What They Do

Logging: Correlation-ID Propagation

Track a single request through a distributed system with automatic correlation-ID injection on every log, HTTP call, database query, and background task.

Before:

class OrderService:
    def create_order(self, order_id: int):
        print(f"Creating order {order_id}")  # No correlation tracking
        send_notification(order_id)  # Loses request context

After:

from mixin_logging import LoggingMixin, set_correlation_id

set_correlation_id("req-123")

class OrderService(LoggingMixin):
    def create_order(self, order_id: int):
        self.log_info("order.create", order_id=order_id)
        # Logs with: {"correlation_id": "req-123", "order_id": 123, ...}
        send_notification(order_id)  # Correlation ID propagates automatically

Sensitivity: Prevent Accidental Secret Leaks

Mark sensitive fields in dataclasses via field metadata, and adopt SensitiveRepr to auto-mask in repr output.

Before:

from dataclasses import dataclass

@dataclass(frozen=True)
class APICredentials:
    user_id: int
    api_token: str

creds = APICredentials(user_id=1, api_token="sk-abc123xyz")
logger.info("Creds: %s", creds)  # LEAKED: api_token exposed

After:

from dataclasses import dataclass, field
from mixin_sensitivity import Sensitivity, SensitiveRepr

@dataclass(frozen=True, slots=True, repr=False)
class APICredentials(SensitiveRepr):
    user_id: int
    api_token: str = field(metadata={"sensitivity": Sensitivity.SECRET})

creds = APICredentials(user_id=1, api_token="sk-abc123xyz")
logger.info("Creds: %s", creds)  # SAFE: repr shows "api_token=***MASKED***"

Retry: Exponential Backoff with Predicates

Resilient function execution with configurable backoff and predicate-based retry decisions.

from mixin_retry import RetryPolicy, RetryExecutor

policy = RetryPolicy(
    max_attempts=3,
    backoff_base_seconds=0.1,
    backoff_multiplier=2.0,
    backoff_max_seconds=1.0,
    jitter=True,
    should_retry=lambda exc: isinstance(exc, ConnectionError)
)

executor = RetryExecutor()

def flaky_api_call(url):
    # Retries on ConnectionError, exponential backoff
    pass

wrapped_call = executor.wrap(flaky_api_call, policy=policy)
result = wrapped_call("https://api.example.com")

Latency: High-Precision Measurement

Measure elapsed time with perf_counter precision and automatic rounding.

from mixin_latency import LatencyClock

clock = LatencyClock.start()
# ... do work ...
measurement = clock.stop()
print(f"Elapsed: {measurement.duration_ms} ms")

# Or context-manager form:
with LatencyClock.measure() as clock:
    # ... do work ...
    pass  # Duration auto-measured on exit

Installation

Base installation:

pip install mixin-suite

or with uv:

uv add mixin-suite

With optional extras for logging adapters:

Base package includes mixin_logging (stdlib adapter only) and mixin_sensitivity (no dependencies).

Optional extras (mixin_logging adapters):

  • [aiohttp]: aiohttp client instrumentation
  • [botocore]: AWS SDK instrumentation
  • [celery]: Celery task propagation
  • [fastapi]: FastAPI middleware and dependencies
  • [grpc]: gRPC server instrumentation
  • [httpx]: HTTPX client instrumentation
  • [requests]: Requests client instrumentation
  • [urllib3]: urllib3 client instrumentation
  • [all]: All adapters

Install with extras:

uv add "mixin-suite[httpx,botocore]"  # Multiple extras
uv add "mixin-suite[all]"             # All adapters

Python version: Requires Python 3.11 or later (3.11 and 3.14 tested).

Quick Start

Logging with Correlation IDs

1. Add stdlib adapter to your logging config

import logging
from mixin_logging.adapters.stdlib.stdlib_client import CorrelationLogFilter

logging.basicConfig()
logging.getLogger().addFilter(CorrelationLogFilter())

2. Install FastAPI adapter and add middleware

For FastAPI applications:

from fastapi import FastAPI
from mixin_logging.adapters.fastapi import CorrelationIdMiddleware

app = FastAPI()
app.add_middleware(CorrelationIdMiddleware)

Or manually for other frameworks:

from mixin_logging import set_correlation_id
from fastapi import Request

@app.middleware("http")
async def correlation_middleware(request: Request, call_next):
    set_correlation_id(request.headers.get("x-correlation-id", "auto-gen"))
    return await call_next(request)

3. Use LoggingMixin in your classes

from mixin_logging import LoggingMixin

class UserService(LoggingMixin):
    def create_user(self, user_name: str):
        self.log_info("user.create", user_name=user_name)
        # Logs include correlation_id automatically

Sensitivity: Masking Sensitive Fields

1. Decorate and mark sensitive fields

from dataclasses import dataclass, field
from mixin_sensitivity import sensitive, Sensitivity

@sensitive
@dataclass(frozen=True, slots=True)
class User:
    id: int
    api_token: str = field(metadata={"sensitivity": "secret"})
    email: str = field(metadata={"sensitivity": "pii"})
    ssn: str = field(metadata={"sensitivity": "phi"})
    name: str

2. Use in your code

user = User(
    id=1,
    api_token="sk-123456",
    email="alice@example.com",
    ssn="123-45-6789",
    name="Alice"
)

# Safe for logging
logger.info("User created: %s", repr(user))
# → "User created: User(id=1, api_token='***', email='***', ssn='***', name='Alice')"

# Introspect sensitivity profile
from mixin_sensitivity import classify
profile = classify(user)
# → SensitivityProfile(classes=(
#     ('api_token', Sensitivity.SECRET),
#     ('email', Sensitivity.PII),
#     ('ssn', Sensitivity.PHI),
# ))

Run-Verified Examples

These examples are executed against the published mixin-suite==0.2.0 distribution.

Logging Example: Correlation-ID Propagation

import logging
from mixin_logging import LoggingMixin, logged, set_correlation_id

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s - correlation_id=%(correlation_id)s",
)


class DocumentService(LoggingMixin):
    """Service that processes documents with correlation-ID tracking."""

    @logged("document.upload")
    def upload(self, doc_name: str, size_bytes: int) -> dict:
        """Upload a document and return metadata."""
        self.log_info("upload.initiated", doc_name=doc_name, size_bytes=size_bytes)
        result = {"id": "doc-123", "doc_name": doc_name, "stored": True}
        self.log_info("upload.complete", doc_id=result["id"])
        return result

    @logged("document.process")
    def process(self, doc_id: str) -> str:
        """Process a document and return status."""
        self.log_info("process.started", doc_id=doc_id)
        status = "processed"
        self.log_info("process.finished", doc_id=doc_id, status=status)
        return status


# Execute with correlation context
set_correlation_id("req-2026-07-10-001")
service = DocumentService()
result = service.upload("report.pdf", 1024000)
status = service.process("doc-123")

Output (Python 3.14, mixin-suite==0.2.0):

2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - document.upload.start - correlation_id=req-2026-07-10-001
2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - upload.initiated - correlation_id=req-2026-07-10-001
2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - upload.complete - correlation_id=req-2026-07-10-001
2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - document.process.start - correlation_id=req-2026-07-10-001
2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - process.started - correlation_id=req-2026-07-10-001
2026-07-10 03:05:20,405 - __main__.DocumentService - INFO - process.finished - correlation_id=req-2026-07-10-001

Sensitivity Example: Data Masking

from dataclasses import dataclass, field
from mixin_sensitivity import sensitive, classify, Sensitivity

@sensitive
@dataclass(frozen=True, slots=True)
class HealthRecord:
    patient_id: int
    ssn: str = field(metadata={"sensitivity": "phi"})
    diagnosis: str = field(metadata={"sensitivity": "phi"})
    treatment_notes: str = field(metadata={"sensitivity": "phi"})
    attending_physician: str

record = HealthRecord(
    patient_id=42,
    ssn="987-65-4321",
    diagnosis="Type 2 Diabetes",
    treatment_notes="Prescribed Metformin 500mg",
    attending_physician="Dr. Smith"
)

# Safe repr
print(repr(record))

# Introspect profile
profile = classify(record)
print(f"PHI fields: {[f for f, s in profile.classes if s == Sensitivity.PHI]}")

Output (Python 3.14, mixin-suite==0.2.0):

HealthRecord(patient_id=42, ssn=***, diagnosis=***, treatment_notes=***, attending_physician='Dr. Smith')

PHI fields: ['ssn', 'diagnosis', 'treatment_notes']

Documentation

  • Logging: See docs/mixin_logging/ for detailed adapter documentation, architecture, and integration patterns
  • Sensitivity: See docs/mixin_sensitivity/ for classifier API, masking customization, and examples
  • Historical Changelogs: See docs/mixin_logging/CHANGELOG-history.md and docs/mixin_sensitivity/CHANGELOG-history.md

Public API

mixin_logging (v0.6.0)

Core classes and functions:

  • LoggingMixin: Base class providing log_info(), log_debug(), and @logged support
  • logged(event_name): Decorator for automatic event logging and error handling
  • set_correlation_id(id): Set the correlation ID for the current context
  • get_correlation_id(): Retrieve the current correlation ID
  • clear_correlation_id(): Clear the correlation ID from context
  • CorrelationContext: Data class representing correlation metadata
  • ContextVarClient: Internal context-variable manager for correlation propagation
  • LoggedClient, LoggedContainer: Internal decorator implementation details
  • PUBLIC_API: Frozenset of all public names

mixin_sensitivity (v0.4.0)

Core classes and functions:

  • sensitive: Class decorator enabling automatic masking of sensitive fields
  • classify(dataclass_or_instance): Introspect sensitivity profile of a dataclass
  • Sensitivity: Enum taxonomy: PHI, PII, PCI, SECRET
  • SensitivityProfile: Data class containing field-to-sensitivity mappings

mixin_retry

Core classes and functions:

  • retried(retry_on=None, max_retries=3, base_delay_ms=100): Class-capable decorator for automatic retry logic with exponential backoff
  • RetryClient: Client implementation for retry decorator logic
  • RetryContainer: Container for retry decorator metadata

Imports

All packages maintain their original import roots:

# Logging
from mixin_logging import LoggingMixin, logged, set_correlation_id, get_correlation_id

# Sensitivity
from mixin_sensitivity import sensitive, classify, Sensitivity

# Retry
from mixin_retry import retried

Contributing

This is a consolidation of independently-maintained packages. Bug reports and feature requests should be filed in this repository or the respective upstream repositories:

License

Licensed under the Apache License 2.0. See LICENSE for details.

Download files

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

Source Distribution

mixin_suite-0.5.0.tar.gz (403.0 kB view details)

Uploaded Source

Built Distribution

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

mixin_suite-0.5.0-py3-none-any.whl (82.1 kB view details)

Uploaded Python 3

File details

Details for the file mixin_suite-0.5.0.tar.gz.

File metadata

  • Download URL: mixin_suite-0.5.0.tar.gz
  • Upload date:
  • Size: 403.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for mixin_suite-0.5.0.tar.gz
Algorithm Hash digest
SHA256 fc1bb96ab474e6b4a215991314112e3b4dac0f66ef7b6578822f290412328e10
MD5 93c4792900dd005372d83348937413ee
BLAKE2b-256 465776a94bf85c2e87dfdda40aafbc69bed3abf6e7665228a13f9b9808cb3692

See more details on using hashes here.

Provenance

The following attestation bundles were made for mixin_suite-0.5.0.tar.gz:

Publisher: publish.yml on jekhator/mixin-suite

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

File details

Details for the file mixin_suite-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: mixin_suite-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 82.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for mixin_suite-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 62f52db53d068ca1b01fc0d3dddc8ddb3e6c8ef941c9166df27df67a52c794f8
MD5 52c9d85110652cad98831604e6c161c3
BLAKE2b-256 4b49b63e4472919f9235576f950ea5df05f7346599fc9eb4d4445d9b5aeed3c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for mixin_suite-0.5.0-py3-none-any.whl:

Publisher: publish.yml on jekhator/mixin-suite

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

Release history Release notifications | RSS feed

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 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