Skip to content

Commit 51eeb09

Browse files
[FSSDK-11458] Python - Add SDK Multi-Region Support for Data Hosting (#459)
* [FSSDK-11458] Python - Add SDK Multi-Region Support for Data Hosting * Fix lint issues * Fix lint issue * Fix errors * Add region on base test * Update Region as enum * Delete blank lines * Fix lint issue * Fix lint issue * Fix test errors * Add enum * Fix region value * Fix type issue * Correct the event url * Add region in params * Fix region param * Add region to create * Fix test cases * Fix test cases * Fix lint * Add region * Fix test cases * Fix tests * Cast to string * Fix tests * Fix order * Remove unnecessary region implementation * Fix lint issues * Fix test cases * Fix error * Implemented reviews and added new tests * Fix test cases * Implement comments * Fix lint issue
1 parent 1e8b9ba commit 51eeb09

10 files changed

+479
-49
lines changed

optimizely/event/event_factory.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ class EventFactory:
4242
to record the events via the Optimizely Events API ("https://developers.optimizely.com/x/events/api/index.html")
4343
"""
4444

45-
EVENT_ENDPOINT: Final = 'https://logx.optimizely.com/v1/events'
45+
EVENT_ENDPOINTS: Final = {
46+
'US': 'https://logx.optimizely.com/v1/events',
47+
'EU': 'https://eu.logx.optimizely.com/v1/events'
48+
}
4649
HTTP_VERB: Final = 'POST'
4750
HTTP_HEADERS: Final = {'Content-Type': 'application/json'}
4851
ACTIVATE_EVENT_KEY: Final = 'campaign_activated'
@@ -97,7 +100,11 @@ def create_log_event(
97100

98101
event_params = event_batch.get_event_params()
99102

100-
return log_event.LogEvent(cls.EVENT_ENDPOINT, event_params, cls.HTTP_VERB, cls.HTTP_HEADERS)
103+
region = user_context.region or 'US' # Default to 'US' if None
104+
region_key = str(region).upper()
105+
endpoint = cls.EVENT_ENDPOINTS.get(region_key, cls.EVENT_ENDPOINTS['US'])
106+
107+
return log_event.LogEvent(endpoint, event_params, cls.HTTP_VERB, cls.HTTP_HEADERS)
101108

102109
@classmethod
103110
def _create_visitor(cls, event: Optional[user_event.UserEvent], logger: Logger) -> Optional[payload.Visitor]:

optimizely/event/user_event.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ def __init__(
9999
class EventContext:
100100
""" Class respresenting User Event Context. """
101101

102-
def __init__(self, account_id: str, project_id: str, revision: str, anonymize_ip: bool):
102+
def __init__(self, account_id: str, project_id: str, revision: str, anonymize_ip: bool, region: str):
103103
self.account_id = account_id
104104
self.project_id = project_id
105105
self.revision = revision
106106
self.client_name = CLIENT_NAME
107107
self.client_version = version.__version__
108108
self.anonymize_ip = anonymize_ip
109+
self.region = region or 'US'

optimizely/event/user_event_factory.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ def create_impression_event(
7777
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
7878

7979
event_context = user_event.EventContext(
80-
project_config.account_id, project_config.project_id, project_config.revision, project_config.anonymize_ip,
80+
project_config.account_id,
81+
project_config.project_id,
82+
project_config.revision,
83+
project_config.anonymize_ip,
84+
project_config.region
8185
)
8286

8387
return user_event.ImpressionEvent(
@@ -117,7 +121,11 @@ def create_conversion_event(
117121
"""
118122

119123
event_context = user_event.EventContext(
120-
project_config.account_id, project_config.project_id, project_config.revision, project_config.anonymize_ip,
124+
project_config.account_id,
125+
project_config.project_id,
126+
project_config.revision,
127+
project_config.anonymize_ip,
128+
project_config.region
121129
)
122130

123131
return user_event.ConversionEvent(

optimizely/event_builder.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ class EventBuilder:
5454
""" Class which encapsulates methods to build events for tracking
5555
impressions and conversions using the new V3 event API (batch). """
5656

57-
EVENTS_URL: Final = 'https://logx.optimizely.com/v1/events'
57+
EVENTS_URLS: Final = {
58+
'US': 'https://logx.optimizely.com/v1/events',
59+
'EU': 'https://eu.logx.optimizely.com/v1/events'
60+
}
5861
HTTP_VERB: Final = 'POST'
5962
HTTP_HEADERS: Final = {'Content-Type': 'application/json'}
6063

@@ -266,7 +269,11 @@ def create_impression_event(
266269

267270
params[self.EventParams.USERS][0][self.EventParams.SNAPSHOTS].append(impression_params)
268271

269-
return Event(self.EVENTS_URL, params, http_verb=self.HTTP_VERB, headers=self.HTTP_HEADERS)
272+
region = project_config.region or 'US'
273+
region_key = str(region).upper()
274+
events_url = self.EVENTS_URLS.get(region_key, self.EVENTS_URLS['US'])
275+
276+
return Event(events_url, params, http_verb=self.HTTP_VERB, headers=self.HTTP_HEADERS)
270277

271278
def create_conversion_event(
272279
self, project_config: ProjectConfig, event_key: str,
@@ -289,4 +296,9 @@ def create_conversion_event(
289296
conversion_params = self._get_required_params_for_conversion(project_config, event_key, event_tags)
290297

291298
params[self.EventParams.USERS][0][self.EventParams.SNAPSHOTS].append(conversion_params)
292-
return Event(self.EVENTS_URL, params, http_verb=self.HTTP_VERB, headers=self.HTTP_HEADERS)
299+
300+
region = project_config.region or 'US'
301+
region_key = str(region).upper()
302+
events_url = self.EVENTS_URLS.get(region_key, self.EVENTS_URLS['US'])
303+
304+
return Event(events_url, params, http_verb=self.HTTP_VERB, headers=self.HTTP_HEADERS)

optimizely/project_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def __init__(self, datafile: str | bytes, logger: Logger, error_handler: Any):
8585
self.host_for_odp: Optional[str] = None
8686
self.all_segments: list[str] = []
8787

88+
region_value = config.get('region')
89+
self.region: str = region_value or 'US'
90+
8891
# Utility maps for quick lookup
8992
self.group_id_map: dict[str, entities.Group] = self._generate_key_map(self.groups, 'id', entities.Group)
9093
self.experiment_id_map: dict[str, entities.Experiment] = self._generate_key_map(

tests/test_config.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ def test_init(self):
154154
self.assertEqual(expected_variation_key_map, self.project_config.variation_key_map)
155155
self.assertEqual(expected_variation_id_map, self.project_config.variation_id_map)
156156

157+
def test_region_when_no_region(self):
158+
""" Test that region defaults to 'US' when not specified in the config. """
159+
config_dict = copy.deepcopy(self.config_dict_with_multiple_experiments)
160+
opt_obj = optimizely.Optimizely(json.dumps(config_dict))
161+
project_config = opt_obj.config_manager.get_config()
162+
self.assertEqual(project_config.region, 'US')
163+
164+
def test_region_when_specified_in_datafile(self):
165+
""" Test that region is set to 'US' when specified in the config. """
166+
config_dict_us = copy.deepcopy(self.config_dict_with_multiple_experiments)
167+
config_dict_us['region'] = 'US'
168+
opt_obj_us = optimizely.Optimizely(json.dumps(config_dict_us))
169+
project_config_us = opt_obj_us.config_manager.get_config()
170+
self.assertEqual(project_config_us.region, 'US')
171+
172+
""" Test that region is set to 'EU' when specified in the config. """
173+
config_dict_eu = copy.deepcopy(self.config_dict_with_multiple_experiments)
174+
config_dict_eu['region'] = 'EU'
175+
opt_obj_eu = optimizely.Optimizely(json.dumps(config_dict_eu))
176+
project_config_eu = opt_obj_eu.config_manager.get_config()
177+
self.assertEqual(project_config_eu.region, 'EU')
178+
157179
def test_cmab_field_population(self):
158180
""" Test that the cmab field is populated correctly in experiments."""
159181

0 commit comments

Comments
 (0)