feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag#220
Merged
Conversation
Rename PipelineAnalyticsProcessor to EventProcessor and surface a single `track_event` method on it. Remove `record_evaluation_event` and the per-flag autocapture wiring in `Flags.get_flag`; flag-evaluation telemetry is no longer emitted automatically. The PipelineAnalyticsConfig dataclass is renamed to EventProcessorConfig and the matching client kwarg becomes `event_processor_config`. BREAKING CHANGE: PipelineAnalyticsProcessor/PipelineAnalyticsConfig are renamed to EventProcessor/EventProcessorConfig. The `pipeline_analytics_config` kwarg on `Flagsmith` is renamed to `event_processor_config`. Automatic flag-evaluation events are no longer recorded by the event processor.
for more information, see https://pre-commit.ci
Zaimwa9
reviewed
May 28, 2026
Add `Flagsmith.track_exposure_event(feature_name, identifier, value, traits, metadata, timestamp)` and `EventProcessor.track_exposure_event` for emitting "flag_exposure" events. Value is narrowed to Optional[str] since variants are named. Add `Flagsmith.get_experiment_flag(feature_name, identifier, traits)` that calls `get_identity_flags` + `get_flag`, fires `track_exposure_event` (skipped for DefaultFlag results to keep experiment data clean), and returns the Flag/DefaultFlag. Reshape `track_event` and `track_exposure_event` signatures: rename `event_name` → `event`, `identity_identifier` → `identifier`; add `value`, `timestamp` params. Extract a private `_buffer_event` helper to share buffer + auto-flush logic between the two event types.
for more information, see https://pre-commit.ci
…/events Align wire format with the analytics-pipeline RFC (Flagsmith/flagsmith-analytics-pipeline#9): - Fields: event, feature_name, identifier, value, traits, metadata, timestamp. - Fold event_type discriminator into the event field; use reserved literal "$flag_exposure" for exposure events (FLAG_EXPOSURE_EVENT constant). - Drop event_id, event_type, identity_identifier, enabled, evaluated_at, and the duplicated top-level environment_key from the POST body. - Switch endpoint path from /v1/analytics/batch to /v1/events.
for more information, see https://pre-commit.ci
…_url and default it Default points to https://events.api.flagsmith.com/ so callers don't have to specify it for cloud Flagsmith. Self-hosted users override via the events_api_url field. BREAKING CHANGE: EventProcessorConfig field analytics_server_url renamed to events_api_url.
for more information, see https://pre-commit.ci
Introduce `enable_events: bool = False` on Flagsmith() as the on/off gate for the event processor. `event_processor_config` stays as an optional tuning knob (URL override for self-hosted, buffer/flush settings) and raises ValueError if supplied without `enable_events=True`. Split the formerly-overloaded `_initialise_analytics` into two narrow methods: `_initialise_analytics` (legacy AnalyticsProcessor only) and `_initialise_events` (EventProcessor only). Update track_event / track_exposure_event error messages to point at `enable_events=True`. BREAKING CHANGE: passing `event_processor_config` no longer enables the event processor on its own — also pass `enable_events=True`.
There was a problem hiding this comment.
Pull request overview
Adds the new experimentation events surface to the SDK, replacing the prior pipeline analytics API with explicit custom-event and flag-exposure tracking.
Changes:
- Introduces
EventProcessor/EventProcessorConfigand the new/v1/eventspayload shape. - Adds
Flagsmith.track_event,track_exposure_event, andget_experiment_flag. - Removes automatic per-flag pipeline evaluation event capture and updates tests/fixtures for the new events API.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
flagsmith/analytics.py |
Replaces pipeline analytics processor/config with event processor/config and new event payload/flush endpoint. |
flagsmith/flagsmith.py |
Wires event processor construction and adds the new public event/experiment methods. |
flagsmith/models.py |
Removes pipeline analytics state and automatic evaluation-event recording from Flags. |
flagsmith/__init__.py |
Re-exports EventProcessorConfig instead of PipelineAnalyticsConfig. |
tests/conftest.py |
Renames analytics fixtures to event processor fixtures. |
tests/test_event_processor.py |
Adds coverage for buffering, flushing, failure requeue, and lifecycle behavior of the new processor. |
tests/test_flagsmith.py |
Updates client-level tests for event gating, delegation, and experiment exposure tracking. |
tests/test_pipeline_analytics.py |
Deletes tests for the removed pipeline analytics behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…esolving flag Move the event-processor presence check to the top of get_experiment_flag so the method fails immediately when events are disabled, instead of issuing an identity-flag request only to then raise from track_exposure_event. Matches the early-guard pattern in track_event and track_exposure_event. Also unify the three error messages to a single terse declarative sentence each, matching the existing codebase style. Addresses review feedback on #220.
for more information, see https://pre-commit.ci
Zaimwa9
requested changes
May 29, 2026
Contributor
Zaimwa9
left a comment
There was a problem hiding this comment.
Couple of minor comments (except the timestamp). Server-side it looks less important but still wondering whether we shouldn't deduplicate (at least in the same buffer) the exposure events.
That might be just a client issue (deal with re-rendering etc).
Only intended use case was back-filling events from other sources, which is overkill for now. EventProcessor always stamps datetime.now() at buffer time.
…road type DB stores value as string, so it must be string on the wire. Move the conversion to the single deepest point (`_buffer_event`) instead of forcing callers / Flagsmith.* to stringify. All public methods now accept Optional[Union[str, int, float, bool]] symmetrically; get_experiment_flag just passes flag.value through.
Mirrors the server-side validation added in Flagsmith/flagsmith-analytics-pipeline@8fb51c0 — Flagsmith.track_event raises ValueError when the event name starts with "$" and isn't a known system event. Avoids a fire-and-forget 400 round-trip; user gets the error at the call site. Only applies to track_event (user-controlled name); track_exposure_event sets the literal internally and is unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an experimentation-events surface to the SDK and rewires the underlying processor end-to-end. Wire schema agreed in Flagsmith/flagsmith-analytics-pipeline#9.
New public API on
Flagsmith:track_event(event, identifier=None, value=None, traits=None, metadata=None, timestamp=None)— record arbitrary product events (e.g."purchase").track_exposure_event(feature_name, identifier=None, value=None, traits=None, metadata=None, timestamp=None)— record that an identity was exposed to a flag/variant. Internally emits an event with the reserved name$flag_exposure.get_experiment_flag(feature_name, identifier, traits=None) -> Flag | DefaultFlag— resolves an identity flag and firestrack_exposure_event(skipped when the resolved flag is aDefaultFlag).Opt-in via constructor:
events_api_url:https://events.api.flagsmith.com/(verified live).event_processor_configwithoutenable_events=TrueraisesValueError(no silent no-ops).enable_events=True, any of the three new methods raiseValueError.Wire payload (
POST {events_api_url}/v1/events):{ "events": [ { "event": "purchase", // or "$flag_exposure" for exposures "feature_name": null, // populated only for $flag_exposure "identifier": "user_42", "value": 99.5, "traits": {"plan": "premium"}, "metadata": {"sdk_version": "...", ...}, "timestamp": 1748462400000 } ] }Auth:
X-Environment-Keyheader (Redis-validated server-side).Internals:
EventProcessor(renamed fromPipelineAnalyticsProcessor) with batched async flush viaFuturesSession. Defaults: 1000-item buffer, 10s timer. Re-queues on failure.EventProcessorConfig(renamed fromPipelineAnalyticsConfig) — fields:events_api_url(default = cloud),max_buffer_items=1000,flush_interval_seconds=10.0._initialise_analyticssplit into two narrow methods:_initialise_analytics(legacyAnalyticsProcessoronly) and_initialise_events(newEventProcessoronly).record_evaluation_eventand its per-flag autocapture inFlags.get_flag— flag-evaluation events are no longer auto-emitted; opt back in via an OpenFeatureafterhook at the provider layer if desired._pipeline_analytics_processor/_identity_identifier/_traitsfields onFlagsand their constructor kwargs.Breaking changes
PipelineAnalyticsProcessor→EventProcessor;PipelineAnalyticsConfig→EventProcessorConfig.Flagsmith(pipeline_analytics_config=…)→Flagsmith(enable_events=True, event_processor_config=…).EventProcessorConfig.analytics_server_url→events_api_url.EventProcessor.record_custom_event→track_event; signature reshaped (event,identifier,value,traits,metadata,timestamp).EventProcessor.record_evaluation_eventremoved.event_id→event,evaluated_at→timestamp,identity_identifier→identifier;event_type,enabled, and top-level bodyenvironment_keydropped;feature_nameadded as a top-level field. Endpoint changed from/v1/analytics/batchto/v1/events.Test plan
poetry run pytest— 99 passed locally.https://events.api.flagsmith.com/healthreturns{"status":"healthy"}.POST https://events.api.flagsmith.com/v1/eventswith a bogus env key returns403 Unknown environment key(auth + route wired).Flagsmith(environment_key=…, enable_events=True).track_event("purchase", identifier="user_1", value=99.5)→ event lands in pipeline.get_experiment_flag(feature_name=…, identifier=…)returns theFlag, emits exactly one$flag_exposureevent, and skips emission forDefaultFlag.