diff --git a/CHANGES.txt b/CHANGES.txt
index 0845c52e..2e3d313d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,7 @@
+10.6.1 (Jun 23, 2026)
+- Updated splitChanges parsing to handle responses without the 'rbs' field.
+- Fixed fallback treatment not being applied when a feature flag has an unsupported matcher type.
+
10.6.0 (Jan 28, 2026)
- Fixed non-blocking error when fetching feature flags from redis.
- Added the ability to listen to different events triggered by the SDK. Read more in our docs.
diff --git a/LICENSE.txt b/LICENSE
similarity index 98%
rename from LICENSE.txt
rename to LICENSE
index 954507dd..d6456956 100644
--- a/LICENSE.txt
+++ b/LICENSE
@@ -1,12 +1,18 @@
-Apache License
+
+ Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
1. Definitions.
+
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
+
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
+
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
@@ -14,19 +20,24 @@ Apache License
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
+
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
+
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
+
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
+
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
+
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
@@ -34,6 +45,7 @@ Apache License
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
+
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
@@ -47,15 +59,18 @@ Apache License
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
+
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
+
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
+
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
@@ -71,19 +86,24 @@ Apache License
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
+
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
+
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
+
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
+
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
+
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
@@ -100,12 +120,14 @@ Apache License
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
+
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
+
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
@@ -113,10 +135,12 @@ Apache License
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
+
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
+
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
@@ -126,6 +150,7 @@ Apache License
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
+
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
@@ -137,6 +162,7 @@ Apache License
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
+
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
@@ -147,8 +173,11 @@ Apache License
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+
END OF TERMS AND CONDITIONS
+
APPENDIX: How to apply the Apache License to your work.
+
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
@@ -157,11 +186,15 @@ Apache License
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2026 Harness Corporation
+
+ Copyright [yyyy] [name of copyright owner]
+
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
+
http://www.apache.org/licenses/LICENSE-2.0
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
diff --git a/NOTICE.txt b/NOTICE
similarity index 53%
rename from NOTICE.txt
rename to NOTICE
index 7d7d845e..440c9e31 100644
--- a/NOTICE.txt
+++ b/NOTICE
@@ -1,5 +1,5 @@
-Harness Feature Management JavaScript SDK Copyright 2024-2026 Harness Inc.
+Harness Feature Management Python SDK Copyright 2024-2026 Harness Inc.
This product includes software developed at Harness Inc. (https://harness.io/).
-This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2015-2024 Split Software, Inc.
+This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2016-2024 Split Software, Inc.
diff --git a/doc/source/introduction.rst b/doc/source/introduction.rst
index a6df7a71..db989102 100644
--- a/doc/source/introduction.rst
+++ b/doc/source/introduction.rst
@@ -6,7 +6,7 @@ This project provides Python programs access to the `Split.io
Installation and Requirements
-----------------------------
-``splitio_client`` supports Python 3 (3.3 or later). Stable versions can be installed from `PyPI `_ using pip: ::
+``splitio_client`` supports Python 3 (3.7 or later). Stable versions can be installed from `PyPI `_ using pip: ::
pip install splitio_client
@@ -14,95 +14,141 @@ and development versions are installed directly from the `Github >> from splitio import get_factory
- >>> factory = get_factory('some_api_key')
+ >>> factory = get_factory('YOUR_SDK_KEY')
+ >>> factory.block_until_ready(5)
>>> client = factory.client()
>>> client.get_treatment('some_user', 'some_feature')
'SOME_TREATMENT'
+For asyncio environments: ::
+
+ >>> from splitio import get_factory_async
+ >>> factory = await get_factory_async('YOUR_SDK_KEY')
+ >>> await factory.block_until_ready(5)
+ >>> client = factory.client()
+ >>> await client.get_treatment('some_user', 'some_feature')
+ 'SOME_TREATMENT'
+
Bucketing key
-------------
-In advanced mode the key can be set as two different parts, one of them just to match the condition and the other one to calculate the treatment bucket ::
+In advanced mode the key can be set as two different parts, one of them just to match the condition and the other one to calculate the treatment bucket: ::
>>> from splitio import get_factory, Key
>>> user = 'some_user_or_anonymous'
>>> bucketing_key = 'some_random_string'
>>> split_key = Key(user, bucketing_key)
- >>> factory = get_factory('API_KEY')
+ >>> factory = get_factory('YOUR_SDK_KEY')
+ >>> factory.block_until_ready(5)
>>> client = factory.client()
>>> client.get_treatment(split_key, 'some_feature')
Manager API
-----------
-Manager API is very useful to get a representation (view) of cached splits: ::
+Manager API is useful to get a representation (view) of cached feature flags: ::
>>> from splitio import get_factory
- >>> factory = get_factory('API_KEY')
+ >>> factory = get_factory('YOUR_SDK_KEY')
+ >>> factory.block_until_ready(5)
>>> manager = factory.manager()
Available methods:
-**splits():** Returns a list of SplitView instance ::
+**splits():** Returns a list of SplitView instances: ::
+
>>> manager.splits()
-**split(name):** Returns a SplitView instance ::
- >>> manager.split('some_test_name')
+**split(name):** Returns a SplitView instance: ::
+
+ >>> manager.split('some_feature_flag')
+
+**split_names():** Returns a list of feature flag names (String): ::
-**split_names():** Returns a list of Split names (String) ::
>>> manager.split_names()
+Client API
+----------
+
+The client provides the following methods for evaluating feature flags:
+
+**get_treatment(key, feature_flag_name, attributes=None, evaluation_options=None):** Returns a treatment string for a single flag.
+
+**get_treatment_with_config(key, feature_flag_name, attributes=None, evaluation_options=None):** Returns a treatment string and configuration for a single flag.
+
+**get_treatments(key, feature_flag_names, attributes=None, evaluation_options=None):** Returns treatments for multiple flags.
+
+**get_treatments_with_config(key, feature_flag_names, attributes=None, evaluation_options=None):** Returns treatments and configurations for multiple flags.
+
+**get_treatments_by_flag_set(key, flag_set, attributes=None, evaluation_options=None):** Returns treatments for all flags in a flag set.
+
+**get_treatments_by_flag_sets(key, flag_sets, attributes=None, evaluation_options=None):** Returns treatments for all flags in multiple flag sets.
+
+**get_treatments_with_config_by_flag_set(key, flag_set, attributes=None, evaluation_options=None):** Returns treatments and configs for all flags in a flag set.
+
+**get_treatments_with_config_by_flag_sets(key, flag_sets, attributes=None, evaluation_options=None):** Returns treatments and configs for all flags in multiple flag sets.
+
+**track(key, traffic_type, event_type, value=None, properties=None):** Tracks custom events.
+
+**destroy():** Gracefully shuts down the client and flushes pending data.
+
Client configuration
--------------------
-It's possible to control certain aspects of the client behaviour by supplying a ``config`` dictionary. For instance, the following snippets shows you how to set the segment update interval to 10 seconds: ::
+It's possible to control certain aspects of the client behaviour by supplying a ``config`` dictionary. For instance, the following snippet shows you how to set the segment update interval to 10 seconds: ::
>>> from splitio import get_factory
>>> config = {'segmentsRefreshRate': 10}
- >>> factory = get_factory('some_api_key', config=config)
+ >>> factory = get_factory('YOUR_SDK_KEY', config=config)
+ >>> factory.block_until_ready(5)
>>> client = factory.client()
All the possible configuration options are:
-+------------------------+------+--------------------------------------------------------+---------+
-| Key | Type | Description | Default |
-+========================+======+========================================================+=========+
-| connectionTimeout | int | The timeout for HTTP connections in milliseconds. | 1500 |
-+------------------------+------+--------------------------------------------------------+---------+
-| readTimeout | int | The read timeout for HTTP connections in milliseconds. | 1500 |
-+------------------------+------+--------------------------------------------------------+---------+
-| featuresRefreshRate | int | The features (splits) update refresh period in | 5 |
-| | | seconds. | |
-+------------------------+------+--------------------------------------------------------+---------+
-| segmentsRefreshRate | int | The segments update refresh period in seconds. | 60 |
-+------------------------+------+--------------------------------------------------------+---------+
-| metricsRefreshRate | int | The metrics report period in seconds | 60 |
-+------------------------+------+--------------------------------------------------------+---------+
-| impressionsRefreshRate | int | The impressions report period in seconds | 60 |
-+------------------------+------+--------------------------------------------------------+---------+
-| ready | int | How long to wait (in milliseconds) for the features | |
-| | | and segments information to be available. If the | |
-| | | timeout is exceeded, a ``TimeoutException`` will be | |
-| | | raised. If value is 0, the constructor will return | |
-| | | immediately but not all the information might be | |
-| | | available right away. | |
-+------------------------+------+--------------------------------------------------------+---------+
++----------------------------+------+--------------------------------------------------------+-----------+
+| Key | Type | Description | Default |
++============================+======+========================================================+===========+
+| connectionTimeout | int | The timeout for HTTP connections in milliseconds. | 1500 |
++----------------------------+------+--------------------------------------------------------+-----------+
+| featuresRefreshRate | int | The feature flags update refresh period in seconds. | 30 |
++----------------------------+------+--------------------------------------------------------+-----------+
+| segmentsRefreshRate | int | The segments update refresh period in seconds. | 30 |
++----------------------------+------+--------------------------------------------------------+-----------+
+| impressionsRefreshRate | int | The impressions report period in seconds. | 300 |
++----------------------------+------+--------------------------------------------------------+-----------+
+| eventsPushRate | int | The events report period in seconds. | 10 |
++----------------------------+------+--------------------------------------------------------+-----------+
+| impressionsMode | str | Impressions tracking mode: OPTIMIZED, DEBUG, or NONE. | OPTIMIZED |
++----------------------------+------+--------------------------------------------------------+-----------+
+| streamingEnabled | bool | Enable streaming updates via SSE. | True |
++----------------------------+------+--------------------------------------------------------+-----------+
+| labelsEnabled | bool | Whether to send rule labels with impressions. | True |
++----------------------------+------+--------------------------------------------------------+-----------+
+| IPAddressesEnabled | bool | Send machine name and IP in headers. | True |
++----------------------------+------+--------------------------------------------------------+-----------+
+| flagSetsFilter | list | Only sync feature flags belonging to these flag sets. | None |
++----------------------------+------+--------------------------------------------------------+-----------+
.. _localhost_environment:
+
The localhost environment
-------------------------
-During development it is possible to create a 'localhost client' to avoid hitting the
-Split.io API SDK. The configuration is taken from a ``.split`` file in the user's *HOME*
-directory. The ``.split`` file has the following format: ::
+During development it is possible to create a 'localhost client' to avoid hitting the Split.io API. The configuration is taken from a ``.split`` file in the user's *HOME* directory or from a JSON file. The ``.split`` file has the following format: ::
file: (comment | split_line)+
comment : '#' string*\n
@@ -120,6 +166,7 @@ Whenever a treatment is requested for the feature ``feature_0``, ``treatment_0``
>>> from splitio import get_factory
>>> factory = get_factory('localhost')
+ >>> factory.block_until_ready(5)
>>> client = factory.client()
>>> client.get_treatment('some_user', 'feature_0')
'treatment_0'
@@ -128,11 +175,11 @@ Whenever a treatment is requested for the feature ``feature_0``, ``treatment_0``
>>> client.get_treatment('yet_another_user', 'feature_1')
'treatment_1'
>>> client.get_treatment('some_user', 'non_existent_feature')
- 'CONTROL'
+ 'control'
-Notice that an API key is not necessary for the localhost environment, and the ``CONTROL`` is returned for non existent features.
+Notice that an SDK key is not necessary for the localhost environment, and ``control`` is returned for non-existent features.
-It is possible to specify a different splits file using the ``split_definition_file_name`` argument: ::
+JSON files are also supported in localhost mode by setting the ``splitFile`` config option to a ``.json`` file path: ::
>>> from splitio import get_factory
>>> factory = get_factory('localhost', split_definition_file_name='/path/to/splits/file')
diff --git a/splitio/api/client.py b/splitio/api/client.py
index c9032e0e..559d1405 100644
--- a/splitio/api/client.py
+++ b/splitio/api/client.py
@@ -177,7 +177,7 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
:type telemetry_url: str
"""
HttpClientBase.__init__(self, timeout, sdk_url, events_url, auth_url, telemetry_url)
-
+
def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments
"""
Issue a get request.
@@ -209,9 +209,9 @@ def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint:
except requests.exceptions.ChunkedEncodingError as exc:
_LOGGER.error("IncompleteRead exception detected: %s", exc)
- return HttpResponse(400, "", {})
-
- except Exception as exc: # pylint: disable=broad-except
+ return HttpResponse(400, "", {})
+
+ except Exception as exc: # pylint: disable=broad-except
raise HttpClientException(_EXC_MSG.format(source='request')) from exc
def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
@@ -306,8 +306,8 @@ async def get(self, server, path, apikey, query=None, extra_headers=None): # py
except aiohttp.ClientPayloadError as exc:
_LOGGER.error("ContentLengthError exception detected: %s", exc)
- return HttpResponse(400, "", {})
-
+ return HttpResponse(400, "", {})
+
except aiohttp.ClientError as exc: # pylint: disable=broad-except
raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc
diff --git a/splitio/api/splits.py b/splitio/api/splits.py
index 771100fc..a86ec019 100644
--- a/splitio/api/splits.py
+++ b/splitio/api/splits.py
@@ -104,24 +104,27 @@ def fetch_splits(self, change_number, rbs_change_number, fetch_options):
if 200 <= response.status_code < 300:
if self._spec_version == _SPEC_1_1:
return util.convert_to_new_spec(json.loads(response.body))
-
+
self.clear_storage = self._last_proxy_check_timestamp != 0
self._last_proxy_check_timestamp = 0
- return json.loads(response.body)
+ parsed = json.loads(response.body)
+ if 'rbs' not in parsed:
+ parsed['rbs'] = {"d": [], "s": -1, "t": -1}
+ return parsed
else:
if response.status_code == 414:
_LOGGER.error('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.')
-
+
if self._client.is_sdk_endpoint_overridden() and response.status_code == 400 and self._spec_version == SPEC_VERSION:
_LOGGER.warning('Detected proxy response error, changing spec version from %s to %s and re-fetching.', self._spec_version, _SPEC_1_1)
self._spec_version = _SPEC_1_1
- self._last_proxy_check_timestamp = utctime_ms()
+ self._last_proxy_check_timestamp = utctime_ms()
return self.fetch_splits(change_number, None, FetchOptions(fetch_options.cache_control_headers, fetch_options.change_number,
None, fetch_options.sets, self._spec_version))
-
+
raise APIException(response.body, response.status_code)
-
+
except HttpClientException as exc:
_LOGGER.error('Error fetching feature flags because an exception was raised by the HTTPClient')
_LOGGER.debug('Error: ', exc_info=True)
@@ -178,24 +181,27 @@ async def fetch_splits(self, change_number, rbs_change_number, fetch_options):
if 200 <= response.status_code < 300:
if self._spec_version == _SPEC_1_1:
return util.convert_to_new_spec(json.loads(response.body))
-
+
self.clear_storage = self._last_proxy_check_timestamp != 0
self._last_proxy_check_timestamp = 0
- return json.loads(response.body)
+ parsed = json.loads(response.body)
+ if 'rbs' not in parsed:
+ parsed['rbs'] = {"d": [], "s": -1, "t": -1}
+ return parsed
else:
if response.status_code == 414:
_LOGGER.error('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.')
-
+
if self._client.is_sdk_endpoint_overridden() and response.status_code == 400 and self._spec_version == SPEC_VERSION:
_LOGGER.warning('Detected proxy response error, changing spec version from %s to %s and re-fetching.', self._spec_version, _SPEC_1_1)
self._spec_version = _SPEC_1_1
- self._last_proxy_check_timestamp = utctime_ms()
+ self._last_proxy_check_timestamp = utctime_ms()
return await self.fetch_splits(change_number, None, FetchOptions(fetch_options.cache_control_headers, fetch_options.change_number,
None, fetch_options.sets, self._spec_version))
raise APIException(response.body, response.status_code)
-
+
except HttpClientException as exc:
_LOGGER.error('Error fetching feature flags because an exception was raised by the HTTPClient')
_LOGGER.debug('Error: ', exc_info=True)
diff --git a/splitio/client/config.py b/splitio/client/config.py
index 25b1bc31..8310e5db 100644
--- a/splitio/client/config.py
+++ b/splitio/client/config.py
@@ -170,36 +170,36 @@ def sanitize(sdk_key, config):
' Defaulting to `none` mode.')
processed["httpAuthenticateScheme"] = authenticate_scheme
- processed = _sanitize_fallback_config(config, processed)
-
+ processed = _sanitize_fallback_config(config, processed)
+
if config.get("redisErrors") is not None:
_LOGGER.warning('Parameter `redisErrors` is deprecated as it is no longer supported in redis lib.' \
' Will ignore this value.')
-
+
processed["redisErrors"] = None
return processed
def _sanitize_fallback_config(config, processed):
if config.get('fallbackTreatments') is None:
return processed
-
+
if not isinstance(config['fallbackTreatments'], FallbackTreatmentsConfiguration):
_LOGGER.warning('Config: fallbackTreatments parameter should be of `FallbackTreatmentsConfiguration` class.')
processed['fallbackTreatments'] = None
- return processed
-
+ return processed
+
sanitized_global_fallback_treatment = config['fallbackTreatments'].global_fallback_treatment
if config['fallbackTreatments'].global_fallback_treatment is not None and not validate_fallback_treatment(config['fallbackTreatments'].global_fallback_treatment):
_LOGGER.warning('Config: global fallbacktreatment parameter is discarded.')
sanitized_global_fallback_treatment = None
-
+
sanitized_flag_fallback_treatments = {}
if config['fallbackTreatments'].by_flag_fallback_treatment is not None:
for feature_name in config['fallbackTreatments'].by_flag_fallback_treatment.keys():
if not validate_regex_name(feature_name) or not validate_fallback_treatment(config['fallbackTreatments'].by_flag_fallback_treatment[feature_name]):
_LOGGER.warning('Config: fallback treatment parameter for feature flag %s is discarded.', feature_name)
continue
-
+
sanitized_flag_fallback_treatments[feature_name] = config['fallbackTreatments'].by_flag_fallback_treatment[feature_name]
processed['fallbackTreatments'] = FallbackTreatmentsConfiguration(sanitized_global_fallback_treatment, sanitized_flag_fallback_treatments)
diff --git a/splitio/engine/evaluator.py b/splitio/engine/evaluator.py
index b47db5c5..908cfadc 100644
--- a/splitio/engine/evaluator.py
+++ b/splitio/engine/evaluator.py
@@ -64,7 +64,13 @@ def eval_with_context(self, key, bucketing, feature_name, attrs, ctx):
else:
label, _treatment = self._check_prerequisites(feature, bucketing, key, attrs, ctx, label, _treatment)
label, _treatment = self._get_treatment(feature, bucketing, key, attrs, ctx, label, _treatment)
- config = feature.get_configurations_for(_treatment)
+ if _treatment == CONTROL:
+ fallback_treatment = self._fallback_treatment_calculator.resolve(feature_name, label)
+ label = fallback_treatment.label
+ _treatment = fallback_treatment.treatment
+ config = fallback_treatment.config
+ else:
+ config = feature.get_configurations_for(_treatment)
return {
'treatment': _treatment,