e-volv
Docs menu

Python

Not released yet. The Python SDK for e-volv Launch is built and conformance-tested, but its first release with flags is not on the registry yet. This page documents that release: until it ships, pip install e-volv-logs finds either nothing or an earlier Observer-only version without flags. The SDKs index shows what can be installed today.

e-volv-logs is the e-volv Launch SDK for Python 3.10 and later — the same package that ships your logs. It is a server SDK: with a server key (evk_…) it receives the environment’s full ruleset and evaluates flags locally, so a flag check never performs I/O, never blocks and never throws. Delivery is a stream of change notifications (SSE) with a polling fallback, a last-known cache on disk, and exposure tracking that rides the same transport as your logs. The behaviour is pinned by the Launch SDK contract and both of its conformance suites run in CI.

Install

shell
pip install e-volv-logs          # or: uv add e-volv-logs

The distribution exposes two import names for the same package — e_volv_logs and evolve_logs. These docs use e_volv_logs; every sample reads identically with either.

OpenFeature provider

If your stack already speaks OpenFeature, install the provider — it pulls in the SDK and the OpenFeature client:

shell
pip install e-volv-openfeature   # pulls in e-volv-logs and openfeature-sdk
python
from openfeature import api
from openfeature.evaluation_context import EvaluationContext

from e_volv_logs import init
from evolve_openfeature import EvolveProvider

client = init(
    key="evk_…",                      # server key
    url="https://api.e-volv.io/api/public/v1/logs",
)
api.set_provider(EvolveProvider(client.flags))

details = api.get_client().get_boolean_details(
    "checkout.new",
    False,
    EvaluationContext(targeting_key="u_1", attributes={"plan": "pro"}),
)
# details.value, details.variant, details.flag_metadata["evolveReason"]

Reason mapping: RULE:* and TARGET_MATCH become TARGETING_MATCH, ROLLOUT becomes SPLIT, OFF/KILLED become DISABLED, and error reasons carry the matching OpenFeature error code (FLAG_NOT_FOUND, TYPE_MISMATCH, GENERAL). The native e-volv reason is always available on flag_metadata["evolveReason"].

Native API

python
import os

from e_volv_logs import init

client = init(
    key=os.environ["EVOLVE_KEY"],     # server key (evk_…)
    url="https://api.e-volv.io/api/public/v1/logs",
    service="checkout-api",
)
client.flags.ready(timeout=5.0)  # optional: the only call that waits

if client.flags.bool(
    "checkout.new", False, {"targetingKey": user.id, "plan": user.plan}
):
    render_new_checkout()

# The full detail when you need the variant and the reason:
d = client.flags.detail("checkout.banner", "default", {"targetingKey": user.id})
# d.value, d.variant ("on" | "off" | "control" | …), d.reason
# ("RULE:0" | "ROLLOUT" | "TARGET_MATCH" | "DEFAULT" | "FLAG_NOT_FOUND" | …)

Evaluation methods: bool, string, number, json and detail — all synchronous, all safe. detail returns an Evaluation dataclass with value, variant and reason; detail_typed(kind, key, default, context) is the typed variant behind the helpers when you want the variant and reason together — a served value of the wrong kind yields your default with reason TYPE_MISMATCH and records no exposure. A flag absent from the ruleset returns your default with FLAG_NOT_FOUND.

Options

Flags are configured with a FlagsOptions dataclass passed as the flags keyword of init():

OptionDefaultMeaning
enabledTrueStart the flags client. False gives a defaults-only stub.
mode'stream'stream | poll | offline. Offline never contacts the control plane (bootstrap snapshot or defaults only).
poll_interval_seconds30 (min 15)Poll period once streaming has fallen back.
urlderivedBase URL ending in /api/public/v1/flags; defaults to the origin of the Observer url argument, else the e-volv cloud.
cachetemp dirWhere the last-known ruleset is kept (one file per key, hashed name, atomic write). False disables.
bootstrapnoneA bundled ruleset used before the first fetch completes.
exposures_enabledTrueRecord an exposure on each evaluation of a flag present in the ruleset.
exposures_sample_rate1.0Probability an evaluation is recorded; sent as sampleRate (clamped to 0–1).
exposures_dedupe_window_seconds60Suppress repeats of the same (flag, variant, context kind, subject) within the window; suppressed counts ride the next exposure.
exposures_send_attributesFalseSend context attributes with exposures (private attributes are always removed).
private_attributes[]Attribute names never sent anywhere.
stale_after_seconds300Internal: seconds without a confirmed update before a one-time staleness warning.

Install check and lifecycle

python
# Install check: is this key bound, and to which environment?
ping = client.flags.verify()
# {'environment': 'production', 'keyKind': 'server', 'flags': 42, 'etag': '3f2a…'}
# None when the control plane did not answer.

flags.ready(timeout=5.0) waits for the first ruleset (only if you choose to; it returns a bool and returns immediately once a ruleset has arrived), flags.last_updated_at reports the last confirmed update as epoch seconds, flags.mode reports the current delivery mode, and flags.on_change(fn) notifies with the keys whose values changed — it returns an unsubscribe function.

python
# On shutdown (serverless teardown, before os._exit): stop delivery,
# persist the last-known ruleset, flush exposures.
client.close()

Prefork workers: under gunicorn, uWSGI or Celery the delivery thread dies across fork(). The SDK registers os.register_at_fork and relaunches delivery in the child, so workers keep receiving ruleset updates.

Troubleshooting by status code

Evaluation never breaks because of the control plane — it degrades. What each response means for the SDK (contract §2.6):

ResponseSDK behaviour
401Key invalid or revoked. The SDK keeps serving held values or defaults, logs once, and retries bootstrap every 5 minutes.
403 — lacks the scope flags:readFlags are disabled for this key with one warning; telemetry is unaffected. No retry until re-init.
404Launch is not enabled for the workspace, or the key is not bound to an environment. Same handling as 401.
429Honours Retry-After (seconds). The delivery rate limit is 3,000 requests/min per key.
5xx, timeout, network errorRetries with full-jitter exponential backoff while serving the last held values.

Guarantees

  • A flag check is local and synchronous — bounded by CPU, not the network; no await, and ready() is the only call that waits, and only when you ask it to.
  • An unreachable or slow control plane never changes an answer mid-flag: the last held ruleset (or a valid cache, or your defaults) keeps serving while the SDK backs off and reconnects.
  • Cold start reads the on-disk cache instantly and swaps to a fresh ruleset without a flicker for unchanged flags; corrupt or other-environment caches are ignored and overwritten.
  • Exposures are de-duplicated, sampled, capped and retried with the same backoff as your logs — telemetry you can trust as much as the flag answers themselves.
  • Flag evaluation is byte-for-byte identical to every other e-volv SDK: the Python kernel is a port held to packages/flags-kernel/fixture.json, and both conformance suites run in CI.

Next: the e-volv Launch overview explains the model, the HTTP API page documents the wire format the SDK speaks, and the SDKs index lists every platform.