feat(capture): add capture_mode config scaffolding (capture v1, 1/6)#701
feat(capture): add capture_mode config scaffolding (capture v1, 1/6)#701eli-r-ph wants to merge 1 commit into
Conversation
Introduce a CaptureMode enum (V0 legacy /batch/, V1 /i/v1/analytics/events) and resolve_capture_mode() with precedence kwarg > POSTHOG_CAPTURE_MODE env > V0. Plumb capture_mode through Client and Consumer (including fork-reinit and the module-level default client). The mode is resolved and stored but inert in this change: V0 still runs everywhere, so behavior is unchanged. First of a stacked series adding Capture V1 support.
|
Reviews (1): Last reviewed commit: "feat(capture): add capture_mode config s..." | Re-trigger Greptile |
| @parameterized.expand( | ||
| [ | ||
| ("enum_v0", CaptureMode.V0, CaptureMode.V0), | ||
| ("enum_v1", CaptureMode.V1, CaptureMode.V1), | ||
| ("str_v0", "v0", CaptureMode.V0), | ||
| ("str_v1", "v1", CaptureMode.V1), | ||
| ("str_legacy_alias", "legacy", CaptureMode.V0), | ||
| ("str_analytics_v1_alias", "analytics_v1", CaptureMode.V1), | ||
| ("str_upper_and_padded", " V1 ", CaptureMode.V1), | ||
| ] | ||
| ) | ||
| def test_explicit_kwarg_takes_precedence_and_coerces( | ||
| self, _name, kwarg, expected | ||
| ) -> None: | ||
| # Env is set to the opposite mode to prove the kwarg wins. | ||
| with mock.patch.dict(os.environ, {CAPTURE_MODE_ENV_VAR: "v1"}): | ||
| self.assertIs(resolve_capture_mode(kwarg), expected) |
There was a problem hiding this comment.
In
test_explicit_kwarg_takes_precedence_and_coerces, the env is set to "v1" for every sub-case. For the four cases where the kwarg also resolves to V1 (enum_v1, str_v1, str_analytics_v1_alias, str_upper_and_padded), the result is V1 regardless of whether the kwarg or the env won — so these cases never actually exercise the precedence claim. Setting the env to "v0" for those cases would give the test its full demonstrative value.
| @parameterized.expand( | |
| [ | |
| ("enum_v0", CaptureMode.V0, CaptureMode.V0), | |
| ("enum_v1", CaptureMode.V1, CaptureMode.V1), | |
| ("str_v0", "v0", CaptureMode.V0), | |
| ("str_v1", "v1", CaptureMode.V1), | |
| ("str_legacy_alias", "legacy", CaptureMode.V0), | |
| ("str_analytics_v1_alias", "analytics_v1", CaptureMode.V1), | |
| ("str_upper_and_padded", " V1 ", CaptureMode.V1), | |
| ] | |
| ) | |
| def test_explicit_kwarg_takes_precedence_and_coerces( | |
| self, _name, kwarg, expected | |
| ) -> None: | |
| # Env is set to the opposite mode to prove the kwarg wins. | |
| with mock.patch.dict(os.environ, {CAPTURE_MODE_ENV_VAR: "v1"}): | |
| self.assertIs(resolve_capture_mode(kwarg), expected) | |
| @parameterized.expand( | |
| [ | |
| # env=v1, kwarg resolves to V0: kwarg must win | |
| ("enum_v0", CaptureMode.V0, CaptureMode.V0, "v1"), | |
| # env=v0, kwarg resolves to V1: kwarg must win | |
| ("enum_v1", CaptureMode.V1, CaptureMode.V1, "v0"), | |
| ("str_v0", "v0", CaptureMode.V0, "v1"), | |
| ("str_v1", "v1", CaptureMode.V1, "v0"), | |
| ("str_legacy_alias", "legacy", CaptureMode.V0, "v1"), | |
| ("str_analytics_v1_alias", "analytics_v1", CaptureMode.V1, "v0"), | |
| ("str_upper_and_padded", " V1 ", CaptureMode.V1, "v0"), | |
| ] | |
| ) | |
| def test_explicit_kwarg_takes_precedence_and_coerces( | |
| self, _name, kwarg, expected, env_value | |
| ) -> None: | |
| # Env is set to the opposite mode to prove the kwarg wins. | |
| with mock.patch.dict(os.environ, {CAPTURE_MODE_ENV_VAR: env_value}): | |
| self.assertIs(resolve_capture_mode(kwarg), expected) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
posthog-python Compliance ReportDate: 2026-06-27 19:33:51 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
💡 Motivation and Context
First PR in a stacked series adding Capture V1 (
POST /i/v1/analytics/events) support to the SDK, at parity with posthog-go'sCaptureModeand posthog-rs'scapture-v1.This PR is pure, inert scaffolding: it adds the capture-mode selector and threads it everywhere it will be needed, but changes no runtime behavior — V0 (legacy
/batch/) still runs on every path. Later PRs add the v1 serializer, transport, wiring, harness suite, and docs.CaptureModeenum:V0(legacy/batch/, the default) andV1(/i/v1/analytics/events).resolve_capture_mode()with precedence explicit kwarg >POSTHOG_CAPTURE_MODEenv >V0. Env acceptsv0/legacyandv1/analytics_v1(mirrors posthog-go's vocabulary); an unrecognized env value warns and defaults toV0so a typo never silently flips the wire protocol. An invalid explicit kwarg raisesValueError(explicit misconfig should be loud).Client.__init__(resolved + stored asself.capture_mode), bothConsumer(...)constructions (initial + fork-reinit), and the module-level default client.Consumerstores it but does not yet act on it.💚 How did you test it?
posthog/test/test_capture_mode.py(25 cases): resolution precedence, env aliases/case/whitespace, blank-env default, unrecognized-env warn+default, invalid-kwarg raises, and plumbing toClient.capture_mode+ per-Consumerpropagation (incl. multi-thread).test_consumer,test_client,test_module,test_client_fork,test_dedicated_ai_endpoint+ the new file — 213 passed.ruff format/ruff checkclean;mypyclean on new modules; regeneratedreferences/public_api_snapshot.txt.📝 Checklist
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored with Cursor (Claude Opus 4.8) following a pre-agreed, hardened implementation plan. Design decisions: enum members are Pythonic
V0/V1(not posthog-go'sLegacy/AnalyticsV1) but the env var accepts both spellings for cross-SDK consistency; the env typo path defaults defensively while an explicit bad kwarg fails fast. Mode is fully inert here by design to keep this PR a no-op for existing users and easy to review. Changeset (sampo add) and docs are intentionally deferred to the final PR so the feature releases as one entry.