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.
Files changed (73) hide show
  1. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/CHANGELOG.md +47 -0
  2. {optimizely_sdk-5.3.0/optimizely_sdk.egg-info → optimizely_sdk-5.5.0}/PKG-INFO +55 -8
  3. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/bucketer.py +49 -65
  4. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/config_manager.py +6 -9
  5. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision_service.py +142 -124
  6. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/entities.py +73 -0
  7. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/event_factory.py +5 -1
  8. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/user_event.py +4 -3
  9. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/user_event_factory.py +6 -5
  10. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event_dispatcher.py +1 -1
  11. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/enums.py +8 -0
  12. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/types.py +4 -2
  13. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/validator.py +2 -2
  14. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event_manager.py +8 -1
  15. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely.py +12 -6
  16. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/project_config.py +125 -81
  17. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/version.py +1 -1
  18. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0/optimizely_sdk.egg-info}/PKG-INFO +55 -8
  19. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/requires.txt +6 -6
  20. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/test.txt +1 -1
  21. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/LICENSE +0 -0
  22. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/MANIFEST.in +0 -0
  23. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/README.md +0 -0
  24. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/__init__.py +0 -0
  25. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/__init__.py +0 -0
  26. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/cmab_client.py +0 -0
  27. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/cmab/cmab_service.py +0 -0
  28. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/__init__.py +0 -0
  29. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decide_option.py +0 -0
  30. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decision.py +0 -0
  31. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/decision/optimizely_decision_message.py +0 -0
  32. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/error_handler.py +0 -0
  33. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/__init__.py +0 -0
  34. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/event_processor.py +0 -0
  35. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/log_event.py +0 -0
  36. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event/payload.py +0 -0
  37. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/event_builder.py +0 -0
  38. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/exceptions.py +0 -0
  39. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/__init__.py +0 -0
  40. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/audience.py +0 -0
  41. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/condition.py +0 -0
  42. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/condition_tree_evaluator.py +0 -0
  43. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/constants.py +0 -0
  44. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/event_tag_utils.py +0 -0
  45. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/experiment.py +0 -0
  46. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/helpers/sdk_settings.py +0 -0
  47. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/lib/__init__.py +0 -0
  48. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/lib/pymmh3.py +0 -0
  49. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/logger.py +0 -0
  50. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/notification_center.py +0 -0
  51. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/notification_center_registry.py +0 -0
  52. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/__init__.py +0 -0
  53. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/lru_cache.py +0 -0
  54. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_config.py +0 -0
  55. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event.py +0 -0
  56. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_event_api_manager.py +0 -0
  57. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_manager.py +0 -0
  58. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_segment_api_manager.py +0 -0
  59. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/odp_segment_manager.py +0 -0
  60. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/odp/optimizely_odp_option.py +0 -0
  61. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_config.py +0 -0
  62. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_factory.py +0 -0
  63. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/optimizely_user_context.py +0 -0
  64. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/py.typed +0 -0
  65. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely/user_profile.py +0 -0
  66. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/SOURCES.txt +0 -0
  67. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/dependency_links.txt +0 -0
  68. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/optimizely_sdk.egg-info/top_level.txt +0 -0
  69. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/core.txt +0 -0
  70. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/docs.txt +0 -0
  71. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/requirements/typing.txt +0 -0
  72. {optimizely_sdk-5.3.0 → optimizely_sdk-5.5.0}/setup.cfg +0 -0
  73. {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.0
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: pytest>=6.2.0; extra == "test"
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[Optional[Variation], list[str]]:
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
- if isinstance(experiment, dict):
129
- # This is a holdout dictionary
130
- experiment_key = experiment.get('key', '')
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
- if isinstance(experiment, dict):
145
- # For holdouts, find the variation in the holdout's variations array
146
- variations = experiment.get('variations', [])
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 objects and holdout dictionaries
180
- if isinstance(experiment, dict):
181
- # This is a holdout dictionary - holdouts don't have groups
182
- experiment_key = experiment.get('key', '')
183
- experiment_id = experiment.get('id', '')
184
- traffic_allocations = experiment.get('trafficAllocation', [])
185
- has_cmab = False
186
- group_policy = None
187
- else:
188
- # This is an Experiment object
189
- experiment_key = experiment.key
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
- # Determine if experiment is in a mutually exclusive group.
196
- # This will not affect evaluation of rollout rules or holdouts.
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
- if not group:
201
- return None, decide_reasons
187
+ user_experiment_id = self.find_bucket(
188
+ project_config, bucketing_id, experiment.groupId, group.trafficAllocation,
189
+ )
202
190
 
203
- user_experiment_id = self.find_bucket(
204
- project_config, bucketing_id, experiment.groupId, group.trafficAllocation,
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
- if not user_experiment_id:
208
- message = f'User "{user_id}" is in no experiment.'
209
- project_config.logger.info(message)
210
- decide_reasons.append(message)
211
- return None, decide_reasons
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
- if user_experiment_id != experiment_id:
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
- return None, decide_reasons
218
-
219
- message = f'User "{user_id}" is in experiment {experiment_key} of group {experiment.groupId}.'
220
- project_config.logger.info(message)
221
- decide_reasons.append(message)
222
-
223
- if has_cmab:
224
- if experiment.cmab:
225
- traffic_allocations = [
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
- finally:
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
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
  )