Composable mixins for Python services: structured logging with correlation IDs and sensitive data classification.
Project description
mixin-suite
Composable Python mixins for production services: structured logging with automatic correlation-ID propagation, and sensitive-data classification and masking for frozen dataclasses.
This distribution consolidates two concern-specific packages and adds composable retry logic:
- mixin-logging (v0.6.0): End-to-end correlation-ID propagation across 14 adapters for distributed systems
- mixin-sensitivity (v0.4.0): Decorator-based sensitivity classification and masking for frozen dataclasses
- mixin-retry: Class-capable retry decorator with full-jitter backoff and predicate-based retry conditions
All packages retain their original import roots (mixin_logging, mixin_sensitivity, mixin_retry) 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 once, and they auto-mask in logs, reprs, and tracebacks.
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 sensitive, classify
@sensitive
@dataclass(frozen=True, slots=True)
class APICredentials:
user_id: int
api_token: str = field(metadata={"sensitivity": "secret"})
creds = APICredentials(user_id=1, api_token="sk-abc123xyz")
logger.info("Creds: %s", creds) # SAFE: repr shows "api_token=***"
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.mdanddocs/mixin_sensitivity/CHANGELOG-history.md
Public API
mixin_logging (v0.6.0)
Core classes and functions:
LoggingMixin: Base class providinglog_info(),log_debug(), and@loggedsupportlogged(event_name): Decorator for automatic event logging and error handlingset_correlation_id(id): Set the correlation ID for the current contextget_correlation_id(): Retrieve the current correlation IDclear_correlation_id(): Clear the correlation ID from contextCorrelationContext: Data class representing correlation metadataContextVarClient: Internal context-variable manager for correlation propagationLoggedClient,LoggedContainer: Internal decorator implementation detailsPUBLIC_API: Frozenset of all public names
mixin_sensitivity (v0.4.0)
Core classes and functions:
sensitive: Class decorator enabling automatic masking of sensitive fieldsclassify(dataclass_or_instance): Introspect sensitivity profile of a dataclassSensitivity: Enum taxonomy: PHI, PII, PCI, SECRETSensitivityProfile: 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 backoffRetryClient: Client implementation for retry decorator logicRetryContainer: 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:
- mixin-logging historical issues: https://github.com/jekhator/mixin-logging/issues
- mixin-sensitivity historical issues: https://github.com/jekhator/mixin-sensitivity/issues
- mixin-retry and suite issues: https://github.com/jekhator/mixin-suite/issues
License
Licensed under the Apache License 2.0. See LICENSE for details.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mixin_suite-0.2.0.tar.gz.
File metadata
- Download URL: mixin_suite-0.2.0.tar.gz
- Upload date:
- Size: 390.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6786c420db6a56646d341ab5e73f6da584c8b5287e34db690b20f2782da775e8
|
|
| MD5 |
c7e29085bc94093b37b40a936157c947
|
|
| BLAKE2b-256 |
9261b649c338883bee1058bf2ac1aaa68feae8c38c6e0b318e7625049e76866d
|
Provenance
The following attestation bundles were made for mixin_suite-0.2.0.tar.gz:
Publisher:
publish.yml on jekhator/mixin-suite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mixin_suite-0.2.0.tar.gz -
Subject digest:
6786c420db6a56646d341ab5e73f6da584c8b5287e34db690b20f2782da775e8 - Sigstore transparency entry: 2171154065
- Sigstore integration time:
-
Permalink:
jekhator/mixin-suite@173d5c0c262470d68a602d4fc335cdfea1e197bc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jekhator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@173d5c0c262470d68a602d4fc335cdfea1e197bc -
Trigger Event:
release
-
Statement type:
File details
Details for the file mixin_suite-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mixin_suite-0.2.0-py3-none-any.whl
- Upload date:
- Size: 82.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
653272dcc49b50f2d8a22b52da95735d9beb887e0ff1ba4574b7bf11c98ad533
|
|
| MD5 |
2de85f1a843648cf80302177cd530ab0
|
|
| BLAKE2b-256 |
075eae78248fe7086087ef23a0ee26207a1eef5b3d93a9ae1aa1f5e7a7288937
|
Provenance
The following attestation bundles were made for mixin_suite-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on jekhator/mixin-suite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mixin_suite-0.2.0-py3-none-any.whl -
Subject digest:
653272dcc49b50f2d8a22b52da95735d9beb887e0ff1ba4574b7bf11c98ad533 - Sigstore transparency entry: 2171154174
- Sigstore integration time:
-
Permalink:
jekhator/mixin-suite@173d5c0c262470d68a602d4fc335cdfea1e197bc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jekhator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@173d5c0c262470d68a602d4fc335cdfea1e197bc -
Trigger Event:
release
-
Statement type: