From 6cc60bc6df1b46026c733d67c45a8d3f58b6d50e Mon Sep 17 00:00:00 2001 From: Vihang Shah Date: Sat, 27 Jun 2026 12:18:56 -0400 Subject: [PATCH 1/3] feat: support flag keys in remote evaluation fetch options --- src/amplitude_experiment/remote/client.py | 5 +++++ src/amplitude_experiment/remote/fetch_options.py | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/amplitude_experiment/remote/client.py b/src/amplitude_experiment/remote/client.py index 52baece..2d6568e 100644 --- a/src/amplitude_experiment/remote/client.py +++ b/src/amplitude_experiment/remote/client.py @@ -1,3 +1,4 @@ +import base64 import json import threading import time @@ -143,6 +144,10 @@ def __do_fetch(self, user, fetch_options: FetchOptions = None): headers['X-Amp-Exp-Track'] = "track" if fetch_options.tracksAssignment else "no-track" if fetch_options and fetch_options.tracksExposure is not None: headers['X-Amp-Exp-Exposure-Track'] = "track" if fetch_options.tracksExposure else "no-track" + if fetch_options and fetch_options.flagKeys: + headers['X-Amp-Exp-Flag-Keys'] = base64.urlsafe_b64encode( + json.dumps(fetch_options.flagKeys).encode("utf-8") + ).rstrip(b"=").decode("utf-8") conn = self._connection_pool.acquire() body = user_context.to_json().encode('utf8') diff --git a/src/amplitude_experiment/remote/fetch_options.py b/src/amplitude_experiment/remote/fetch_options.py index 8538888..99a3402 100644 --- a/src/amplitude_experiment/remote/fetch_options.py +++ b/src/amplitude_experiment/remote/fetch_options.py @@ -1,14 +1,24 @@ -from typing import Optional +from typing import List, Optional + + class FetchOptions: - def __init__(self, tracksAssignment: Optional[bool] = None, tracksExposure: Optional[bool] = None): + def __init__( + self, + tracksAssignment: Optional[bool] = None, + tracksExposure: Optional[bool] = None, + flagKeys: Optional[List[str]] = None, + ): """ Fetch Options Parameters: tracksAssignment (Optional[bool]): Whether to track the assignment. The default None uses the server's default behavior (track the assignment event). tracksExposure (Optional[bool]): Whether to track the exposure. The default None uses the server's default behavior (don't track the exposure event). + flagKeys (Optional[List[str]]): Specific flag keys to evaluate and set variants for. """ self.tracksAssignment = tracksAssignment self.tracksExposure = tracksExposure + self.flagKeys = flagKeys def __str__(self): - return f"FetchOptions(tracksAssignment={self.tracksAssignment}, tracksExposure={self.tracksExposure})" + return (f"FetchOptions(tracksAssignment={self.tracksAssignment}, " + f"tracksExposure={self.tracksExposure}, flagKeys={self.flagKeys})") From 1eed96718197671fd21fc4f5c404596328aaa2d8 Mon Sep 17 00:00:00 2001 From: Vihang Shah Date: Sat, 27 Jun 2026 12:21:49 -0400 Subject: [PATCH 2/3] test: add flag keys assertion to fetch options test --- tests/remote/client_test.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/remote/client_test.py b/tests/remote/client_test.py index 43fcf24..a4b1ab8 100644 --- a/tests/remote/client_test.py +++ b/tests/remote/client_test.py @@ -81,6 +81,25 @@ def test_fetch_with_fetch_options(self): 'X-Amp-Exp-Exposure-Track': 'no-track' }) + mock_conn.request.reset_mock() + + variants = client.fetch_v2(user, FetchOptions(flagKeys=['flag-a', 'flag-b'])) + self.assertIn('sdk-ci-test', variants) + mock_conn.request.assert_called_once_with('POST', '/sdk/v2/vardata?v=0', mock.ANY, { + 'Authorization': f"Api-Key {API_KEY}", + 'Content-Type': 'application/json;charset=utf-8', + 'X-Amp-Exp-Flag-Keys': 'WyJmbGFnLWEiLCAiZmxhZy1iIl0' # gitleaks:allow + }) + + mock_conn.request.reset_mock() + + variants = client.fetch_v2(user, FetchOptions(flagKeys=[])) + self.assertIn('sdk-ci-test', variants) + mock_conn.request.assert_called_once_with('POST', '/sdk/v2/vardata?v=0', mock.ANY, { + 'Authorization': f"Api-Key {API_KEY}", + 'Content-Type': 'application/json;charset=utf-8' + }) + @parameterized.expand([ (300, "Fetch Exception 300", True), (400, "Fetch Exception 400", False), From 2654122b88bd9c1e9e70fdc4bbc4beb4844e4c25 Mon Sep 17 00:00:00 2001 From: Vihang Shah Date: Sat, 27 Jun 2026 12:38:32 -0400 Subject: [PATCH 3/3] patch: match the core experiment sdk encoding strategy for X-Amp-Exp-Flag-Keys header --- src/amplitude_experiment/remote/client.py | 2 +- tests/remote/client_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/amplitude_experiment/remote/client.py b/src/amplitude_experiment/remote/client.py index 2d6568e..8db2653 100644 --- a/src/amplitude_experiment/remote/client.py +++ b/src/amplitude_experiment/remote/client.py @@ -146,7 +146,7 @@ def __do_fetch(self, user, fetch_options: FetchOptions = None): headers['X-Amp-Exp-Exposure-Track'] = "track" if fetch_options.tracksExposure else "no-track" if fetch_options and fetch_options.flagKeys: headers['X-Amp-Exp-Flag-Keys'] = base64.urlsafe_b64encode( - json.dumps(fetch_options.flagKeys).encode("utf-8") + json.dumps(fetch_options.flagKeys, separators=(",", ":")).encode("utf-8") ).rstrip(b"=").decode("utf-8") conn = self._connection_pool.acquire() diff --git a/tests/remote/client_test.py b/tests/remote/client_test.py index a4b1ab8..45c45b8 100644 --- a/tests/remote/client_test.py +++ b/tests/remote/client_test.py @@ -88,7 +88,7 @@ def test_fetch_with_fetch_options(self): mock_conn.request.assert_called_once_with('POST', '/sdk/v2/vardata?v=0', mock.ANY, { 'Authorization': f"Api-Key {API_KEY}", 'Content-Type': 'application/json;charset=utf-8', - 'X-Amp-Exp-Flag-Keys': 'WyJmbGFnLWEiLCAiZmxhZy1iIl0' # gitleaks:allow + 'X-Amp-Exp-Flag-Keys': 'WyJmbGFnLWEiLCJmbGFnLWIiXQ' }) mock_conn.request.reset_mock()