Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk_compliance_adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The adapter implements the standard SDK adapter interface defined in the [test h
- `POST /flush` - Flush pending events
- `GET /state` - Return internal state
- `POST /get_feature_flag` - Evaluate a flag locally or remotely
- `POST /get_feature_flag_payload` - Read a flag payload remotely with the legacy payload accessor
- `POST /reload_feature_flag_definitions` - Fresh, bounded definitions readiness barrier
- `POST /reset` - Reset SDK state

Expand Down
28 changes: 28 additions & 0 deletions sdk_compliance_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ def health():
capabilities.append("feature_flags_local_evaluation_v1")
# Remote /flags requests send ?v=4 and read the v4 flag record.
capabilities.append("flags_response_v4")
# The legacy payload accessor is exposed on /get_feature_flag_payload.
capabilities.append("feature_flag_payloads")
return jsonify(
{
"sdk_name": "posthog-python",
Expand Down Expand Up @@ -710,6 +712,32 @@ def get_feature_flag():
return jsonify({"error": str(e)}), 500


@app.route("/get_feature_flag_payload", methods=["POST"])
def get_feature_flag_payload():
"""Read a flag's payload with the legacy payload accessor (remote evaluation)."""
try:
if not state.client:
return jsonify({"error": "SDK not initialized"}), 400
data = request.json or {}
if not data.get("key") or not data.get("distinct_id"):
return jsonify({"error": "key and distinct_id are required"}), 400
client = state.remote_client or state.client
payload = client.get_feature_flag_payload(
data["key"],
data["distinct_id"],
person_properties=data.get("person_properties"),
groups=data.get("groups"),
group_properties=data.get("group_properties"),
disable_geoip=data.get("disable_geoip"),
only_evaluate_locally=False,

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.

P2 · Honor local-only payload evaluation requests

A request with only_evaluate_locally: true still performs remote evaluation because this call hardcodes False and selects the definitions-free client. The harness contract gives this endpoint the same fields as /get_feature_flag, whose local-only option forbids remote fallback. Honor that option using state.client and reject conflicting evaluation modes.

)
client.flush()
return jsonify({"success": True, "value": payload})
except Exception as e:
logger.exception("Error reading feature flag payload")
return jsonify({"error": str(e)}), 500


@app.route("/reset", methods=["POST"])
def reset():
"""Reset SDK state"""
Expand Down
25 changes: 25 additions & 0 deletions sdk_compliance_adapter/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test_health_opts_into_local_evaluation_without_losing_capture(
capabilities = adapter.app.test_client().get("/health").json["capabilities"]
assert "feature_flags_local_evaluation_v1" in capabilities
assert "flags_response_v4" in capabilities
assert "feature_flag_payloads" in capabilities
assert capability in capabilities
assert "capture_ai_v0" in capabilities

Expand Down Expand Up @@ -181,6 +182,30 @@ def test_force_remote_bypasses_loaded_local_definitions(adapter, monkeypatch):
remote.assert_called_once()


def test_payload_route_reads_remotely_and_returns_the_parsed_payload(
adapter, monkeypatch
):
client = initialize(adapter)
remote = Mock(
return_value=posthog.client.normalize_flags_response(
{
"featureFlags": {"flag": True},
"featureFlagPayloads": {"flag": '{"columns": 2}'},
}
)
)
monkeypatch.setattr(adapter.state.remote_client, "_get_flags_decision", remote)
response = client.post(
"/get_feature_flag_payload", json={"key": "flag", "distinct_id": "user"}
)
assert response.json == {"success": True, "value": {"columns": 2}}
remote.assert_called_once()
assert (
client.post("/get_feature_flag_payload", json={"key": "flag"}).status_code
== 400
)


def test_rejects_conflicting_evaluation_modes(adapter):
client = initialize(adapter)
response = client.post(
Expand Down
Loading