feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag#220
feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag#220gagantrivedi wants to merge 11 commits into
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
|
|
||
| @dataclass | ||
| class PipelineAnalyticsConfig: | ||
| class EventProcessorConfig: |
There was a problem hiding this comment.
I added this autoTrackEvaluations property in the JS sdk (naming completely open to discussion), to opt-in/out of auto flag evaluation but I see that you completely removed the flag evaluations
There was a problem hiding this comment.
I'd prefer removing this entirely — I don't see a use case for it anytime soon. The less code, the better.
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.
| (i.e. the feature was not present and was served via the | ||
| `default_flag_handler`), to keep experimentation data clean. | ||
| """ | ||
| flag = self.get_identity_flags(identifier, traits).get_flag(feature_name) |
There was a problem hiding this comment.
Fixed in ef42a70 — added the early guard at the top of get_experiment_flag so it fails immediately when events are disabled, mirroring the pattern in track_event / track_exposure_event. Also unified the three error messages to a single terse declarative sentence each.
…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
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).
| identity_identifier: typing.Optional[str] = None, | ||
| feature_name: str, | ||
| identifier: typing.Optional[str] = None, | ||
| value: typing.Optional[str] = None, |
There was a problem hiding this comment.
Is it intentional? Should be the same as track_event Option[Union[str,int,float]]
| value: typing.Optional[typing.Union[str, int, float]] = None, | ||
| traits: typing.Optional[typing.Dict[str, typing.Any]] = None, | ||
| metadata: typing.Optional[typing.Dict[str, typing.Any]] = None, | ||
| timestamp: typing.Optional[datetime] = None, |
There was a problem hiding this comment.
The only caller using the timestamp is in a test and I don't think this should be a parameter. We would be better owning the value entirely here https://github.com/Flagsmith/flagsmith-python-client/pull/220/changes#diff-6ab1fe663f72c6d402a35766b992df078f6402650191f09d3df831a3d9aeb439R166-R167 and dropping the parameter in all the track_* methods
| (i.e. the feature was not present and was served via the | ||
| `default_flag_handler`), to keep experimentation data clean. | ||
| """ | ||
| flag = self.get_identity_flags(identifier, traits).get_flag(feature_name) |
There was a problem hiding this comment.
We could also raise
if not self._event_processor:
raise ValueError(
"Event processor is not configured. "
"Set enable_events=True to use track_exposure_event."
)
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.