Skip to content

feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag#220

Open
gagantrivedi wants to merge 11 commits into
mainfrom
feat/event-processor
Open

feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag#220
gagantrivedi wants to merge 11 commits into
mainfrom
feat/event-processor

Conversation

@gagantrivedi
Copy link
Copy Markdown
Member

@gagantrivedi gagantrivedi commented May 28, 2026

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 fires track_exposure_event (skipped when the resolved flag is a DefaultFlag).

Opt-in via constructor:

Flagsmith(
    environment_key="...",
    enable_events=True,                          # gate
    event_processor_config=EventProcessorConfig( # optional tuning (URL override, buffer/flush)
        events_api_url="https://my-self-host/",
    ),
)
  • Default events_api_url: https://events.api.flagsmith.com/ (verified live).
  • event_processor_config without enable_events=True raises ValueError (no silent no-ops).
  • Without enable_events=True, any of the three new methods raise ValueError.

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-Key header (Redis-validated server-side).

Internals:

  • New EventProcessor (renamed from PipelineAnalyticsProcessor) with batched async flush via FuturesSession. Defaults: 1000-item buffer, 10s timer. Re-queues on failure.
  • EventProcessorConfig (renamed from PipelineAnalyticsConfig) — fields: events_api_url (default = cloud), max_buffer_items=1000, flush_interval_seconds=10.0.
  • _initialise_analytics split into two narrow methods: _initialise_analytics (legacy AnalyticsProcessor only) and _initialise_events (new EventProcessor only).
  • Removed record_evaluation_event and its per-flag autocapture in Flags.get_flag — flag-evaluation events are no longer auto-emitted; opt back in via an OpenFeature after hook at the provider layer if desired.
  • Removed now-dead _pipeline_analytics_processor / _identity_identifier / _traits fields on Flags and their constructor kwargs.

Breaking changes

  • PipelineAnalyticsProcessorEventProcessor; PipelineAnalyticsConfigEventProcessorConfig.
  • Flagsmith(pipeline_analytics_config=…)Flagsmith(enable_events=True, event_processor_config=…).
  • EventProcessorConfig.analytics_server_urlevents_api_url.
  • EventProcessor.record_custom_eventtrack_event; signature reshaped (event, identifier, value, traits, metadata, timestamp).
  • EventProcessor.record_evaluation_event removed.
  • Wire payload reshaped: event_idevent, evaluated_attimestamp, identity_identifieridentifier; event_type, enabled, and top-level body environment_key dropped; feature_name added as a top-level field. Endpoint changed from /v1/analytics/batch to /v1/events.
  • Flag-evaluation autocapture is no longer emitted by this client.

Test plan

  • poetry run pytest — 99 passed locally.
  • Verified https://events.api.flagsmith.com/health returns {"status":"healthy"}.
  • Verified POST https://events.api.flagsmith.com/v1/events with a bogus env key returns 403 Unknown environment key (auth + route wired).
  • CI green.
  • Manual smoke against staging: Flagsmith(environment_key=…, enable_events=True).track_event("purchase", identifier="user_1", value=99.5) → event lands in pipeline.
  • Manual smoke: get_experiment_flag(feature_name=…, identifier=…) returns the Flag, emits exactly one $flag_exposure event, and skips emission for DefaultFlag.

gagantrivedi and others added 2 commits May 28, 2026 16:26
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.
Copy link
Copy Markdown
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, agreed on the schema knowing it's open. One question around complete flag evaluations.
I'm open to drop it entirely personally.

Otherwise approvable as of, just need to port it to openfeature as you mentioned

Comment thread flagsmith/analytics.py

@dataclass
class PipelineAnalyticsConfig:
class EventProcessorConfig:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer removing this entirely — I don't see a use case for it anytime soon. The less code, the better.

gagantrivedi and others added 7 commits May 29, 2026 12:56
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.
…/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.
…_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.
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`.
@gagantrivedi gagantrivedi changed the title feat!: introduce EventProcessor, drop flag-evaluation autocapture feat!: experimentation surface — track_event, track_exposure_event, get_experiment_flag May 29, 2026
@gagantrivedi gagantrivedi marked this pull request as ready for review May 29, 2026 10:43
@gagantrivedi gagantrivedi requested a review from a team as a code owner May 29, 2026 10:43
@gagantrivedi gagantrivedi requested review from emyller and removed request for a team May 29, 2026 10:43
@gagantrivedi gagantrivedi changed the title feat!: experimentation surface — track_event, track_exposure_event, get_experiment_flag feat: experimentation surface — track_event, track_exposure_event, get_experiment_flag May 29, 2026
@gagantrivedi gagantrivedi requested review from Zaimwa9 and Copilot May 29, 2026 10:43
@gagantrivedi gagantrivedi removed the request for review from emyller May 29, 2026 10:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / EventProcessorConfig and the new /v1/events payload shape.
  • Adds Flagsmith.track_event, track_exposure_event, and get_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.

Comment thread flagsmith/flagsmith.py
(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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Copy Markdown
Member Author

@gagantrivedi gagantrivedi May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

gagantrivedi and others added 2 commits May 29, 2026 17:11
…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.
Copy link
Copy Markdown
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread flagsmith/analytics.py
identity_identifier: typing.Optional[str] = None,
feature_name: str,
identifier: typing.Optional[str] = None,
value: typing.Optional[str] = None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional? Should be the same as track_event Option[Union[str,int,float]]

Comment thread flagsmith/analytics.py
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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread flagsmith/flagsmith.py
(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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."
            )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants