feat(flags): add official PostHog OpenFeature provider#695
feat(flags): add official PostHog OpenFeature provider#695gustavohstrassburger wants to merge 11 commits into
Conversation
|
Reviews (1): Last reviewed commit: "Add official PostHog OpenFeature provide..." | Re-trigger Greptile |
posthog-python Compliance ReportDate: 2026-06-30 14:31:37 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
|
|
Tested this locally end-to-end against a running PostHog instance (not just the mocked unit/e2e tests) — registered the provider with the OpenFeature SDK and evaluated a real boolean flag through Repro script I used: """
Example demonstrating how to use the PostHog OpenFeature provider.
This example shows:
1. Initializing the PostHog client
2. Registering the PostHogProvider with the OpenFeature SDK
3. Evaluating a boolean feature flag via the OpenFeature API
"""
import os
import posthog
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.contrib.provider.posthog import PostHogProvider
# Initialize the PostHog client
posthog_client = posthog.Posthog(
project_api_key=os.getenv("POSTHOG_API_KEY", "phc_PIPWvLdxL4N9RUvpyENExMOFEz2jXuk5ehmyXFG3A2k"),
host=os.getenv("POSTHOG_HOST", "http://localhost:8010"),
)
# Register the PostHog provider with OpenFeature
provider = PostHogProvider(posthog_client)
api.set_provider(provider)
# Get the OpenFeature client
client = api.get_client()
# The flag key to evaluate
flag_key = os.getenv("POSTHOG_FLAG_KEY", "my-flag")
# Evaluation context: targeting_key maps to PostHog's distinct_id
ctx = EvaluationContext(targeting_key="test-user")
# Evaluate the boolean flag
result = client.get_boolean_details(flag_key, False, ctx)
print(f"Flag: {flag_key}")
print(f"Value: {result.value}")
print(f"Reason: {result.reason}")
if result.error_message:
print(f"Error: {result.error_message}")
posthog_client.shutdown()(The |
|
Reviews (2): Last reviewed commit: "Address review: add missing tests and op..." | Re-trigger Greptile |
haacked
left a comment
There was a problem hiding this comment.
Clean, well-tested provider, nothing blocking. A few non-blocking suggestions inline, the correctness one (TYPE_MISMATCH on unmatched experiments) is the one worth a decision.
I'll leave approval to the client libraries team who may want a look.
| evaluation_context: Optional[EvaluationContext] = None, | ||
| ) -> FlagResolutionDetails[str]: | ||
| result = self._resolve(flag_key, evaluation_context) | ||
| if result.variant is None: |
There was a problem hiding this comment.
suggestion: Unenrolled users get TYPE_MISMATCH errors when they shouldn't. When a user matches no condition on a multivariate flag, PostHog returns enabled=False, variant=None, so the result.variant is None check here raises TypeMismatchError. The OpenFeature SDK then returns the correct default value but marks it with error_code=TYPE_MISMATCH and reason=ERROR, so normal non-enrollment gets flagged as type-mismatch errors.
You can tell the two cases apart by enabled: when enabled=True, variant=None, it's a genuine mismatch (boolean flag read as string), while enabled=False, variant=None means the flag is off or nobody matched. Return the default with a non-error reason in that case:
result = self._resolve(flag_key, evaluation_context)
if result.variant is None:
if not result.enabled:
return FlagResolutionDetails(
value=default_value,
reason=self._map_reason(result),
flag_metadata=self._flag_metadata(result),
)
raise TypeMismatchError(
f"Flag '{flag_key}' has no string variant (boolean flag)."
)The same variant is None branch is in _resolve_number; keep the non-numeric-variant case there raising TypeMismatchError. One tradeoff: a disabled boolean flag read as a string would reclassify from TYPE_MISMATCH to DISABLED, since enabled=False, variant=None can't tell the two apart.
There was a problem hiding this comment.
Fixed in 8f24613. enabled=False, variant=None now returns the default with a normal reason (DEFAULT/DISABLED) instead of raising TypeMismatchError; the genuine boolean-read-as-string case (enabled=True, variant=None) still raises. Applied the same in _resolve_number, and (per Manoel) in resolve_object_details. Added detail tests asserting error_code is None on the non-enrollment path.
| groups = attrs.get(GROUPS_KEY) or {} | ||
| group_properties = attrs.get(GROUP_PROPERTIES_KEY) or {} | ||
| person_properties = {k: v for k, v in attrs.items() if k not in _RESERVED_KEYS} | ||
| groups = groups if isinstance(groups, dict) else {} |
There was a problem hiding this comment.
suggestion: Nothing tests the non-dict fallback here. test_context_split only passes well-formed dicts, so when a caller sets groups to a string or list, the isinstance(groups, dict) guard that coerces it to {} is unverified. Drop that guard in a later refactor and a non-dict would flow straight into get_feature_flag_result with no test to catch it.
Add a case mirroring test_context_split that passes groups="acme" and asserts the forwarded kwargs["groups"] is None.
There was a problem hiding this comment.
Fixed in 8f24613. Added test_context_split_non_dict_groups_coerced_to_none, parametrized over "acme", a list, and an int, asserting the forwarded groups/group_properties kwargs are None.
| - name: Set up Python 3.12 | ||
| uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 | ||
| with: | ||
| python-version: 3.12 |
There was a problem hiding this comment.
suggestion: The package declares support for Python 3.10 through 3.14 (requires-python plus classifiers in pyproject.toml), but this job only tests on 3.12. mypy targets 3.10, so type errors are caught, but nothing exercises the floor or ceiling at runtime. A 3.10-only incompatibility or a 3.14 behavior change would ship unverified.
The tests and import-check jobs already matrix across all five versions.
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
...
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
with:
python-version: ${{ matrix.python-version }}There was a problem hiding this comment.
Agree the matrix should mirror the tests/import-check jobs (3.10–3.14). I do not edit .github/workflows/** in this automated review-fix turn (guardrail), so I have left this for a maintainer to apply directly.
| evaluation_context: Optional[EvaluationContext] = None, | ||
| ) -> FlagResolutionDetails[bool]: | ||
| result = self._resolve(flag_key, evaluation_context) | ||
| return FlagResolutionDetails( |
There was a problem hiding this comment.
nit: The four typed resolvers end with the same FlagResolutionDetails tail except for value. A small helper keeps the reason/flag_metadata wiring in one place:
def _details(self, value: _T, result: FeatureFlagResult) -> FlagResolutionDetails[_T]:
return FlagResolutionDetails(
value=value,
variant=result.variant,
reason=self._map_reason(result),
flag_metadata=self._flag_metadata(result),
)There was a problem hiding this comment.
Done in 8f24613 — extracted _details(value, result) and routed all four typed resolvers (and the default-return paths) through it.
| @@ -0,0 +1,103 @@ | |||
| # PostHog provider for OpenFeature (Python) | |||
There was a problem hiding this comment.
i'd move all install/snippets to https://posthog.com/docs/feature-flags and just point to the docs
single source of truth for docs, readme gets outdated
There was a problem hiding this comment.
Done in 8f24613 — slimmed the README to a minimal quickstart and pointed to https://posthog.com/docs/feature-flags as the single source of truth. Kept only the OpenFeature-specific registration snippet + context mapping (not yet on the docs site); those can move there once the provider is documented.
There was a problem hiding this comment.
Done earlier in 8f24613 — README slimmed to a minimal quickstart pointing at https://posthog.com/docs/feature-flags as the source of truth.
|
since its a new distribution, you'd need to adapt https://github.com/PostHog/posthog-python/blob/main/.github/workflows/release.yml to publish multiple packages |
|
|
||
| [project] | ||
| name = "openfeature-provider-posthog" | ||
| version = "0.1.0" |
There was a problem hiding this comment.
needs to set up sampo here, see other comment related to the release process
There was a problem hiding this comment.
The release wiring (Sampo for the new package + adapting release.yml to publish a second distribution) is a .github/workflows/** + release-config change I will not make in this automated turn. Flagged for a maintainer in a summary comment on the PR.
There was a problem hiding this comment.
Implemented in 5236b4b. Sampo already auto-discovers the package as pypi/openfeature-provider-posthog (it has its own pyproject with name+version), and release.yml now builds/publishes/tags it — gated so it only fires when a changeset bumps the provider, leaving posthog-only releases untouched. Added openfeature-provider/CHANGELOG.md for Sampo to maintain. First publish still needs a one-time PyPI trusted-publisher registration for the new project.
There was a problem hiding this comment.
Correction to my earlier reply — you were right, Sampo does not auto-discover this package. I checked sampo-core's pip adapter: it only discovers packages from the root pyproject's [tool.uv.workspace] members (plus the root). Fixed properly in b40251e by declaring a uv workspace (members = ["openfeature-provider"]) so sampo add -p pypi/openfeature-provider-posthog works and sampo release bumps the provider. Details in the PR comment.
|
Optional nice-to-have: add a CI smoke test that installs the built wheel into a fresh env and imports openfeature.contrib.provider.posthog, since namespace packaging issues are easy to miss with source-tree tests. |
|
The unmatched-user/default behavior should include object flags as well? |
|
left a bunch of comments, main blocker is the release process and a few other comments/suggestions, Phil added good points as well |
Adds `@posthog/openfeature-provider`, a JS port of the Python OpenFeature provider (PostHog/posthog-python#695). OpenFeature ships two SDKs with incompatible Provider contracts, so this package ships one provider for each, sharing a runtime-agnostic mapping core: - `/server` — `PostHogServerProvider` wraps `posthog-node` against `@openfeature/server-sdk` (async, multi-user, distinct id from `targetingKey`). - `/web` — `PostHogWebProvider` wraps `posthog-js` against `@openfeature/web-sdk` (synchronous, single-user, context reconciled via `onContextChange`). Both resolve through `getFeatureFlagResult`: boolean→enabled, string→variant, number→parsed variant, object→payload; missing flag→FLAG_NOT_FOUND, wrong type→TYPE_MISMATCH. The SDK/client deps are optional peers so a node-only or browser-only app never installs the other paradigm's stack. Generated-By: PostHog Code Task-Id: 46a3f8c9-fbcd-460e-9457-fca583955e5a
|
@marandaneto — implemented your remaining suggestions in
One manual prerequisite for the first provider release (can't be done in a PR): register a PyPI trusted publisher for Verified locally: 37 tests, ruff, mypy, |
|
Still not fixed / still worth commenting:
those are still open, the 2. is optional but i think its cool to test across all major versions like we do for the main package |
| # Publish the `openfeature-provider-posthog` distribution, but only when | ||
| # this release actually bumped it (a changeset targeting | ||
| # `pypi/openfeature-provider-posthog` was processed by Sampo, changing its | ||
| # version in openfeature-provider/pyproject.toml). Releases that only touch | ||
| # `posthog` skip these steps entirely, so the core release is unaffected. | ||
| # These run after the posthog publish/tag/release above so a provider issue | ||
| # can never block the main release. | ||
| # | ||
| # NOTE: the first provider release requires a PyPI trusted publisher to be | ||
| # registered for `openfeature-provider-posthog` (this workflow, environment | ||
| # "Release"), exactly like posthog/posthoganalytics. | ||
| - name: Detect openfeature-provider release | ||
| id: of-provider | ||
| if: steps.commit-release.outputs.commit-hash != '' | ||
| run: | | ||
| if git diff --name-only HEAD~1 HEAD -- openfeature-provider/pyproject.toml | grep -q .; then | ||
| version=$(python3 -c "import tomllib; print(tomllib.load(open('openfeature-provider/pyproject.toml','rb'))['project']['version'])") | ||
| echo "released=true" >> "$GITHUB_OUTPUT" | ||
| echo "version=$version" >> "$GITHUB_OUTPUT" | ||
| echo "openfeature-provider-posthog bumped to $version; will publish." | ||
| else | ||
| echo "released=false" >> "$GITHUB_OUTPUT" | ||
| echo "openfeature-provider-posthog not changed in this release; skipping." | ||
| fi | ||
|
|
||
| - name: Build openfeature-provider-posthog | ||
| if: steps.of-provider.outputs.released == 'true' | ||
| working-directory: openfeature-provider | ||
| run: uv build --package openfeature-provider-posthog --out-dir dist | ||
|
|
||
| - name: Publish openfeature-provider-posthog to PyPI | ||
| if: steps.of-provider.outputs.released == 'true' | ||
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 | ||
| with: | ||
| packages-dir: openfeature-provider/dist | ||
|
|
||
| - name: Tag openfeature-provider release | ||
| if: steps.of-provider.outputs.released == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ steps.releaser.outputs.token }} | ||
| PROVIDER_VERSION: ${{ steps.of-provider.outputs.version }} | ||
| COMMIT_HASH: ${{ steps.commit-release.outputs.commit-hash }} | ||
| run: | | ||
| gh api "repos/${{ github.repository }}/git/refs" \ | ||
| -f "ref=refs/tags/openfeature-provider-posthog-v${PROVIDER_VERSION}" \ | ||
| -f "sha=${COMMIT_HASH}" |
There was a problem hiding this comment.
i think a build matrix is better as suggested before
for example https://github.com/PostHog/posthog-ruby/blob/4579a7f2c1bb253bf67cb560f14a72190118ff4f/.github/workflows/release.yml#L222-L233
those are also N packages
| ## Development | ||
|
|
||
| ```bash | ||
| cd openfeature-provider | ||
| uv sync --extra dev | ||
| uv run pytest | ||
| uv run ruff check . | ||
| uv run mypy . | ||
| ``` |
There was a problem hiding this comment.
move to https://github.com/PostHog/posthog-python/blob/main/CONTRIBUTING.md or create its own CONTRIBUTING
| ## Install | ||
|
|
||
| ```bash | ||
| pip install openfeature-provider-posthog | ||
| ``` | ||
|
|
||
| ## Quickstart | ||
|
|
||
| ```python | ||
| import posthog | ||
| from openfeature import api | ||
| from openfeature.evaluation_context import EvaluationContext | ||
| from openfeature.contrib.provider.posthog import PostHogProvider | ||
|
|
||
| client = posthog.Posthog("phc_project_api_key", host="https://us.i.posthog.com") | ||
| api.set_provider(PostHogProvider(client, default_distinct_id="anonymous")) | ||
|
|
||
| of_client = api.get_client() | ||
| ctx = EvaluationContext(targeting_key="user-123", attributes={"plan": "pro"}) | ||
| enabled = of_client.get_boolean_value("my-flag", False, ctx) | ||
| ``` | ||
|
|
||
| The OpenFeature `targeting_key` maps to PostHog's `distinct_id`; other context | ||
| attributes become person properties, with reserved keys `groups` and | ||
| `group_properties` mapping to PostHog groups. You own the `Posthog` client | ||
| lifecycle — call `client.shutdown()` when done. |
There was a problem hiding this comment.
this gets outdated super fast, lets point to our docs only, you can link to the specific open feature section instead
Adds an official PostHog provider for the OpenFeature Python SDK, shipped as a separate distribution (`openfeature-provider-posthog`) under the OpenFeature namespace package `openfeature.contrib.provider.posthog`, living in this repo alongside the SDK. The provider wraps a configured `posthog.Posthog` client and resolves all five OpenFeature flag types via the modern, non-deprecated `get_feature_flag_result` (one call yields value + variant + payload + reason): - boolean -> `enabled` - string -> the multivariate variant key - int/float -> the variant parsed as a number - object -> the flag's JSON payload (full object/JSON support) Evaluation context maps `targeting_key` -> `distinct_id`, reserved attributes `groups`/`group_properties` -> PostHog groups, and all other attributes -> `person_properties`. A missing targeting key raises `TargetingKeyMissingError` unless `default_distinct_id` is set; type mismatches raise `TypeMismatchError` so the OpenFeature client returns the caller's default per spec. Repo wiring: - New `openfeature-provider` CI job (mirrors the django5 integration job): uv sync, pytest, ruff, mypy, build + twine check in the sub-project's own env. - `mypy.ini`: exclude `openfeature-provider/.*` from the root mypy pass (the namespace tree is type-checked in its own env where openfeature-sdk is present). - README link to the provider. The new top-level `openfeature/` tree is isolated from the `posthog` build (explicit packages list) and from the `posthog.*`-scoped public-API snapshot. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
- _map_reason: a disabled (enabled=False) result now maps to Reason.DEFAULT (the flag is active but no targeting condition matched), reserving Reason.DISABLED for when the reason text says the flag itself is off. PostHog returns None (-> FlagNotFoundError) for archived/missing flags. - initialize: log a WARNING (with exc_info) instead of silently swallowing a load_feature_flags() failure, so a misconfigured personal_api_key / host / permissions is visible while still falling back to remote evaluation. - tests: collapse the boolean reason-mapping and number-parsing cases into @pytest.mark.parametrize, and add coverage for the initialize logging paths. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Addresses review feedback from @haacked and @marandaneto. - Correctness (haacked + marandaneto): a user who matches no condition or a disabled flag (enabled=False, with no variant/object payload) is no longer reported as a TYPE_MISMATCH error. The string/integer/float/object resolvers now return the caller's default with a normal reason (DEFAULT/DISABLED) in that case, and only raise TypeMismatchError for a genuine mismatch (enabled=True but the value can't be coerced to the requested type). Applied consistently across string, number, and object. - Refactor (haacked): extract a shared `_details()` helper for the repeated reason/flag_metadata wiring across the typed resolvers. - Tests (haacked): add non-dict `groups`/`group_properties` coercion cases asserting they are forwarded as None; add detail tests asserting unmatched string/number/object resolution returns the default with no error_code. - Docs (marandaneto): slim the README to a minimal quickstart and point to https://posthog.com/docs/feature-flags as the single source of truth. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Implements the remaining review suggestions from @marandaneto. - Release (release.yml): publish `openfeature-provider-posthog` as a second distribution. Sampo already auto-discovers the package (it has its own pyproject), so the new steps only build/publish/tag the provider when this release actually bumped its version (a changeset targeting `pypi/openfeature-provider-posthog`). posthog-only releases skip them, and they run after the posthog publish/tag/release so they can never block the core release. Uses the same PyPI OIDC trusted-publishing action as posthog/posthoganalytics (a trusted publisher for the new project must be registered before the first provider release). - Sampo: add openfeature-provider/CHANGELOG.md for Sampo to maintain. - CI (ci.yml): add a clean-env smoke test that installs the built wheel and imports openfeature.contrib.provider.posthog, catching namespace-packaging regressions that source-tree tests miss. - pyproject: pin posthog to the tested major (>=7.0.0,<8.0.0); the local-branch build via [tool.uv.sources] was already in place. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Fixes the release/versioning gap @marandaneto found: `sampo add -p pypi/openfeature-provider-posthog` failed with "not found in the workspace". Sampo's PyPI adapter only discovers packages listed in the root pyproject's [tool.uv.workspace] members (plus the root) — there is no auto-discovery — so the provider was invisible to Sampo and the release detection never triggered. - Root pyproject: declare a uv workspace with `members = ["openfeature-provider"]`. Sampo now discovers `pypi/openfeature-provider-posthog`, so `sampo add` works and `sampo release` bumps openfeature-provider/pyproject.toml from a changeset. The root uv.lock change is purely additive (only the member + openfeature-sdk; no churn to existing posthog pins), and the main `posthog` build is unaffected (explicit packages list). - Provider pyproject: resolve posthog via `{ workspace = true }` instead of a path source; drop the now-redundant standalone uv.lock (the workspace shares the root lock). - CI: the provider job now matrixes Python 3.10–3.14 (matching the main package, per @marandaneto/@haacked) and uses workspace-aware commands (`--package openfeature-provider-posthog`, `uv build --out-dir dist`). - release.yml: build the provider with `--package ... --out-dir dist` so its artifacts stay isolated from the posthog dist. Verified locally: root posthog sync + import, provider sync/tests (37)/ruff/mypy/ build/twine/clean-env-import across the workspace, and the django5 integration project (not a member) still syncs standalone. Sampo itself couldn't be run here (no cargo), but the config matches exactly what its pip adapter parses. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Addresses further review from @marandaneto. - release.yml: restructure into the canonical PostHog multi-package shape (cf. posthog-ruby). A single approval-gated `version-bump` job (environment "Release") bumps versions, commits, and regenerates references; a separate `publish` job with no environment runs a `package` matrix (fail-fast, max-parallel 1) over posthog, posthoganalytics, and openfeature-provider-posthog. Each entry detects whether its version changed in the release commit and only then builds/publishes/tags. Keeping the approval on version-bump (not the matrix) preserves a single approval per release. NOTE: the no-environment publish job means each package's PyPI trusted publisher must point at the `publish` job (not an environment) — a one-time PyPI-side config matraneto/maintainers control. - README: drop the install/quickstart snippets that drift; point to https://posthog.com/docs/feature-flags (OpenFeature section) as the single source of truth. - Move the development instructions out of the README into a dedicated openfeature-provider/CONTRIBUTING.md with the workspace-aware commands. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
47016c2 to
9dfca3c
Compare
Per review: don't keep a separate provider README that drifts — point to the PostHog docs (single source of truth) instead. Companion docs page is added in PostHog/posthog.com#18006. - Delete openfeature-provider/README.md. - pyproject: drop the `readme` field (it referenced the deleted file) and add a Documentation URL so the PyPI page links straight to the docs. - Root README: the OpenFeature entry now links to https://posthog.com/docs/feature-flags/installation/openfeature instead of the in-repo provider README. uv build + twine check still pass (only a non-fatal missing-long_description warning, expected without a README). Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Follow-up to dropping the provider README (these were left unstaged in the prior commit, which deleted the README but not its references): - openfeature-provider/pyproject.toml: drop the `readme = "README.md"` field (it pointed at the now-deleted file and would break the build) and add a Documentation URL to [project.urls] so PyPI links straight to the docs. - README.md: the OpenFeature entry links to https://posthog.com/docs/feature-flags/installation/openfeature. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
Review feedback — all addressed ✅Thanks @marandaneto and @haacked (and Greptile) for the thorough review. Summary of what changed and how. Greptile
@haacked
@marandaneto
Status
One follow-up for maintainers (not blocking this PR)The |
| version_file: pyproject.toml | ||
| build: uv run make build_release | ||
| packages_dir: dist | ||
| tag_prefix: "v" |
There was a problem hiding this comment.
| tag_prefix: "v" | |
| tag_prefix: "posthog-v" |
There was a problem hiding this comment.
i think it makes sense like this now
| build: uv run make build_release | ||
| packages_dir: dist | ||
| tag_prefix: "v" | ||
| changelog: CHANGELOG.md |
There was a problem hiding this comment.
i think we should move this to posthog/CHANGELOG.md now
and the root changelog points to the inner changelogs
similar to https://github.com/PostHog/posthog-ruby/blob/main/CHANGELOG.md
| - name: Create ${{ matrix.package.name }} GitHub Release | ||
| if: steps.detect.outputs.has-new-version == 'true' && matrix.package.github_release | ||
| env: | ||
| GH_TOKEN: ${{ steps.releaser.outputs.token }} | ||
| TAG: ${{ matrix.package.tag_prefix }}${{ steps.detect.outputs.version }} | ||
| CHANGELOG_FILE: ${{ matrix.package.changelog }} | ||
| run: | | ||
| CHANGELOG_ENTRY=$(awk -v defText="see ${CHANGELOG_FILE}" '/^## /{if (flag) exit; flag=1; next} flag; END{if (!flag) print defText}' "${CHANGELOG_FILE}" | sed '/[^[:space:]]/,$!d' | tac | sed '/[^[:space:]]/,$!d' | tac) | ||
| gh release create "$TAG" --notes "$CHANGELOG_ENTRY" |
| @@ -0,0 +1,5 @@ | |||
| # Changelog | |||
There was a problem hiding this comment.
| # Changelog | |
| # openfeature-provider-posthog |
make sure the https://github.com/PostHog/posthog-python/pull/695/changes#r3498456802 works here since its fragile
marandaneto
left a comment
There was a problem hiding this comment.
left a few comments still, but approving to unblock
|
probably requires setting up pypi for the new package (trusted publisher) |
Addresses @marandaneto's 2026-06-30 review comments: - openfeature-provider/CHANGELOG.md: title is now `# openfeature-provider-posthog` (was `# Changelog`), so Sampo's `## <version>` sections sit under a package-named heading. - release.yml: make the GitHub release-notes extraction robust (the previous awk | sed | tac | sed | tac pipeline was fragile). It now falls back to a "See <CHANGELOG>" pointer when the changelog is missing or has no section yet (e.g. a package's first release), so a release is never blocked on notes. Verified against the provider changelog (no section -> fallback), the root posthog changelog (extracts the latest section), and a missing file. Not changed, with rationale: - posthog tag prefix kept as `v` (not `posthog-v`): the Sampo config sets `short_tags = "posthog"` which intentionally tags the posthog package as `v{version}`, matching existing `vX.Y.Z` tags. Switching to `posthog-v` would diverge from both. - Did not move the root CHANGELOG.md to posthog/CHANGELOG.md: Sampo writes a package's changelog in its manifest directory, and the posthog package's manifest is the repo root, so root CHANGELOG.md *is* the posthog changelog. The posthog-ruby layout works because its gem lives in a posthog-ruby/ subdir; replicating it here would require relocating the entire posthog package. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
…age repos Per @marandaneto's review and matching posthog-ruby's multi-package convention. posthog-ruby tags every package with its name prefix once it went multi-package (`posthog-ruby-v3.15.1`, `posthog-rails-v3.15.0`) and carries no `short_tags` in its Sampo config. This mirrors that: - release.yml: posthog package now tags as `posthog-v{version}` (was `v{version}`), so all published packages are consistently name-prefixed (`posthog-v…`, `openfeature-provider-posthog-v…`). posthoganalytics stays untagged (it is a same-version mirror of posthog). - .sampo/config.toml: drop `short_tags = "posthog"`. Tags are created manually in release.yml, so this was inert, but it implied `v{version}` for posthog and was inconsistent with the new prefix; removing it aligns Sampo's default tag format with the workflow and with posthog-ruby. Generated-By: PostHog Code Task-Id: 392fb0da-49bb-4c96-96c7-1b39b0348d32
What
Adds an official PostHog provider for the OpenFeature Python SDK, built in this repo and shipped as a separate distribution (
openfeature-provider-posthog) under the OpenFeature namespace packageopenfeature.contrib.provider.posthog.It wraps a configured
posthog.Posthogclient and resolves all five OpenFeature flag types via the modern, non-deprecatedget_feature_flag_result(one call → value + variant + payload + reason):get_boolean_valueenabledget_string_valueget_integer_value/get_float_valueget_object_valueEvaluation context maps
targeting_key→distinct_id, reserved attributesgroups/group_properties→ PostHog groups, and every other attribute →person_properties.Behavior decisions (recommended defaults, easy to change)
targeting_key→TargetingKeyMissingError(OpenFeature-idiomatic; SDK returns the caller's default). Opt into anonymous eval viadefault_distinct_id="anonymous".get_string_valueon a boolean flag, non-numeric variant for int, non-object payload) →TypeMismatchError→ default returned, per spec.send_feature_flag_events=Trueby default (keeps$feature_flag_called/ experiments working); toggleable.posthog>=6.0.0floor.Layout
Repo wiring
openfeature-providerjob mirroringdjango5-integration—uv sync, pytest, ruff format/check, mypy, thenuv build+twine checkin the sub-project's own env.mypy.ini: excludesopenfeature-provider/.*from the root mypy pass (the namespace tree is type-checked in its own env whereopenfeature-sdkis installed).The new top-level
openfeature/tree is isolated from theposthogwheel (explicit packages list) and from theposthog.*-scoped public-API snapshot — verified.Verification
uv build+twine checkpass; wheel ships only the leaf package +py.typed, no clobberingopenfeature/__init__.py, no testsopenfeature/entries;public_api_checksnapshot up to dateFollow-ups (not in this PR)
openfeature-provider-posthogbefore first publish. A third-partyposthog-openfeature-provider-python(MPL-2.0, deprecated API, no object support) already exists on PyPI; this one is official, MIT, and uses the modern API with full object/JSON support.posthog/posthoganalytics; this dist needs its own version/tag + PyPI publish job. Noposthoganalyticstwin needed.Docs
Documentation (install + usage snippets) lives in the PostHog docs, not in the package README (kept minimal so it doesn't drift). Companion docs PR: PostHog/posthog.com#18006 — adds
/docs/feature-flags/installation/openfeature.🤖 Generated with Claude Code