Python SDK
This is the in-code half of smplkit: resolve flags, read config, control log levels, and emit audit events from one SmplClient. Manage the underlying resources — creating flags, configs, loggers — with the MCP tools or the Console.
Install
pip install smplkit-sdkRequires Python 3.10+. The version installed is the latest published on PyPI. The loguru logging adapter ships as an extra:
pip install "smplkit-sdk[loguru]"Initialize the client
One client exposes all four runtime products as sub-namespaces — client.flags, client.config, client.logging, and client.audit. The SDK key (sk_api_...) is read from the SMPLKIT_API_KEY environment variable or the ~/.smplkit profile, so you rarely pass it in code. Set environment (and optionally service) once on the client — it is never a per-call argument; flag, config, and logger resolution is per-environment.
from smplkit import Context, SmplClient
with SmplClient(environment="production", service="my-service") as client:
client.wait_until_ready() # optional: pre-fetch flags + configs before serving traffic
...wait_until_ready() pre-fetches every flag and config and opens the live-updates WebSocket, giving you a deterministic boot before the first request.
Async: swap in AsyncSmplClient and await the I/O methods. Flag declarations, .get(), and config.subscribe() stay synchronous; wait_until_ready, config.bind, config.get_value, logging.install, and audit.events.record are awaited.
from smplkit import AsyncSmplClient
async with AsyncSmplClient(environment="production", service="my-service") as client:
await client.wait_until_ready()
...Flags
When you need a runtime on/off switch or a typed, targetable value. Declare a typed handle with a safe default, attach context, then evaluate with .get(). The default is served if the flag is missing or smplkit is unreachable — pick the conservative value.
from smplkit import Context
checkout = client.flags.boolean_flag("checkout-v2", default=False)
banner = client.flags.string_flag("banner-color", default="red")
retries = client.flags.number_flag("max-retries", default=3)
# Ambient context — set once per request, typically from middleware.
client.set_context([
Context("user", "alice@acme.com", plan="enterprise", beta_tester=True),
Context("account", "1234", region="us", industry="technology"),
])
if checkout.get():
render_new_checkout()Python uses ambient context via client.set_context([...]) (it returns a scope usable as a with block to auto-revert). You can also pass context=[...] to a single checkout.get(context=[...]) call.
→ Full reference (every language, all context modes, change listeners): Flags Runtime
Config
When your app needs server-managed configuration that updates live. Read a single value with get_value(id, key, default) (the form with a default never raises), or bind() a Pydantic model or dict to get a live object that is mutated in place as the config changes.
from pydantic import BaseModel
class Plan(BaseModel):
max_seats: int = 5
tier: str = "free"
# bind() returns a live object — updates land in place, no re-fetch.
billing = client.config.bind("billing", Plan())
print(billing.max_seats)
# Read one value directly, with a default served if the key is absent.
slow_ms = client.config.get_value("database", "slow_query_threshold_ms", 500)
# Or subscribe to a live dict-like view.
app_name = client.config.subscribe("common")["app.name"]Logging
When you want to control log levels at runtime from smplkit instead of redeploying. One install() call hands your log levels to smplkit; use your loggers normally afterward and levels are server-driven. Adapters auto-load for the Python standard logging module and (with the [loguru] extra) loguru — no explicit wiring needed.
import logging
client.logging.install() # await client.logging.install() on AsyncSmplClient
logging.getLogger("myapp.db").debug("levels are now controlled by smplkit")Audit
When you need a durable, queryable record of who did what. Call client.audit.events.record(...) — fire-and-forget by default (buffered, retried); pass flush=True (or call events.flush(...)) for durability before exit. The first three arguments — event_type, resource_type, resource_id — are required. resource_type must not start with smpl. (reserved).
client.audit.events.record(
event_type="invoice.created",
resource_type="invoice",
resource_id="inv-1",
category="billing",
severity="WARN",
data={"snapshot": {"total_cents": 4900, "currency": "USD"}},
flush=True, # omit to flush asynchronously
)The author + operate loop
Authoring a feature is a tight loop: write the SDK call, provision the resource with the MCP tools, then verify. For a checkout kill-switch:
- Write — declare the handle and evaluate it:
checkout = client.flags.boolean_flag("checkout-v2", default=False)…if checkout.get(): render_new_checkout(). - Provision —
create_flag(key="checkout-v2", type="boolean", default=false). - Verify —
get_flag(key="checkout-v2").
The flag id in code must equal the key you provision (checkout-v2). The same write → create_config/set_log_level → get_config/query_events loop applies to config, logging, and audit.

