optimizely-sdk 5.3.0__tar.gz → 5.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/CHANGELOG.md +47 -0
- {optimizely_sdk-5.3.0/optimizely_sdk.egg-info → optimizely_sdk-5.5.0}/PKG-INFO +55 -8
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/bucketer.py +49 -65
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/config_manager.py +6 -9
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision_service.py +142 -124
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/entities.py +73 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/event_factory.py +5 -1
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/user_event.py +4 -3
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/user_event_factory.py +6 -5
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event_dispatcher.py +1 -1
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/enums.py +8 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/types.py +4 -2
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/validator.py +2 -2
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event_manager.py +8 -1
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely.py +12 -6
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/project_config.py +125 -81
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/version.py +1 -1
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0/optimizely_sdk.egg-info}/PKG-INFO +55 -8
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/requires.txt +6 -6
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/test.txt +1 -1
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/LICENSE +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/MANIFEST.in +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/README.md +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/cmab_client.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/cmab_service.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decide_option.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decision.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decision_message.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/error_handler.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/event_processor.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/log_event.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/payload.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event_builder.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/exceptions.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/audience.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/condition.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/condition_tree_evaluator.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/constants.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/event_tag_utils.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/experiment.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/sdk_settings.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/lib/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/lib/pymmh3.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/logger.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/notification_center.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/notification_center_registry.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/__init__.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/lru_cache.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_config.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event_api_manager.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_manager.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_segment_api_manager.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_segment_manager.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/optimizely_odp_option.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_config.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_factory.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_user_context.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/py.typed +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/user_profile.py +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/SOURCES.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/dependency_links.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/top_level.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/core.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/docs.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/typing.txt +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/setup.cfg +0 -0
- {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/setup.py +0 -0
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Optimizely Python SDK Changelog
|
|
2
2
|
|
|
3
|
+
## 5.5.0
|
|
4
|
+
April 30th, 2026
|
|
5
|
+
|
|
6
|
+
### New Features
|
|
7
|
+
|
|
8
|
+
#### **Feature Rollout Support**
|
|
9
|
+
|
|
10
|
+
- Added support for Feature Rollouts, a new rule type that combines Targeted Delivery simplicity with A/B test measurement capabilities. During project config parsing, the "everyone else" variation from the flag's rollout is automatically injected into feature rollout experiments, enabling correct evaluation without changes to decision logic. ([#499](https://github.com/optimizely/python-sdk/pull/499))
|
|
11
|
+
- Removed experiment type validation from config parsing to support flexible rule types. ([#500](https://github.com/optimizely/python-sdk/pull/500))
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Enhancements
|
|
16
|
+
|
|
17
|
+
- Added event retry support for reliable event dispatching. ([#475](https://github.com/optimizely/python-sdk/pull/475))
|
|
18
|
+
- Excluded CMAB experiments from UserProfileService to prevent stale cached decisions. ([#474](https://github.com/optimizely/python-sdk/pull/474))
|
|
19
|
+
- Cleaned up flag-level holdout fields for simplified configuration. ([#507](https://github.com/optimizely/python-sdk/pull/507))
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
- Fixed `return` in `finally` block silently swallowing exceptions. ([#505](https://github.com/optimizely/python-sdk/pull/505))
|
|
26
|
+
- Fixed `PollingConfigManager` rejecting valid URL-only configuration. ([#497](https://github.com/optimizely/python-sdk/pull/497))
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## 5.4.0
|
|
32
|
+
December 19th, 2025
|
|
33
|
+
|
|
34
|
+
### New Features
|
|
35
|
+
|
|
36
|
+
Holdout features:
|
|
37
|
+
|
|
38
|
+
- Added support for Holdouts in Feature Experimentation, enabling global traffic exclusion from experiments for statistical confidence validation. ([#469](https://github.com/optimizely/python-sdk/pull/469))
|
|
39
|
+
- Added holdout support for default decision service ([#467](https://github.com/optimizely/python-sdk/pull/467))
|
|
40
|
+
- Project Config updated for Holdout ([#464](https://github.com/optimizely/python-sdk/pull/464))
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Bug Fixes
|
|
45
|
+
- Resolved issues with Holdout impression event handling and notification delivery. ([#471](https://github.com/optimizely/python-sdk/pull/471))
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
|
|
3
50
|
## 5.3.0
|
|
4
51
|
November 13th, 2025
|
|
5
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: optimizely-sdk
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.5.0
|
|
4
4
|
Summary: Python SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts.
|
|
5
5
|
Home-page: https://github.com/optimizely/python-sdk
|
|
6
6
|
Author: Optimizely
|
|
@@ -24,17 +24,17 @@ Requires-Dist: requests>=2.21
|
|
|
24
24
|
Requires-Dist: idna>=2.10
|
|
25
25
|
Requires-Dist: rpds-py<0.20.0; python_version < "3.11"
|
|
26
26
|
Provides-Extra: test
|
|
27
|
-
Requires-Dist: flake8>=4.0.1; extra == "test"
|
|
28
|
-
Requires-Dist: python-coveralls; extra == "test"
|
|
29
|
-
Requires-Dist: jsonschema>=3.2.0; extra == "test"
|
|
30
|
-
Requires-Dist: rpds-py<0.20.0; python_version < "3.11" and extra == "test"
|
|
31
27
|
Requires-Dist: requests>=2.21; extra == "test"
|
|
32
|
-
Requires-Dist: pyrsistent>=0.16.0; extra == "test"
|
|
33
28
|
Requires-Dist: idna>=2.10; extra == "test"
|
|
34
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: ruff>=0.15.0; extra == "test"
|
|
30
|
+
Requires-Dist: python-coveralls; extra == "test"
|
|
35
31
|
Requires-Dist: coverage; extra == "test"
|
|
36
|
-
Requires-Dist: funcsigs>=0.4; extra == "test"
|
|
37
32
|
Requires-Dist: pytest-cov; extra == "test"
|
|
33
|
+
Requires-Dist: rpds-py<0.20.0; python_version < "3.11" and extra == "test"
|
|
34
|
+
Requires-Dist: jsonschema>=3.2.0; extra == "test"
|
|
35
|
+
Requires-Dist: funcsigs>=0.4; extra == "test"
|
|
36
|
+
Requires-Dist: pytest>=6.2.0; extra == "test"
|
|
37
|
+
Requires-Dist: pyrsistent>=0.16.0; extra == "test"
|
|
38
38
|
Dynamic: author
|
|
39
39
|
Dynamic: author-email
|
|
40
40
|
Dynamic: classifier
|
|
@@ -304,6 +304,53 @@ idna (BSD 3-Clause License https://github.com/kjd/idna/blob/master/LICENSE.md)
|
|
|
304
304
|
- Swift - https://github.com/optimizely/swift-sdk
|
|
305
305
|
# Optimizely Python SDK Changelog
|
|
306
306
|
|
|
307
|
+
## 5.5.0
|
|
308
|
+
April 30th, 2026
|
|
309
|
+
|
|
310
|
+
### New Features
|
|
311
|
+
|
|
312
|
+
#### **Feature Rollout Support**
|
|
313
|
+
|
|
314
|
+
- Added support for Feature Rollouts, a new rule type that combines Targeted Delivery simplicity with A/B test measurement capabilities. During project config parsing, the "everyone else" variation from the flag's rollout is automatically injected into feature rollout experiments, enabling correct evaluation without changes to decision logic. ([#499](https://github.com/optimizely/python-sdk/pull/499))
|
|
315
|
+
- Removed experiment type validation from config parsing to support flexible rule types. ([#500](https://github.com/optimizely/python-sdk/pull/500))
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### Enhancements
|
|
320
|
+
|
|
321
|
+
- Added event retry support for reliable event dispatching. ([#475](https://github.com/optimizely/python-sdk/pull/475))
|
|
322
|
+
- Excluded CMAB experiments from UserProfileService to prevent stale cached decisions. ([#474](https://github.com/optimizely/python-sdk/pull/474))
|
|
323
|
+
- Cleaned up flag-level holdout fields for simplified configuration. ([#507](https://github.com/optimizely/python-sdk/pull/507))
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### Bug Fixes
|
|
328
|
+
|
|
329
|
+
- Fixed `return` in `finally` block silently swallowing exceptions. ([#505](https://github.com/optimizely/python-sdk/pull/505))
|
|
330
|
+
- Fixed `PollingConfigManager` rejecting valid URL-only configuration. ([#497](https://github.com/optimizely/python-sdk/pull/497))
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
## 5.4.0
|
|
336
|
+
December 19th, 2025
|
|
337
|
+
|
|
338
|
+
### New Features
|
|
339
|
+
|
|
340
|
+
Holdout features:
|
|
341
|
+
|
|
342
|
+
- Added support for Holdouts in Feature Experimentation, enabling global traffic exclusion from experiments for statistical confidence validation. ([#469](https://github.com/optimizely/python-sdk/pull/469))
|
|
343
|
+
- Added holdout support for default decision service ([#467](https://github.com/optimizely/python-sdk/pull/467))
|
|
344
|
+
- Project Config updated for Holdout ([#464](https://github.com/optimizely/python-sdk/pull/464))
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### Bug Fixes
|
|
349
|
+
- Resolved issues with Holdout impression event handling and notification delivery. ([#471](https://github.com/optimizely/python-sdk/pull/471))
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
|
|
307
354
|
## 5.3.0
|
|
308
355
|
November 13th, 2025
|
|
309
356
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
|
-
from typing import Optional, TYPE_CHECKING
|
|
15
|
+
from typing import Optional, TYPE_CHECKING, cast
|
|
16
16
|
import math
|
|
17
17
|
from sys import version_info
|
|
18
18
|
|
|
@@ -28,7 +28,7 @@ else:
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
# prevent circular dependenacy by skipping import at runtime
|
|
30
30
|
from .project_config import ProjectConfig
|
|
31
|
-
from .entities import Experiment, Variation
|
|
31
|
+
from .entities import Experiment, Variation, Holdout
|
|
32
32
|
from .helpers.types import TrafficAllocation
|
|
33
33
|
|
|
34
34
|
|
|
@@ -104,8 +104,8 @@ class Bucketer:
|
|
|
104
104
|
|
|
105
105
|
def bucket(
|
|
106
106
|
self, project_config: ProjectConfig,
|
|
107
|
-
experiment: Experiment, user_id: str, bucketing_id: str
|
|
108
|
-
) -> tuple[
|
|
107
|
+
experiment: Experiment | Holdout, user_id: str, bucketing_id: str
|
|
108
|
+
) -> tuple[Variation | None, list[str]]:
|
|
109
109
|
""" For a given experiment and bucketing ID determines variation to be shown to user.
|
|
110
110
|
|
|
111
111
|
Args:
|
|
@@ -125,14 +125,9 @@ class Bucketer:
|
|
|
125
125
|
project_config.logger.debug(message)
|
|
126
126
|
return None, []
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
experiment_id = experiment.get('id', '')
|
|
132
|
-
else:
|
|
133
|
-
# This is an Experiment object
|
|
134
|
-
experiment_key = experiment.key
|
|
135
|
-
experiment_id = experiment.id
|
|
128
|
+
# Handle both Experiment and Holdout entities
|
|
129
|
+
experiment_key = experiment.key
|
|
130
|
+
experiment_id = experiment.id
|
|
136
131
|
|
|
137
132
|
if not experiment_key or not experiment_key.strip():
|
|
138
133
|
message = 'Invalid entity key provided for bucketing. Returning nil.'
|
|
@@ -141,14 +136,9 @@ class Bucketer:
|
|
|
141
136
|
|
|
142
137
|
variation_id, decide_reasons = self.bucket_to_entity_id(project_config, experiment, user_id, bucketing_id)
|
|
143
138
|
if variation_id:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
variation = next((v for v in variations if v.get('id') == variation_id), None)
|
|
148
|
-
else:
|
|
149
|
-
# For experiments, use the existing method
|
|
150
|
-
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
|
151
|
-
return variation, decide_reasons
|
|
139
|
+
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
|
140
|
+
# Cast is safe here because experiments always use Variation entities, not VariationDict
|
|
141
|
+
return cast('Optional[Variation]', variation), decide_reasons
|
|
152
142
|
|
|
153
143
|
# No variation found - log message for empty traffic range
|
|
154
144
|
message = 'Bucketed into an empty traffic range. Returning nil.'
|
|
@@ -158,7 +148,7 @@ class Bucketer:
|
|
|
158
148
|
|
|
159
149
|
def bucket_to_entity_id(
|
|
160
150
|
self, project_config: ProjectConfig,
|
|
161
|
-
experiment: Experiment, user_id: str, bucketing_id: str
|
|
151
|
+
experiment: Experiment | Holdout, user_id: str, bucketing_id: str
|
|
162
152
|
) -> tuple[Optional[str], list[str]]:
|
|
163
153
|
"""
|
|
164
154
|
For a given experiment and bucketing ID determines variation ID to be shown to user.
|
|
@@ -176,58 +166,52 @@ class Bucketer:
|
|
|
176
166
|
if not experiment:
|
|
177
167
|
return None, decide_reasons
|
|
178
168
|
|
|
179
|
-
# Handle both Experiment
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
experiment_id = experiment.id
|
|
191
|
-
traffic_allocations = experiment.trafficAllocation
|
|
192
|
-
has_cmab = bool(experiment.cmab)
|
|
169
|
+
# Handle both Experiment and Holdout entities
|
|
170
|
+
# Both entities have key, id, and trafficAllocation attributes
|
|
171
|
+
from . import entities
|
|
172
|
+
|
|
173
|
+
experiment_key = experiment.key
|
|
174
|
+
experiment_id = experiment.id
|
|
175
|
+
traffic_allocations = experiment.trafficAllocation
|
|
176
|
+
|
|
177
|
+
# Determine if experiment is in a mutually exclusive group
|
|
178
|
+
# Holdouts don't have groupId or groupPolicy - use isinstance for type narrowing
|
|
179
|
+
if isinstance(experiment, entities.Experiment):
|
|
193
180
|
group_policy = getattr(experiment, 'groupPolicy', None)
|
|
181
|
+
if group_policy and group_policy in GROUP_POLICIES:
|
|
182
|
+
group = project_config.get_group(experiment.groupId)
|
|
194
183
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if group_policy and group_policy in GROUP_POLICIES:
|
|
198
|
-
group = project_config.get_group(experiment.groupId)
|
|
184
|
+
if not group:
|
|
185
|
+
return None, decide_reasons
|
|
199
186
|
|
|
200
|
-
|
|
201
|
-
|
|
187
|
+
user_experiment_id = self.find_bucket(
|
|
188
|
+
project_config, bucketing_id, experiment.groupId, group.trafficAllocation,
|
|
189
|
+
)
|
|
202
190
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
191
|
+
if not user_experiment_id:
|
|
192
|
+
message = f'User "{user_id}" is in no experiment.'
|
|
193
|
+
project_config.logger.info(message)
|
|
194
|
+
decide_reasons.append(message)
|
|
195
|
+
return None, decide_reasons
|
|
206
196
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
197
|
+
if user_experiment_id != experiment_id:
|
|
198
|
+
message = f'User "{user_id}" is not in experiment "{experiment_key}" of group {experiment.groupId}.'
|
|
199
|
+
project_config.logger.info(message)
|
|
200
|
+
decide_reasons.append(message)
|
|
201
|
+
return None, decide_reasons
|
|
212
202
|
|
|
213
|
-
|
|
214
|
-
message = f'User "{user_id}" is not in experiment "{experiment_key}" of group {experiment.groupId}.'
|
|
203
|
+
message = f'User "{user_id}" is in experiment {experiment_key} of group {experiment.groupId}.'
|
|
215
204
|
project_config.logger.info(message)
|
|
216
205
|
decide_reasons.append(message)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
"entityId": "$",
|
|
228
|
-
"endOfRange": experiment.cmab['trafficAllocation']
|
|
229
|
-
}
|
|
230
|
-
]
|
|
206
|
+
|
|
207
|
+
# Holdouts don't have cmab - use isinstance for type narrowing
|
|
208
|
+
if isinstance(experiment, entities.Experiment) and experiment.cmab:
|
|
209
|
+
traffic_allocations = [
|
|
210
|
+
{
|
|
211
|
+
"entityId": "$",
|
|
212
|
+
"endOfRange": experiment.cmab['trafficAllocation']
|
|
213
|
+
}
|
|
214
|
+
]
|
|
231
215
|
|
|
232
216
|
# Bucket user if not in white-list and in group (if any)
|
|
233
217
|
variation_id = self.find_bucket(project_config, bucketing_id,
|
|
@@ -145,14 +145,14 @@ class StaticConfigManager(BaseConfigManager):
|
|
|
145
145
|
except optimizely_exceptions.UnsupportedDatafileVersionException as error:
|
|
146
146
|
error_msg = error.args[0]
|
|
147
147
|
error_to_handle = error
|
|
148
|
-
except:
|
|
148
|
+
except Exception:
|
|
149
149
|
error_msg = enums.Errors.INVALID_INPUT.format('datafile')
|
|
150
150
|
error_to_handle = optimizely_exceptions.InvalidInputException(error_msg)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
|
|
152
|
+
if error_msg or config is None:
|
|
153
|
+
self.logger.error(error_msg)
|
|
154
|
+
self.error_handler.handle_error(error_to_handle or Exception('Unknown Error'))
|
|
155
|
+
return
|
|
156
156
|
|
|
157
157
|
previous_revision = self._config.get_revision() if self._config else None
|
|
158
158
|
|
|
@@ -236,9 +236,6 @@ class PollingConfigManager(StaticConfigManager):
|
|
|
236
236
|
)
|
|
237
237
|
self._sdk_key = sdk_key or self._sdk_key
|
|
238
238
|
|
|
239
|
-
if self._sdk_key is None:
|
|
240
|
-
raise optimizely_exceptions.InvalidInputException(enums.Errors.MISSING_SDK_KEY)
|
|
241
|
-
|
|
242
239
|
self.datafile_url = self.get_datafile_url(
|
|
243
240
|
self._sdk_key, url, url_template or self.DATAFILE_URL_TEMPLATE
|
|
244
241
|
)
|