wingify-fme-python-sdk 1.55.0__tar.gz → 1.61.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.
- {wingify_fme_python_sdk-1.55.0/wingify_fme_python_sdk.egg-info → wingify_fme_python_sdk-1.61.0}/PKG-INFO +22 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/README.md +21 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/setup.py +1 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/api/get_flag_api.py +15 -5
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/constants/Constants.py +1 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/user/context_model.py +16 -2
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/core/segmentation_manager.py +29 -7
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.py +1 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.py +4 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.py +24 -0
- wingify_fme_python_sdk-1.61.0/wingify/packages/segmentation_evaluator/utils/web_testing_segment_util.py +295 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/error-messages.json +11 -2
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/hooks_manager.py +10 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/campaign_util.py +81 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/meg_util.py +9 -5
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/rule_evaluation_util.py +2 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/wingify_client.py +24 -20
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0/wingify_fme_python_sdk.egg-info}/PKG-INFO +22 -1
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify_fme_python_sdk.egg-info/SOURCES.txt +1 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/LICENSE +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/MANIFEST.in +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/NOTICE +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/requirements-dev.txt +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/requirements.txt +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/setup.cfg +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/vwo/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/vwo/vwo.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/vwo/vwo_builder.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/vwo/vwo_client.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/api/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/api/set_attribute_api.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/api/track_api.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/constants/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/decorators/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/decorators/storage_decorator.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/api_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/campaign_type_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/debug_category_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/event_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/headers_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/hooks_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/status_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/storage_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/enums/url_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/Alias.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/AliasSetResponse.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/batch_event_data.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/campaign_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/feature_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/holdout_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/impact_campaign_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/metric_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/rule_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/variable_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/campaign/variation_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/gateway_service_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/campaign_group_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/campaign_metric_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/campaign_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/campaign_variation_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/empty_object_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/feature_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/group_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/holdout_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/rule_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/settings_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/schemas/variable_schema.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/settings/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/settings/settings_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/storage/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/storage/storage_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/user/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/user/context_vwo_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/user/get_flag.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/vwo_options_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/wingify_options_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/decision_maker/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/decision_maker/decision_maker.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/core/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/core/log_manager.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/core/log_transport_manager.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/enums/log_level_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/enums/log_level_number_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/log_message_builder.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/logger.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/transports/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/logger/transports/console_transport.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/client/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/client/network_client.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/handlers/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/handlers/request_handler.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/manager/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/manager/network_manager.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/models/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/models/global_request_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/models/request_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/network_layer/models/response_model.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/core/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/evaluators/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/utils/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/segmentation_evaluator/utils/segment_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/storage/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/storage/connector.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/packages/storage/storage.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/debug-messages.json +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/info-message.json +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/trace-messages.json +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/resources/warn-messages.json +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/batch_event_queue.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/campaign_decision_service.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/settings_manager.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/storage_service.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/url_service.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/__init__.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/aliasing_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/brand_context.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/brand_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/data_type_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/debugger_service_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/decision_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/event_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/function_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/gateway_service_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/holdout_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/impression_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/log_message_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/model_utils.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/network_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/settings_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/usage_stats_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/utils/uuid_util.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/vwo_client.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/wingify.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/wingify_builder.py +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify_fme_python_sdk.egg-info/dependency_links.txt +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify_fme_python_sdk.egg-info/requires.txt +0 -0
- {wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify_fme_python_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wingify-fme-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.61.0
|
|
4
4
|
Summary: Wingify Feature Management and Experimentation SDK for Python
|
|
5
5
|
Home-page: https://github.com/wingify/vwo-fme-python-sdk
|
|
6
6
|
Author: Wingify
|
|
@@ -168,9 +168,30 @@ context = {
|
|
|
168
168
|
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
|
169
169
|
'ip_address': '1.1.1.1',
|
|
170
170
|
'session_id': 1697123456 # Optional: Custom session ID for web client integration
|
|
171
|
+
'platformVariables': {
|
|
172
|
+
# Reference example only:.
|
|
173
|
+
# In production, fetch campaign assignments using script run in frontend and pass that object to backend as webTestingCampaigns.
|
|
174
|
+
'webTestingCampaigns': '{"122":"1","130":"2"}'
|
|
175
|
+
},
|
|
176
|
+
'session_id': 1697123456 # Optional: Custom session ID for web client integration
|
|
171
177
|
}
|
|
172
178
|
```
|
|
173
179
|
|
|
180
|
+
## Web testing pre-segmentation
|
|
181
|
+
|
|
182
|
+
Server-side flag decisions can align with **Web Testing** (browser) experiments. **Campaign assignments are typically read on the frontend** (for example, VWO cookies) and sent to your server; pass them in `platformVariables.webTestingCampaigns` as a map of **campaign ID -> variation ID** (strings), or as a **JSON string** of that object.
|
|
183
|
+
|
|
184
|
+
Pre-segment rules in the VWO dashboard can use the **`campaignVariation`** operator:
|
|
185
|
+
|
|
186
|
+
| Operand pattern | Meaning |
|
|
187
|
+
| --------------- | ------- |
|
|
188
|
+
| `C` | User is in campaign `C` (any variation). |
|
|
189
|
+
| `!C` | User is **not** in campaign `C`. |
|
|
190
|
+
| `C_V` | User is in campaign `C` with variation `V`. |
|
|
191
|
+
| `C_!V` | User is in campaign `C` and assigned variation is **not** `V`. |
|
|
192
|
+
|
|
193
|
+
Rollout rules evaluate pre-segments from the **first variation** only; AB/testing rules use **campaign-level** segments.
|
|
194
|
+
|
|
174
195
|
## Session Management
|
|
175
196
|
|
|
176
197
|
The SDK provides automatic session management capabilities to enable seamless integration with VWO's web client testing campaigns. Session IDs are automatically generated and managed to connect server-side feature flag decisions with client-side user sessions.
|
|
@@ -141,9 +141,30 @@ context = {
|
|
|
141
141
|
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
|
142
142
|
'ip_address': '1.1.1.1',
|
|
143
143
|
'session_id': 1697123456 # Optional: Custom session ID for web client integration
|
|
144
|
+
'platformVariables': {
|
|
145
|
+
# Reference example only:.
|
|
146
|
+
# In production, fetch campaign assignments using script run in frontend and pass that object to backend as webTestingCampaigns.
|
|
147
|
+
'webTestingCampaigns': '{"122":"1","130":"2"}'
|
|
148
|
+
},
|
|
149
|
+
'session_id': 1697123456 # Optional: Custom session ID for web client integration
|
|
144
150
|
}
|
|
145
151
|
```
|
|
146
152
|
|
|
153
|
+
## Web testing pre-segmentation
|
|
154
|
+
|
|
155
|
+
Server-side flag decisions can align with **Web Testing** (browser) experiments. **Campaign assignments are typically read on the frontend** (for example, VWO cookies) and sent to your server; pass them in `platformVariables.webTestingCampaigns` as a map of **campaign ID -> variation ID** (strings), or as a **JSON string** of that object.
|
|
156
|
+
|
|
157
|
+
Pre-segment rules in the VWO dashboard can use the **`campaignVariation`** operator:
|
|
158
|
+
|
|
159
|
+
| Operand pattern | Meaning |
|
|
160
|
+
| --------------- | ------- |
|
|
161
|
+
| `C` | User is in campaign `C` (any variation). |
|
|
162
|
+
| `!C` | User is **not** in campaign `C`. |
|
|
163
|
+
| `C_V` | User is in campaign `C` with variation `V`. |
|
|
164
|
+
| `C_!V` | User is in campaign `C` and assigned variation is **not** `V`. |
|
|
165
|
+
|
|
166
|
+
Rollout rules evaluate pre-segments from the **first variation** only; AB/testing rules use **campaign-level** segments.
|
|
167
|
+
|
|
147
168
|
## Session Management
|
|
148
169
|
|
|
149
170
|
The SDK provides automatic session management capabilities to enable seamless integration with VWO's web client testing campaigns. Session IDs are automatically generated and managed to connect server-side feature flag decisions with client-side user sessions.
|
|
@@ -41,7 +41,11 @@ from ..utils.impression_util import (
|
|
|
41
41
|
from ..models.user.get_flag import GetFlag
|
|
42
42
|
from ..services.storage_service import StorageService
|
|
43
43
|
from ..decorators.storage_decorator import StorageDecorator
|
|
44
|
-
from ..utils.campaign_util import
|
|
44
|
+
from ..utils.campaign_util import (
|
|
45
|
+
get_experiment_key,
|
|
46
|
+
get_rollout_key,
|
|
47
|
+
get_variation_from_campaign_key,
|
|
48
|
+
)
|
|
45
49
|
from ..enums.debug_category_enum import DebugCategoryEnum
|
|
46
50
|
from ..packages.logger.enums.log_level_enum import LogLevelEnum
|
|
47
51
|
from ..constants.Constants import Constants
|
|
@@ -392,7 +396,7 @@ class GetFlagApi:
|
|
|
392
396
|
|
|
393
397
|
self._evaluated_feature_map[feature_key] = {
|
|
394
398
|
"rolloutId": rule.get_id(),
|
|
395
|
-
"rolloutKey": rule
|
|
399
|
+
"rolloutKey": get_rollout_key(rule, feature_key),
|
|
396
400
|
"rolloutVariationId": (
|
|
397
401
|
rule.get_variations()[0].get_id()
|
|
398
402
|
if rule.get_variations()
|
|
@@ -493,7 +497,9 @@ class GetFlagApi:
|
|
|
493
497
|
self._passed_rules_information.update(
|
|
494
498
|
{
|
|
495
499
|
"experimentId": rule.get_id(),
|
|
496
|
-
"experimentKey":
|
|
500
|
+
"experimentKey": get_experiment_key(
|
|
501
|
+
rule, feature.get_key()
|
|
502
|
+
),
|
|
497
503
|
"experimentVariationId": whitelisted_object[
|
|
498
504
|
"variationId"
|
|
499
505
|
],
|
|
@@ -627,7 +633,9 @@ class GetFlagApi:
|
|
|
627
633
|
self._passed_rules_information.update(
|
|
628
634
|
{
|
|
629
635
|
"rolloutId": campaign.get_id(),
|
|
630
|
-
"rolloutKey":
|
|
636
|
+
"rolloutKey": get_rollout_key(
|
|
637
|
+
campaign, decision.get("featureKey")
|
|
638
|
+
),
|
|
631
639
|
"rolloutVariationId": variation.get_id(),
|
|
632
640
|
}
|
|
633
641
|
)
|
|
@@ -635,7 +643,9 @@ class GetFlagApi:
|
|
|
635
643
|
self._passed_rules_information.update(
|
|
636
644
|
{
|
|
637
645
|
"experimentId": campaign.get_id(),
|
|
638
|
-
"experimentKey":
|
|
646
|
+
"experimentKey": get_experiment_key(
|
|
647
|
+
campaign, decision.get("featureKey")
|
|
648
|
+
),
|
|
639
649
|
"experimentVariationId": variation.get_id(),
|
|
640
650
|
}
|
|
641
651
|
)
|
{wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/models/user/context_model.py
RENAMED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
from .context_vwo_model import ContextVWOModel
|
|
17
|
-
from typing import Dict, List
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
from ...utils.data_type_util import is_object
|
|
18
19
|
from ...utils.uuid_util import get_uuid
|
|
19
20
|
from ...services.settings_manager import SettingsManager
|
|
20
21
|
from ...utils.function_util import get_current_unix_timestamp
|
|
@@ -33,6 +34,11 @@ class ContextModel:
|
|
|
33
34
|
self._vwo = ContextVWOModel(vwo_context_data) if vwo_context_data else None
|
|
34
35
|
self.post_segmentation_variables = context.get("post_segmentation_variables", [])
|
|
35
36
|
self.bucketingSeed = context.get("bucketingSeed")
|
|
37
|
+
self.platform_variables: Optional[Dict[str, Any]] = (
|
|
38
|
+
dict(context.get("platform_variables"))
|
|
39
|
+
if is_object(context.get("platform_variables"))
|
|
40
|
+
else None
|
|
41
|
+
)
|
|
36
42
|
self.session_id = context.get("session_id", None)
|
|
37
43
|
if self.session_id is None:
|
|
38
44
|
self.session_id = get_current_unix_timestamp()
|
|
@@ -96,4 +102,12 @@ class ContextModel:
|
|
|
96
102
|
self.session_id = session_id
|
|
97
103
|
|
|
98
104
|
def get_bucketing_seed(self) -> str:
|
|
99
|
-
return self.bucketingSeed
|
|
105
|
+
return self.bucketingSeed
|
|
106
|
+
|
|
107
|
+
def get_platform_variables(self) -> Optional[Dict[str, Any]]:
|
|
108
|
+
return self.platform_variables
|
|
109
|
+
|
|
110
|
+
def set_platform_variables(self, platform_variables: Optional[Dict[str, Any]]) -> None:
|
|
111
|
+
self.platform_variables = (
|
|
112
|
+
dict(platform_variables) if is_object(platform_variables) else None
|
|
113
|
+
)
|
|
@@ -24,6 +24,10 @@ from ....models.user.context_model import ContextModel
|
|
|
24
24
|
from ....utils.gateway_service_util import get_query_params, get_from_gateway_service
|
|
25
25
|
from ....constants.Constants import Constants
|
|
26
26
|
from ....enums.api_enum import ApiEnum
|
|
27
|
+
from ..utils.web_testing_segment_util import (
|
|
28
|
+
dsl_contains_campaign_variation_node,
|
|
29
|
+
is_web_testing_campaigns_absent_or_null,
|
|
30
|
+
)
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
class SegmentationManager:
|
|
@@ -81,14 +85,25 @@ class SegmentationManager:
|
|
|
81
85
|
|
|
82
86
|
# Check if any holdout requires gateway service
|
|
83
87
|
holdouts = settings.get_holdouts() or []
|
|
84
|
-
is_gateway_service_required_for_holdouts = len(
|
|
85
|
-
|
|
88
|
+
is_gateway_service_required_for_holdouts = len(
|
|
89
|
+
list(
|
|
90
|
+
filter(
|
|
91
|
+
lambda holdout: holdout.get_is_gateway_service_required(), holdouts
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
) > 0
|
|
95
|
+
is_segmentation_gateway_required = (
|
|
96
|
+
feature.get_is_gateway_service_required()
|
|
97
|
+
or is_gateway_service_required_for_holdouts
|
|
98
|
+
)
|
|
99
|
+
# Call get-user-details only when gateway_service is configured and
|
|
100
|
+
# segmentation (feature or holdout) requires gateway data.
|
|
101
|
+
# proxy_url alone must never trigger a gateway call.
|
|
86
102
|
should_call_gateway_service = (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
SettingsManager.get_instance().is_gateway_service_provided
|
|
104
|
+
and is_segmentation_gateway_required
|
|
105
|
+
)
|
|
106
|
+
|
|
92
107
|
if should_call_gateway_service and context.get_vwo() is None:
|
|
93
108
|
query_params = {}
|
|
94
109
|
if not context.get_user_agent() and not context.get_ip_address():
|
|
@@ -115,4 +130,11 @@ class SegmentationManager:
|
|
|
115
130
|
:param properties: The properties to validate against.
|
|
116
131
|
:return: True if segmentation is valid, otherwise False.
|
|
117
132
|
"""
|
|
133
|
+
# if the dsl contains a campaign variation node, then we need to check if the campaign variation is valid
|
|
134
|
+
if dsl_contains_campaign_variation_node(dsl):
|
|
135
|
+
evaluation_context = getattr(self.evaluator, "context", None)
|
|
136
|
+
if evaluation_context is None or is_web_testing_campaigns_absent_or_null(
|
|
137
|
+
evaluation_context
|
|
138
|
+
):
|
|
139
|
+
return False
|
|
118
140
|
return self.evaluator.is_segmentation_valid(dsl, properties)
|
|
@@ -68,6 +68,10 @@ class SegmentEvaluator:
|
|
|
68
68
|
return SegmentOperandEvaluator().evaluate_string_operand_dsl(
|
|
69
69
|
sub_dsl, self.context, SegmentOperatorValueEnum.OS_VERSION.value
|
|
70
70
|
)
|
|
71
|
+
elif operator == SegmentOperatorValueEnum.WEB_CAMPAIGN_VARIATION.value:
|
|
72
|
+
return SegmentOperandEvaluator().evaluate_campaign_variation_dsl(
|
|
73
|
+
sub_dsl, self.context
|
|
74
|
+
)
|
|
71
75
|
else:
|
|
72
76
|
return False
|
|
73
77
|
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
import re
|
|
17
17
|
from typing import Dict, Any
|
|
18
18
|
from ..utils.segment_util import get_key_value, match_with_regex
|
|
19
|
+
from ..utils.web_testing_segment_util import (
|
|
20
|
+
coerce_campaign_variation_operand_to_str,
|
|
21
|
+
parse_web_testing_campaigns_from_context,
|
|
22
|
+
evaluate_web_testing_campaign_variation,
|
|
23
|
+
)
|
|
19
24
|
from ..enums.segment_operand_regex_enum import SegmentOperandRegexEnum
|
|
20
25
|
from ..enums.segment_operand_value_enum import SegmentOperandValueEnum
|
|
21
26
|
from ..enums.segment_operator_value_enum import SegmentOperatorValueEnum
|
|
@@ -84,6 +89,25 @@ class SegmentOperandEvaluator:
|
|
|
84
89
|
operand_type, processed_values["operand_value"], tag_value
|
|
85
90
|
)
|
|
86
91
|
|
|
92
|
+
def evaluate_campaign_variation_dsl(
|
|
93
|
+
self, dsl_operand_value: Any, context: ContextModel
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Evaluates a campaignVariation DSL operand against the user's web testing campaign assignments.
|
|
97
|
+
|
|
98
|
+
:param dsl_operand_value: The raw operand value from the DSL (e.g. "123_1", "!123", 100).
|
|
99
|
+
:param context: The context object containing platform_variables.web_testing_campaigns.
|
|
100
|
+
:return: True if the segment condition is satisfied, otherwise False.
|
|
101
|
+
"""
|
|
102
|
+
operand_str = coerce_campaign_variation_operand_to_str(dsl_operand_value)
|
|
103
|
+
if operand_str is None:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
operand_str = operand_str.strip()
|
|
107
|
+
campaigns_map = parse_web_testing_campaigns_from_context(context)
|
|
108
|
+
result, _invalid = evaluate_web_testing_campaign_variation(operand_str, campaigns_map)
|
|
109
|
+
return result
|
|
110
|
+
|
|
87
111
|
def evaluate_user_dsl(self, dsl_operand_value, properties):
|
|
88
112
|
"""
|
|
89
113
|
Evaluates a user DSL expression to check if a user ID is in a specified List.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import math
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any, Dict, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from ....models.user.context_model import ContextModel
|
|
21
|
+
from ....utils.data_type_util import (
|
|
22
|
+
get_type,
|
|
23
|
+
is_array,
|
|
24
|
+
is_boolean,
|
|
25
|
+
is_defined,
|
|
26
|
+
is_null,
|
|
27
|
+
is_number,
|
|
28
|
+
is_object,
|
|
29
|
+
is_string,
|
|
30
|
+
)
|
|
31
|
+
from ...logger.core.log_manager import LogManager
|
|
32
|
+
from ....enums.api_enum import ApiEnum
|
|
33
|
+
from ..enums.segment_operator_value_enum import SegmentOperatorValueEnum
|
|
34
|
+
|
|
35
|
+
_WEB_TESTING_JSON_STRING_KEY_REGEX = re.compile(r'"([^"\\]*)"\s*:')
|
|
36
|
+
|
|
37
|
+
def coerce_campaign_variation_operand_to_str(operand: Any) -> Optional[str]:
|
|
38
|
+
"""
|
|
39
|
+
Coerces a campaignVariation operand to a string when the value is a valid string or finite number.
|
|
40
|
+
|
|
41
|
+
:param operand: Raw campaignVariation operand from the segmentation DSL.
|
|
42
|
+
:return: Coerced operand string, or None if the type cannot be coerced.
|
|
43
|
+
"""
|
|
44
|
+
if is_null(operand):
|
|
45
|
+
return None
|
|
46
|
+
if is_boolean(operand):
|
|
47
|
+
return None
|
|
48
|
+
if is_string(operand):
|
|
49
|
+
return operand
|
|
50
|
+
if is_number(operand):
|
|
51
|
+
# bool excluded above; avoids treating True as 1 via int subclassing.
|
|
52
|
+
if type(operand) is float:
|
|
53
|
+
if not math.isfinite(operand):
|
|
54
|
+
return None
|
|
55
|
+
if operand.is_integer():
|
|
56
|
+
return str(int(operand))
|
|
57
|
+
return str(operand)
|
|
58
|
+
return str(operand)
|
|
59
|
+
if is_array(operand) or is_object(operand):
|
|
60
|
+
return None
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def normalize_web_testing_campaigns_map(web_testing_source: Dict) -> Dict[str, str]:
|
|
65
|
+
"""
|
|
66
|
+
Normalizes a web_testing_campaigns map so campaign and variation IDs are non-empty string keys and values.
|
|
67
|
+
|
|
68
|
+
:param web_testing_source: Campaign ID to variation ID map from context or parsed JSON.
|
|
69
|
+
:return: Map with string campaign keys and string variation values.
|
|
70
|
+
"""
|
|
71
|
+
normalized_campaigns_map: Dict[str, str] = {}
|
|
72
|
+
# iterate over the web testing source and normalize the campaign ids and variation ids
|
|
73
|
+
for campaign_key in web_testing_source:
|
|
74
|
+
variation_raw = web_testing_source[campaign_key]
|
|
75
|
+
# convert the campaign key to a string
|
|
76
|
+
campaign_key_as_str = str(campaign_key)
|
|
77
|
+
# if the variation raw is not None and the campaign key as string is not empty, then add the campaign key and variation to the normalized campaigns map
|
|
78
|
+
if is_defined(variation_raw) and len(campaign_key_as_str) > 0:
|
|
79
|
+
normalized_campaigns_map[campaign_key_as_str] = str(variation_raw)
|
|
80
|
+
return normalized_campaigns_map
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _log_first_duplicate_json_object_key_scan(
|
|
84
|
+
web_testing_campaigns_json: str,
|
|
85
|
+
context: ContextModel,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Scans a web_testing_campaigns JSON string for duplicate object keys and logs the first duplicate found.
|
|
89
|
+
|
|
90
|
+
:param web_testing_campaigns_json: Trimmed JSON string of web_testing_campaigns.
|
|
91
|
+
:param context: User context used for debug metadata in the error log.
|
|
92
|
+
:return: None; parsing continues after logging (last duplicate value wins).
|
|
93
|
+
"""
|
|
94
|
+
# set to track the keys seen so far
|
|
95
|
+
keys_seen_so_far = set()
|
|
96
|
+
# iterate over the web testing campaigns json and log the first duplicate json object key
|
|
97
|
+
for key_regex_match in _WEB_TESTING_JSON_STRING_KEY_REGEX.finditer(
|
|
98
|
+
web_testing_campaigns_json
|
|
99
|
+
):
|
|
100
|
+
object_key_name = key_regex_match.group(1)
|
|
101
|
+
# if the object key name is already in the keys seen so far, then log an error
|
|
102
|
+
if object_key_name in keys_seen_so_far:
|
|
103
|
+
LogManager.get_instance().error_log(
|
|
104
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_DUPLICATE_KEY",
|
|
105
|
+
{"duplicateKey": object_key_name},
|
|
106
|
+
{
|
|
107
|
+
"an": ApiEnum.GET_FLAG.value,
|
|
108
|
+
"uuid": context.get_vwo_uuid(),
|
|
109
|
+
"sId": context.get_session_id(),
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
# add the object key name to the keys seen so far
|
|
114
|
+
keys_seen_so_far.add(object_key_name)
|
|
115
|
+
|
|
116
|
+
def is_web_testing_campaigns_absent_or_null(context: ContextModel) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Returns whether platform_variables.web_testing_campaigns is missing or explicitly null.
|
|
119
|
+
|
|
120
|
+
:param context: User context that may carry platform_variables.
|
|
121
|
+
:return: True if web_testing_campaigns is absent or null; otherwise False.
|
|
122
|
+
"""
|
|
123
|
+
# get the platform variables
|
|
124
|
+
platform_variables = context.get_platform_variables()
|
|
125
|
+
# if the platform variables are not present, then return True
|
|
126
|
+
if is_null(platform_variables) or not platform_variables:
|
|
127
|
+
return True
|
|
128
|
+
if not is_object(platform_variables):
|
|
129
|
+
return True
|
|
130
|
+
if "web_testing_campaigns" not in platform_variables:
|
|
131
|
+
return True
|
|
132
|
+
# get the web testing campaigns input
|
|
133
|
+
web_testing_campaigns_input = platform_variables.get("web_testing_campaigns")
|
|
134
|
+
# if the web testing campaigns input is None, then return True
|
|
135
|
+
if is_null(web_testing_campaigns_input):
|
|
136
|
+
return True
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def dsl_contains_campaign_variation_node(segmentation_dsl: Any) -> bool:
|
|
140
|
+
"""
|
|
141
|
+
Recursively checks whether a segmentation DSL tree contains a campaignVariation operator node.
|
|
142
|
+
|
|
143
|
+
:param segmentation_dsl: Segmentation DSL object to walk (and/or/not/campaignVariation nodes).
|
|
144
|
+
:return: True if any campaignVariation node exists in the tree; otherwise False.
|
|
145
|
+
"""
|
|
146
|
+
# if the segmentation dsl is None or not a dictionary, then return False
|
|
147
|
+
if is_null(segmentation_dsl) or not is_object(segmentation_dsl):
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
# iterate over the segmentation dsl and check if it contains a campaign variation node
|
|
151
|
+
for dsl_property_name in segmentation_dsl:
|
|
152
|
+
# if the dsl property name is the campaign variation node, then return True
|
|
153
|
+
if dsl_property_name == SegmentOperatorValueEnum.WEB_CAMPAIGN_VARIATION.value:
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
nested_value = segmentation_dsl[dsl_property_name]
|
|
157
|
+
if is_array(nested_value):
|
|
158
|
+
for list_item in nested_value:
|
|
159
|
+
if dsl_contains_campaign_variation_node(list_item):
|
|
160
|
+
return True
|
|
161
|
+
elif is_object(nested_value):
|
|
162
|
+
if dsl_contains_campaign_variation_node(nested_value):
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def parse_web_testing_campaigns_from_context(
|
|
169
|
+
context: ContextModel,
|
|
170
|
+
) -> Optional[Dict[str, str]]:
|
|
171
|
+
"""
|
|
172
|
+
Parses context.platform_variables.web_testing_campaigns from a dict or JSON string into a normalized map.
|
|
173
|
+
|
|
174
|
+
:param context: User context containing platform_variables.web_testing_campaigns.
|
|
175
|
+
:return: Normalized campaign-to-variation map, or None if missing, invalid, or unparseable.
|
|
176
|
+
"""
|
|
177
|
+
platform_variables = context.get_platform_variables()
|
|
178
|
+
if is_null(platform_variables) or not platform_variables:
|
|
179
|
+
return None
|
|
180
|
+
if not is_object(platform_variables):
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
web_testing_campaigns_raw = platform_variables.get("web_testing_campaigns")
|
|
184
|
+
if is_null(web_testing_campaigns_raw):
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
# if the web testing campaigns raw is an object map, normalize it.
|
|
188
|
+
if is_object(web_testing_campaigns_raw):
|
|
189
|
+
return normalize_web_testing_campaigns_map(web_testing_campaigns_raw)
|
|
190
|
+
|
|
191
|
+
# if the web testing campaigns raw is a string, then parse the web testing campaigns
|
|
192
|
+
if is_string(web_testing_campaigns_raw):
|
|
193
|
+
trimmed_json_input = web_testing_campaigns_raw.strip()
|
|
194
|
+
if trimmed_json_input == "":
|
|
195
|
+
return None
|
|
196
|
+
_log_first_duplicate_json_object_key_scan(trimmed_json_input, context)
|
|
197
|
+
try:
|
|
198
|
+
# parse the web testing campaigns as a json object
|
|
199
|
+
parsed_json_object = json.loads(trimmed_json_input)
|
|
200
|
+
# if the parsed json object is an object map, normalize it
|
|
201
|
+
if is_object(parsed_json_object):
|
|
202
|
+
return normalize_web_testing_campaigns_map(parsed_json_object)
|
|
203
|
+
# if the parsed json object is not an object map, then log an error
|
|
204
|
+
LogManager.get_instance().error_log(
|
|
205
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_JSON",
|
|
206
|
+
{},
|
|
207
|
+
{
|
|
208
|
+
"an": ApiEnum.GET_FLAG.value,
|
|
209
|
+
"uuid": context.get_vwo_uuid(),
|
|
210
|
+
"sId": context.get_session_id(),
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
except json.JSONDecodeError:
|
|
214
|
+
# if the json decode error is raised, then log an error
|
|
215
|
+
LogManager.get_instance().error_log(
|
|
216
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_JSON",
|
|
217
|
+
{},
|
|
218
|
+
{
|
|
219
|
+
"an": ApiEnum.GET_FLAG.value,
|
|
220
|
+
"uuid": context.get_vwo_uuid(),
|
|
221
|
+
"sId": context.get_session_id(),
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
# if the web testing campaigns raw is not an object or a string, then log an error
|
|
227
|
+
value_kind_label = get_type(web_testing_campaigns_raw)
|
|
228
|
+
LogManager.get_instance().error_log(
|
|
229
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_TYPE",
|
|
230
|
+
{"kind": value_kind_label},
|
|
231
|
+
{
|
|
232
|
+
"an": ApiEnum.GET_FLAG.value,
|
|
233
|
+
"uuid": context.get_vwo_uuid(),
|
|
234
|
+
"sId": context.get_session_id(),
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
def evaluate_web_testing_campaign_variation(
|
|
240
|
+
operand_encoding: str,
|
|
241
|
+
campaigns_map: Optional[Dict[str, str]],
|
|
242
|
+
) -> Tuple[bool, bool]:
|
|
243
|
+
"""
|
|
244
|
+
Evaluates a campaignVariation operand encoding against the user's web testing campaign assignments.
|
|
245
|
+
|
|
246
|
+
:param operand_encoding: Encoded operand (e.g. "123", "123_2", "123_!2", "!123").
|
|
247
|
+
:param campaigns_map: Normalized map of campaign ID to assigned variation ID, or None.
|
|
248
|
+
:return: Tuple of (segment matched, operand encoding invalid).
|
|
249
|
+
"""
|
|
250
|
+
if not is_string(operand_encoding):
|
|
251
|
+
return (False, True)
|
|
252
|
+
|
|
253
|
+
assignments_by_campaign_id = (
|
|
254
|
+
campaigns_map if is_defined(campaigns_map) else {}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# e.g. "!123" — not in campaign 123 (no assignment for that campaign id).
|
|
258
|
+
not_in_campaign_regex_match = re.match(r"^!(\d+)$", operand_encoding)
|
|
259
|
+
if not_in_campaign_regex_match:
|
|
260
|
+
campaign_id_digits = not_in_campaign_regex_match.group(1)
|
|
261
|
+
exists_in_assignments_map = campaign_id_digits in assignments_by_campaign_id
|
|
262
|
+
return (not exists_in_assignments_map, False)
|
|
263
|
+
|
|
264
|
+
# e.g. "123_!3" — in campaign 123 but assigned variation is not 3.
|
|
265
|
+
in_campaign_but_not_variation_regex_match = re.match(r"^(\d+)_!(\d+)$", operand_encoding)
|
|
266
|
+
# if the operand encoding is a campaign and variation id, then check if the user is in the campaign but not the variation
|
|
267
|
+
if in_campaign_but_not_variation_regex_match:
|
|
268
|
+
campaign_id_digits = in_campaign_but_not_variation_regex_match.group(1)
|
|
269
|
+
variation_id_digits = in_campaign_but_not_variation_regex_match.group(2)
|
|
270
|
+
# if the campaign id is not in the assignments by campaign id, then return False and False
|
|
271
|
+
if campaign_id_digits not in assignments_by_campaign_id:
|
|
272
|
+
return (False, False)
|
|
273
|
+
assigned_variation_id = assignments_by_campaign_id[campaign_id_digits]
|
|
274
|
+
return (assigned_variation_id != variation_id_digits, False)
|
|
275
|
+
|
|
276
|
+
# e.g. "123_3" — in campaign 123 with variation exactly 3.
|
|
277
|
+
campaign_and_variation_regex_match = re.match(r"^(\d+)_(\d+)$", operand_encoding)
|
|
278
|
+
if campaign_and_variation_regex_match:
|
|
279
|
+
# if the campaign id is not in the assignments by campaign id, then return False
|
|
280
|
+
campaign_id_digits = campaign_and_variation_regex_match.group(1)
|
|
281
|
+
# get the variation id digits
|
|
282
|
+
variation_id_digits = campaign_and_variation_regex_match.group(2)
|
|
283
|
+
if campaign_id_digits not in assignments_by_campaign_id:
|
|
284
|
+
return (False, False)
|
|
285
|
+
assigned_variation_id = assignments_by_campaign_id[campaign_id_digits]
|
|
286
|
+
return (assigned_variation_id == variation_id_digits, False)
|
|
287
|
+
|
|
288
|
+
# e.g. "123" — in campaign 123 (any assigned variation counts).
|
|
289
|
+
campaign_id_only_regex_match = re.match(r"^(\d+)$", operand_encoding)
|
|
290
|
+
if campaign_id_only_regex_match:
|
|
291
|
+
campaign_id_digits = campaign_id_only_regex_match.group(1)
|
|
292
|
+
user_in_campaign = campaign_id_digits in assignments_by_campaign_id
|
|
293
|
+
return (user_in_campaign, False)
|
|
294
|
+
|
|
295
|
+
return (False, True)
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
"UPDATING_CLIENT_INSTANCE_FAILED_WHEN_WEBHOOK_TRIGGERED": "Failed to fetch settings. {brand} client instance couldn't be updated. API:{apiName} called having isViaWebhook:{isViaWebhook}. Error: {err}",
|
|
13
13
|
|
|
14
14
|
"EXECUTION_FAILED": "API - {apiName} failed to execute. Error:{err}",
|
|
15
|
+
"INTEGRATIONS_CALLBACK_FAILED": "Error while executing integrations_callback: Error: {err}",
|
|
15
16
|
"INVALID_PARAM": "Key:{key} passed to API:{apiName} is not of valid type. Got type:{type}, should be:{correctType}",
|
|
16
17
|
"INVALID_CONTEXT_PASSED": "Context should be of type:object and must contain a mandatory key: id, which is User ID",
|
|
18
|
+
"INVALID_CONTEXT_USER_ID": "user_id under context must be a string value",
|
|
17
19
|
"INVALID_BUCKETING_SEED": "Invalid bucketingSeed passed, since it can only be a valid string. Removing it from context so that userId is used for decisioning.",
|
|
18
20
|
|
|
19
21
|
"FEATURE_NOT_FOUND": "Feature not found for the key:{featureKey}",
|
|
20
22
|
"FEATURE_NOT_FOUND_WITH_ID": "Feature not found for the id:{featureId}",
|
|
21
|
-
"EVENT_NOT_FOUND": "Event:{eventName} not found in any of the features' metrics",
|
|
23
|
+
"EVENT_NOT_FOUND": "Event:{eventName} not found in any of the features' metrics, please ensure event API name is being used correctly",
|
|
22
24
|
|
|
23
25
|
"ERROR_READING_STORED_DATA_IN_STORAGE": "Error reading data from storage. Error:{err}",
|
|
24
26
|
"ERROR_STORING_DATA_IN_STORAGE": "Key:{featureKey} is not valid. Unable to store data into storage",
|
|
@@ -49,5 +51,12 @@
|
|
|
49
51
|
|
|
50
52
|
"ERROR_SENDING_DEBUG_EVENT": "Error occurred while sending debugger event. Error:{err}",
|
|
51
53
|
"ERROR_SETTING_ALIAS": "Error while setting alias on gateway service. Error: {err}",
|
|
52
|
-
"ERROR_GETTING_ALIAS": "Error while getting alias from gateway service. Error: {err}"
|
|
54
|
+
"ERROR_GETTING_ALIAS": "Error while getting alias from gateway service. Error: {err}",
|
|
55
|
+
|
|
56
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_JSON": "webTestingCampaigns must be a JSON object (not an array)",
|
|
57
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_DUPLICATE_KEY": "webTestingCampaigns JSON string contains duplicate object key: {duplicateKey}, only last occurrence is considered",
|
|
58
|
+
"INVALID_WEB_TESTING_CAMPAIGNS_TYPE": "webTestingCampaigns has invalid type: {kind}",
|
|
59
|
+
"INVALID_WEB_TESTING_CAMPAIGN_VARIATION_OPERAND_TYPE": "campaignVariation operand must be a string or finite number, got type: {type}",
|
|
60
|
+
"INVALID_WEB_TESTING_CAMPAIGN_VARIATION_OPERAND_EMPTY": "campaignVariation operand is empty",
|
|
61
|
+
"INVALID_WEB_TESTING_CAMPAIGN_VARIATION_OPERAND_FORMAT": "Invalid campaignVariation operand encoding: {operandEncoding}"
|
|
53
62
|
}
|
{wingify_fme_python_sdk-1.55.0 → wingify_fme_python_sdk-1.61.0}/wingify/services/hooks_manager.py
RENAMED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
from typing import Callable, Dict, Any, Optional
|
|
17
|
+
from ..packages.logger.core.log_manager import LogManager
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class HooksManager:
|
|
@@ -36,7 +37,15 @@ class HooksManager:
|
|
|
36
37
|
:param properties: A dictionary of properties to pass to the callback.
|
|
37
38
|
"""
|
|
38
39
|
if self.is_callback_function and self.callback:
|
|
39
|
-
|
|
40
|
+
try:
|
|
41
|
+
self.callback(properties)
|
|
42
|
+
except Exception as err:
|
|
43
|
+
LogManager.get_instance().error_log(
|
|
44
|
+
"INTEGRATIONS_CALLBACK_FAILED",
|
|
45
|
+
data={"err": str(err)},
|
|
46
|
+
debug_data={"an": properties.get("api")},
|
|
47
|
+
should_send_log_to_vwo=False,
|
|
48
|
+
)
|
|
40
49
|
|
|
41
50
|
def set(self, properties: Dict[str, Any]) -> None:
|
|
42
51
|
"""
|