English | 한국어
Python-native bluetape libraries for backend services, testing, logging, and operational helpers.
bluetape-py follows the same ecosystem discipline as bluetape4k,
bluetape-go, and bluetape-rs, but it is not a mechanical port. The Python
line starts from Python 3.13+, keeps the default install thin, and splits
heavier capabilities into explicit PyPI distributions and extras.
v0.1.0 has been released as the first Python-native foundation:
v0.1.0.
PyPI publication remains on hold until package ownership and trusted publishing
are confirmed. The collections, codec, compression, cache, Redis provider,
serde, ID, JWT, measure, money, testcontainers, and audit packages are available
from the source workspace; registry install commands describe the intended
post-publication shape only.
The current planning track is milestone
0.2.0. It tracks
ecosystem issues #7 through #34, serialization follow-ups #45/#46, local cache
#50, compressor contracts #59, Redis provider #54, and coordination #55.
Detailed planning lives in
WIP.md, and completed user-facing changes are tracked in
CHANGELOG.md.
| Track | Scope |
|---|---|
v0.1.0 |
Released foundation: workspace, core, logging, testing, docs, and release preflight. |
0.2.0 |
Active ecosystem work, including value packages #13, serde #45/#46, cache/Redis #50/#54/#55, observability #24, and compressor contracts #59. |
| PyPI publish | On hold until project ownership and trusted publishing are confirmed. |
The repository is a single uv workspace with multiple focused distributions.
The meta package remains intentionally small: pip install bluetape installs
only bluetape-core by default, so the core-only default is Redis-free.
| Distribution | Import path | Default install | Status | Purpose |
|---|---|---|---|---|
bluetape |
none | yes | active | Thin meta distribution that depends on bluetape-core. |
bluetape-core |
bluetape.core |
yes | active | Stdlib-only validation and foundation helpers. |
bluetape-audit |
bluetape.audit |
no | active, source workspace | Stdlib-only immutable audit values, explicit limits, and preservation helpers. |
bluetape-id |
bluetape.id |
no | active, source workspace | Stdlib-only UUIDv4/v7 and random/monotonic ULID values. |
bluetape-jwt |
bluetape.jwt |
no | active, source workspace | Strict JWS verification, key rotation, issuance profiles, and optional bounded verification cache. |
bluetape-measure |
bluetape.measure |
no | active, source workspace | Immutable runtime dimension-checked linear measurements. |
bluetape-money |
bluetape.money |
no | active, source workspace | Current ISO 4217 currencies and exact Decimal money with caller-owned FX rates. |
bluetape-async |
bluetape.asyncio |
no | active, source workspace | Stdlib-only bounded structured-concurrency helpers. |
bluetape-codec |
bluetape.codec |
no | active, source workspace | Strict URL-safe Base64 and hexadecimal helpers. |
bluetape-collections |
bluetape.collections |
no | active, source workspace | Stdlib-only eager iterable/list/dict helpers. |
bluetape-compression |
bluetape.compression |
no | active, source workspace | Structural compressor contracts for bounded gzip, zlib, raw-DEFLATE, LZ4, Snappy, and Zstandard payloads. |
bluetape-logging |
bluetape.logging |
no | active | Stdlib logging, contextvars, and redaction helpers. |
bluetape-observability |
bluetape.observability |
no | active, source workspace | API-only OpenTelemetry adapters for resilience and Redis observer events. |
bluetape-resilience |
bluetape.resilience |
no | active, source workspace | Stdlib-only sync/async retry, circuit breaker, bulkhead, and cooperative async timeout policies. |
bluetape-testing |
bluetape.testing |
no | active, internal-first | Pytest helpers used first by this workspace, with a small stable public subset. |
bluetape-serde |
bluetape.serde |
no | active, source workspace | Strict JSON v1 plus an explicit CPython 3.13 Apache Fory extra. |
bluetape-cache |
bluetape.cache |
no | active, source workspace | Stdlib-only bounded sync and async local TTL loading caches. |
bluetape-cache-redis |
bluetape.cache.redis |
no | active, source workspace | Byte-only Redis providers, bounded result envelopes, and sync/async load coordinators. |
bluetape-leader |
bluetape.leader |
no | active, source workspace | Stdlib-only backend-neutral leader and distributed-lock contracts. |
bluetape-leader-redis |
bluetape.leader.redis |
no | active, source workspace | Bounded single-primary Redis locks and leader electors with fencing. |
bluetape-testcontainers |
bluetape.testcontainers |
no | active, source workspace | Ecosystem-owned Redis 8, PostgreSQL 18, and selected-service LocalStack test-server lifecycles and connection details. |
bluetape-benchmark |
bluetape.benchmark |
no | private, source-only | Built/tested benchmark report and comparison contracts; never published. |
bluetape-fastapi |
bluetape.fastapi |
no | planned | FastAPI integration helpers after the core/logging/testing layer stabilizes. |
- Python-native APIs come first; Kotlin, Go, and Rust siblings are references, not source languages to copy.
bluetape-corestays stdlib-only.bluetape-loggingstays stdlib-first and builds onloggingpluscontextvars.bluetape-id,bluetape-measure, andbluetape-moneystay independent, stdlib-only value distributions. Callers own stronger ID coordination, custom unit registries, historical currency policy, and FX providers.bluetape-resiliencestays stdlib-only, separates sync and async policy families, and provides no synchronous timeout or hidden workers.bluetape-observabilityis installed directly, depends only on the OpenTelemetry API at runtime, and leaves SDK/exporter lifecycle to applications.bluetape-testingmay depend onpytest, but starts as an internal support module before promising a broad public API.- The root
bluetapedistribution exposes extras, but it does not create a rootbluetape/__init__.pyimport surface. - The default meta install remains core-only. Cache, Redis, leader coordination,
compression providers, resilience, observability, serde, and Testcontainers
are opt-in; Apache Fory is trusted-internal only and available solely through
the explicit
foryextra.
PyPI publication is still on hold. Until publishing is enabled, use the local workspace commands below instead of registry install commands.
The public install shape after the first PyPI release is:
pip install bluetape
pip install "bluetape[audit]"
pip install "bluetape[asyncio]"
pip install "bluetape[cache]"
pip install "bluetape[cache-redis]"
pip install "bluetape[leader]"
pip install "bluetape[leader-redis]"
pip install "bluetape[codec]"
pip install "bluetape[collections]"
pip install "bluetape[compression]"
pip install "bluetape[compression-lz4]"
pip install "bluetape[compression-snappy]"
pip install "bluetape[compression-zstd]"
pip install "bluetape[compression-native]"
pip install "bluetape[id]"
pip install "bluetape[jwt]"
pip install "bluetape[logging]"
pip install "bluetape[measure]"
pip install "bluetape[money]"
pip install "bluetape[values]"
pip install "bluetape[resilience]"
pip install "bluetape[serde]"
pip install "bluetape[fory]" # CPython 3.13 only
pip install "bluetape[testing]"
pip install "bluetape[testcontainers]"
pip install "bluetape[dev]"
pip install "bluetape[all]"Focused distributions can also be installed directly:
pip install bluetape-core
pip install bluetape-audit
pip install bluetape-id
pip install bluetape-jwt
pip install bluetape-measure
pip install bluetape-money
pip install bluetape-async
pip install bluetape-cache
pip install bluetape-cache-redis
pip install bluetape-leader
pip install bluetape-leader-redis
pip install bluetape-codec
pip install bluetape-collections
pip install bluetape-compression
pip install "bluetape-compression[lz4]"
pip install "bluetape-compression[snappy]"
pip install "bluetape-compression[zstd]"
pip install "bluetape-compression[native]"
pip install bluetape-logging
pip install bluetape-observability
pip install bluetape-resilience
pip install bluetape-serde
pip install "bluetape-serde[fory]" # CPython 3.13 only
pip install bluetape-testing
pip install bluetape-testcontainers
pip install "bluetape-testcontainers[postgres]"
pip install "bluetape-testcontainers[aws]"
pip install "bluetape-testcontainers[all]"For local development from this repository:
uv sync --all-packages --locked
uv sync --package bluetape-compression --extra native --locked
uv run --package bluetape-cache python -c "from bluetape.cache import AsyncTTLCache, TTLCache; assert TTLCache and AsyncTTLCache"
uv run --package bluetape-cache-redis python -c "from bluetape.cache.redis import BinaryEnvelopeFormat, SyncRedisProvider; assert BinaryEnvelopeFormat().format_id == 'binary-v1' and SyncRedisProvider"
uv run --package bluetape-observability python -c "from bluetape.observability.redis import OpenTelemetryRedisObserver; from bluetape.observability.resilience import OpenTelemetryPolicyObserver; assert OpenTelemetryPolicyObserver and OpenTelemetryRedisObserver"
uv run --package bluetape-resilience python -c "from bluetape.resilience import AsyncResiliencePipeline, ResiliencePipeline; assert ResiliencePipeline and AsyncResiliencePipeline"
uv run --package bluetape-serde python -c "import bluetape.serde"
uv sync --all-packages --extra fory --python 3.13.14 --locked
uv run --package bluetape-serde --extra fory --python 3.13.14 python -c "import bluetape.serde.fory"
uv run --package bluetape-testcontainers --extra all --group test --python 3.13.14 pytest \
-m testcontainers packages/bluetape-testcontainers -qThe #54 Redis provider tests consume RedisServer from the ecosystem wrapper
and verify Redis 8 commands, TTL, NX, Lua compare-and-delete, lifecycle, ACL,
and redaction behavior. Values use explicit serialize -> compress -> store
composition. Issue #55 provides bounded sync/async Redis lease coordination
while preserving the opt-in package boundary.
Issue #15 adds caller-owned PostgresServer and LocalStackServer adapters
using postgres:18-alpine and localstack/localstack:4.14.0. Their provider
extras remain focused-package choices; the root bluetape[testcontainers]
extra stays base-only.
Build the current focused wheel, install it into an isolated environment, and run a strict JSON roundtrip:
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
uv build --package bluetape-serde --out-dir "$tmp_dir/dist"
uv venv "$tmp_dir/venv"
uv pip install --python "$tmp_dir/venv/bin/python" "$tmp_dir"/dist/bluetape_serde-*.whl
"$tmp_dir/venv/bin/python" -c 'from bluetape.serde import PayloadMetadata, TrustProfile, json_deserialize, json_serialize; m = PayloadMetadata(format="json", version=1, content_type="application/json", trust_profile=TrustProfile.UNTRUSTED); p = json_serialize({"order_id": 42}, metadata=m); assert json_deserialize(p, expected_metadata=m) == {"order_id": 42}'Only the local workspace and local-wheel paths are runnable today. The pip
commands above remain unavailable from PyPI until publication is enabled.
Fory is not pulled by the base, serde, dev, or all extras.
from bluetape.core import require_not_blank
service_name = require_not_blank("orders", "service_name")import logging
from bluetape.logging import ContextLogFilter, log_context
logger = logging.getLogger("orders")
logger.addFilter(ContextLogFilter())
with log_context(trace_id="trace-123", tenant="blue"):
logger.info("order accepted")from bluetape.testing import eventually
eventually(lambda: cache.get("ready"), timeout=2.0)from bluetape.collections import chunked, group_by
chunks = chunked(range(5), 2)
by_initial = group_by(["ant", "ape", "bee"], lambda value: value[0])from bluetape.codec import base64url_encode
from bluetape.compression import Compressor, GzipCompressor
from bluetape.compression.native import ZstdCompressor
token = base64url_encode(b"order:42")
compressor: Compressor = GzipCompressor()
assert compressor.decompress(compressor.compress(token.encode("ascii"))) == token.encode("ascii")
assert ZstdCompressor().algorithm == "zstd-frame"bluetape.codec decodes only canonical URL-safe Base64 and strict hex text.
bluetape.compression provides a structural Compressor Protocol and frozen
implementations for gzip, zlib, raw DEFLATE, LZ4 frame, raw Snappy, and
Zstandard frame. Decompression defaults to a 64 MiB logical returned-payload
limit and rejects invalid, truncated, or trailing data. Native providers are
available only through explicit extras. The feature and failure rules align
with sibling bluetape libraries, but cross-language wire compatibility is not
promised. Neither codec nor compression is part of the default install.
import asyncio
from bluetape.asyncio import map_bounded
async def fetch_order(order_id: int) -> str:
await asyncio.sleep(0.01)
return f"order-{order_id}"
async def main() -> None:
print(await map_bounded([1, 2, 3], fetch_order, limit=2, timeout=1.0))
asyncio.run(main())Use a synchronous loop for simple sequential work and asyncio.gather only
when the coroutine set is already small and bounded. Use map_bounded when an
input iterable can grow and the caller must set a cooperative concurrency cap.
from bluetape.cache import TTLCache
cache = TTLCache[str, object](default_ttl=30, max_size=100, max_inflight=8)
calls: list[str] = []
def load_order(key: str) -> object:
calls.append(key)
return {"key": key}
order = cache.get_or_load(
"tenant-blue:42",
load_order,
ttl=5,
)
assert cache.get("tenant-blue:42") is order
assert cache.get_or_load("tenant-blue:42", load_order) is order
assert calls == ["tenant-blue:42"]
cache.set("none", None)
assert cache.get("none") is None
assert cache.invalidate("tenant-blue:42") is True
assert cache.invalidate("tenant-blue:42") is False
try:
cache.get("tenant-blue:42")
except KeyError as error:
assert error.args == ("tenant-blue:42",)
assert len(cache) == 1import asyncio
from bluetape.cache import AsyncTTLCache
async def cache_example() -> None:
cache = AsyncTTLCache[str, object](
default_ttl=30,
max_size=100,
max_inflight=8,
)
calls: list[str] = []
async def load_order(key: str) -> object:
calls.append(key)
return {"key": key}
order = await cache.get_or_load("tenant-blue:42", load_order, ttl=5)
assert await cache.get("tenant-blue:42") is order
assert await cache.get_or_load("tenant-blue:42", load_order) is order
assert calls == ["tenant-blue:42"]
await cache.set("none", None)
assert await cache.get("none") is None
assert await cache.invalidate("tenant-blue:42") is True
assert await cache.invalidate("tenant-blue:42") is False
try:
await cache.get("tenant-blue:42")
except KeyError as error:
assert error.args == ("tenant-blue:42",)
assert await cache.size() == 1
asyncio.run(cache_example())The sync and async caches share bounded LRU/TTL, cached-None, same-key loader
coalescing, mutation supersession, and immutable statistics semantics. Include
tenant and authorization context in keys, redact KeyError(key) and loader
exceptions before logging, and give async callers deadlines because a
cancellation-resistant loader keeps its max_inflight slot until terminal.
Redis-backed storage and bounded cross-process load coordination are available
through the opt-in provider below. RESP3 near-cache invalidation remains a
separate upstream-blocked track in issue #56.
from bluetape.cache.redis import BinaryEnvelopeFormat, ResultEnvelopeCodec, SyncRedisProvider
# Supply an application-owned PayloadCodec.
codec = ResultEnvelopeCodec(
payload_codec=payload_codec,
envelope_format=BinaryEnvelopeFormat(),
)
with SyncRedisProvider.from_url("redis://localhost:6379/0") as provider:
provider.set("orders:result:42", codec.encode("owner-42", result), ttl=30.0)
stored = provider.get("orders:result:42")
decoded = None if stored is None else codec.decode(
stored, expected_owner_token="owner-42"
)The focused package also provides AsyncRedisProvider, JsonEnvelopeFormat,
optional explicit ZstdCompressor, stable redacted errors, and borrowed
or factory-owned client lifecycles. See its package README for the complete
contract and rollout rules.
from bluetape.serde import (
PayloadMetadata,
TrustProfile,
json_deserialize,
json_serialize,
)
producer_metadata = PayloadMetadata(
format="json",
version=1,
content_type="application/json",
trust_profile=TrustProfile.UNTRUSTED,
)
payload = json_serialize({"order_id": 42}, metadata=producer_metadata)
# Construct consumer policy independently from authenticated configuration;
# never copy expected policy from payload.metadata.
consumer_policy = PayloadMetadata(
format="json",
version=1,
content_type="application/json",
trust_profile=TrustProfile.UNTRUSTED,
)
assert json_deserialize(payload, expected_metadata=consumer_policy) == {"order_id": 42}UNTRUSTED is the recommended default. TRUSTED_INTERNAL is only for a closed
boundary with an authenticated and authorized producer; network location and
payload claims are insufficient. Both profiles enforce identical strict UTF-8
JSON, exact metadata, duplicate-key/non-finite-number rejection, 16 MiB default
input/output limits, depth 100 by default, a hard depth ceiling of 256, and a
fixed 640-decimal-digit integer limit on both encode and decode. Strings must
contain Unicode scalar values; decode normalizes valid escaped surrogate pairs
to their non-BMP scalar and rejects unpaired surrogates.
These byte limits are not a fixed process-memory guarantee. See the package
README for error handling and versioned rollout/rollback guidance.
SerdeError covers stable serde domain failures; caller type and configuration
mistakes remain native TypeError or ValueError.
Install the explicit fory extra only on CPython 3.13. Each application route
owns a fixed (schema_id, schema_version, type_id) tuple and one exact root
type. Consumers construct expected metadata and registration independently;
payloads never select an adapter, class, schema, or fallback. Deploy readers
before writers, move schema changes to a new versioned route, and stop Fory
writes when predeclared canary error or latency thresholds are breached. Keep
the old codec on a separate route until drain evidence is complete.
Fory is limited to authenticated and authorized internal producers. Its byte, depth, schema, and concurrency limits are acceptance bounds, not hard CPU/RSS ceilings; use a separately constrained process for hard containment. Telemetry may include only operation, stable error code, envelope size, success/failure, latency, and a fixed route ID. Do not record payloads, decoded values, provider exception text, tracebacks, or caller-controlled high-cardinality names.
The three value packages are independent opt-ins. bluetape-id provides
canonical UUID/ULID values without distributed-order or secrecy claims;
bluetape-measure provides linear runtime dimensions with caller-owned custom
units; bluetape-money provides exact Decimal arithmetic over the committed
current ISO snapshot while callers own rates and historical policy.
from decimal import ROUND_HALF_UP
from bluetape.id import uuid7
from bluetape.measure import KILOMETER, METER, Measure
from bluetape.money import USD, Money
identifier = uuid7()
distance = Measure(1.25, KILOMETER).to(METER)
price = Money.of("12.345", USD).quantize(rounding=ROUND_HALF_UP)
assert identifier.version == 7
assert distance == Measure(1250, METER)
assert price == Money.of("12.35", USD)Open the audit boundary SVG source.
bluetape-audit is an independent stdlib-only opt-in for immutable audit
event values, explicit limits, safe failures, and adapter preservation tests.
Applications serialize payload bytes; caller-owned adapters invoke
validate_audit_event immediately before their first side effect; caller-owned
storage, outboxes, relays, and transports establish durability and delivery.
The default bluetape install remains core-only. See the
English package guide or
Korean package guide.
| Track | Plan |
|---|---|
v0.1.0 |
Released the initial core, logging, testing, documentation, and release preflight foundation. |
0.2.0 |
Track ecosystem issues #7-#34, including value packages #13 and observability #24, plus cache/Redis #50/#54/#55, compressor contracts #59, and serialization #45/#46. |
| Later | Add FastAPI helpers and workshop examples only after the base packages are stable. |
Project planning and release policy:
The 0.2.0 milestone tracks Python-native equivalents for proven bluetape-go
and bluetape4k ecosystem capabilities. Research issues intentionally come before
implementation where the Python package boundary or dependency choice is not
obvious. See WIP.md for the issue-by-issue task queue and
docs/research/README.md for research gates.
uv sync --all-packages
uv build --all-packages
uv run pytest
uv run ruff check .
uv run ruff format --check .


