wingify-fme-python-sdk 1.60.0__tar.gz → 1.62.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.60.0/wingify_fme_python_sdk.egg-info → wingify_fme_python_sdk-1.62.0}/PKG-INFO +1 -1
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/setup.py +1 -1
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/get_flag_api.py +15 -5
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/constants/Constants.py +4 -1
- wingify_fme_python_sdk-1.62.0/wingify/constants/sampled_debug_error_template_keys.py +34 -0
- wingify_fme_python_sdk-1.62.0/wingify/models/settings/internal_event_sampling_settings_model.py +53 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/settings/settings_model.py +19 -2
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/core/segmentation_manager.py +18 -7
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/error-messages.json +3 -1
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/hooks_manager.py +10 -1
- wingify_fme_python_sdk-1.62.0/wingify/services/internal_events_sampling_service.py +62 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/campaign_util.py +81 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/debugger_service_util.py +16 -0
- wingify_fme_python_sdk-1.62.0/wingify/utils/internal_events_sampling_util.py +106 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/meg_util.py +9 -5
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/rule_evaluation_util.py +2 -1
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify.py +10 -9
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify_client.py +24 -20
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0/wingify_fme_python_sdk.egg-info}/PKG-INFO +1 -1
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/SOURCES.txt +4 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/LICENSE +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/MANIFEST.in +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/NOTICE +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/README.md +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/requirements-dev.txt +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/requirements.txt +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/setup.cfg +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo_builder.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo_client.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/set_attribute_api.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/track_api.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/constants/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/decorators/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/decorators/storage_decorator.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/api_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/campaign_type_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/debug_category_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/event_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/headers_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/hooks_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/status_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/storage_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/url_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/Alias.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/AliasSetResponse.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/batch_event_data.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/campaign_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/feature_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/holdout_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/impact_campaign_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/metric_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/rule_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/variable_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/variation_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/gateway_service_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_group_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_metric_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_variation_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/empty_object_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/feature_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/group_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/holdout_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/rule_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/settings_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/variable_schema.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/settings/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/storage/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/storage/storage_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/context_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/context_vwo_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/get_flag.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/vwo_options_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/wingify_options_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/decision_maker/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/decision_maker/decision_maker.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/log_manager.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/log_transport_manager.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/enums/log_level_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/enums/log_level_number_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/log_message_builder.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/logger.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/transports/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/transports/console_transport.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/client/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/client/network_client.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/handlers/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/handlers/request_handler.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/manager/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/manager/network_manager.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/global_request_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/request_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/response_model.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/core/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/enums/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/evaluators/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/utils/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/utils/segment_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/utils/web_testing_segment_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/connector.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/storage.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/debug-messages.json +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/info-message.json +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/trace-messages.json +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/warn-messages.json +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/batch_event_queue.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/campaign_decision_service.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/settings_manager.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/storage_service.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/url_service.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/__init__.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/aliasing_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/brand_context.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/brand_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/data_type_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/decision_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/event_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/function_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/gateway_service_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/holdout_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/impression_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/log_message_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/model_utils.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/network_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/settings_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/usage_stats_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/uuid_util.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/vwo_client.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify_builder.py +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/dependency_links.txt +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/requires.txt +0 -0
- {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/top_level.txt +0 -0
|
@@ -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.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/constants/Constants.py
RENAMED
|
@@ -17,7 +17,7 @@ class Constants:
|
|
|
17
17
|
# TODO: read from setup.py
|
|
18
18
|
sdk_meta = {
|
|
19
19
|
"name": "vwo-fme-python-sdk",
|
|
20
|
-
"version": "1.
|
|
20
|
+
"version": "1.62.0",
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
SDK_VERSION = sdk_meta["version"]
|
|
@@ -87,3 +87,6 @@ class Constants:
|
|
|
87
87
|
|
|
88
88
|
VARIATION_IS_PART_OF_HOLDOUT = 1
|
|
89
89
|
VARIATION_NOT_PART_OF_HOLDOUT = 2
|
|
90
|
+
|
|
91
|
+
INTERNAL_EVENTS_DEFAULT_SAMPLING_PERCENT_SERVER = 10.0
|
|
92
|
+
INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING = False
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
class SampledDebugErrorTemplateKeys:
|
|
16
|
+
"""
|
|
17
|
+
Debug error template keys (`msg_t`) that are subject to sampling.
|
|
18
|
+
Keys not listed here always send and bypass sampling.
|
|
19
|
+
"""
|
|
20
|
+
SAMPLED_KEYS = {
|
|
21
|
+
"EVENT_NOT_FOUND",
|
|
22
|
+
"FEATURE_NOT_FOUND",
|
|
23
|
+
"FEATURE_NOT_FOUND_WITH_ID"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def contains(message_template_key: str) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
Checks whether a debug error template key should be sampled before sending.
|
|
30
|
+
|
|
31
|
+
:param message_template_key: The `msg_t` value from the debug event
|
|
32
|
+
:return: `True` if the key is in the sampled set; `False` otherwise
|
|
33
|
+
"""
|
|
34
|
+
return message_template_key is not None and message_template_key in SampledDebugErrorTemplateKeys.SAMPLED_KEYS
|
wingify_fme_python_sdk-1.62.0/wingify/models/settings/internal_event_sampling_settings_model.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
from typing import Dict, Any, Optional
|
|
16
|
+
|
|
17
|
+
class PlatformSamplingRates:
|
|
18
|
+
"""
|
|
19
|
+
Holds the sampling percentage for a specific platform (e.g., server).
|
|
20
|
+
Maps to the per-platform object inside `sampling.usage` or `sampling.debug`.
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, data: Dict[str, Any]):
|
|
23
|
+
self._server = data.get("server")
|
|
24
|
+
|
|
25
|
+
def get_server(self) -> Optional[float]:
|
|
26
|
+
return self._server
|
|
27
|
+
|
|
28
|
+
class EventCategorySamplingRates:
|
|
29
|
+
"""
|
|
30
|
+
Groups sampling rates by event category: `usage` and `debug`.
|
|
31
|
+
Maps to the top-level `sampling` object in the DaCDN settings.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, data: Dict[str, Any]):
|
|
34
|
+
self._usage = PlatformSamplingRates(data.get("usage", {})) if data.get("usage") else None
|
|
35
|
+
self._debug = PlatformSamplingRates(data.get("debug", {})) if data.get("debug") else None
|
|
36
|
+
|
|
37
|
+
def get_usage(self) -> Optional[PlatformSamplingRates]:
|
|
38
|
+
return self._usage
|
|
39
|
+
|
|
40
|
+
def get_debug(self) -> Optional[PlatformSamplingRates]:
|
|
41
|
+
return self._debug
|
|
42
|
+
|
|
43
|
+
class AlwaysApplySampling:
|
|
44
|
+
"""
|
|
45
|
+
Controls whether sampling is enforced before sending an event.
|
|
46
|
+
Maps to `alwaysApplySampling` in DaCDN settings.
|
|
47
|
+
When `server` is False (the default), usage-stats and sampled debug events bypass sampling and are always sent.
|
|
48
|
+
"""
|
|
49
|
+
def __init__(self, data: Dict[str, Any]):
|
|
50
|
+
self._server = data.get("server")
|
|
51
|
+
|
|
52
|
+
def get_server(self) -> Optional[bool]:
|
|
53
|
+
return self._server
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
from typing import List, Dict, Optional
|
|
16
|
+
from typing import List, Dict, Optional, Any
|
|
17
17
|
from ..campaign.campaign_model import CampaignModel
|
|
18
18
|
from ..campaign.feature_model import FeatureModel
|
|
19
19
|
from ...utils.model_utils import _parse_campaign, _parse_feature
|
|
20
20
|
import json
|
|
21
21
|
from ...constants.Constants import Constants
|
|
22
22
|
from ..campaign.holdout_model import HoldoutModel
|
|
23
|
+
from .internal_event_sampling_settings_model import AlwaysApplySampling, EventCategorySamplingRates
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class SettingsModel:
|
|
@@ -37,6 +38,10 @@ class SettingsModel:
|
|
|
37
38
|
self._is_web_connectivity_enabled = data.get("isWebConnectivityEnabled", True)
|
|
38
39
|
self._holdouts = [HoldoutModel(h) for h in data.get("holdouts", [])]
|
|
39
40
|
self._is_tracking_usage_enabled = data.get("isMAU", False)
|
|
41
|
+
self._sdk_meta_info = data.get("sdkMetaInfo", {})
|
|
42
|
+
self._usage_stats_account_id = data.get("usageStatsAccountId", None)
|
|
43
|
+
self._always_apply_sampling = AlwaysApplySampling(data.get("alwaysApplySampling", {})) if data.get("alwaysApplySampling") is not None else None
|
|
44
|
+
self._sampling = EventCategorySamplingRates(data.get("sampling", {})) if data.get("sampling") is not None else None
|
|
40
45
|
|
|
41
46
|
# Getter methods for accessing private attributes
|
|
42
47
|
def get_features(self) -> List[FeatureModel]:
|
|
@@ -107,4 +112,16 @@ class SettingsModel:
|
|
|
107
112
|
return self._is_tracking_usage_enabled
|
|
108
113
|
|
|
109
114
|
def set_is_tracking_usage_enabled(self, value: bool):
|
|
110
|
-
self._is_tracking_usage_enabled = value
|
|
115
|
+
self._is_tracking_usage_enabled = value
|
|
116
|
+
|
|
117
|
+
def get_sdk_meta_info(self) -> Dict[str, Any]:
|
|
118
|
+
return self._sdk_meta_info
|
|
119
|
+
|
|
120
|
+
def get_usage_stats_account_id(self) -> Optional[int]:
|
|
121
|
+
return self._usage_stats_account_id
|
|
122
|
+
|
|
123
|
+
def get_always_apply_sampling(self) -> Optional[AlwaysApplySampling]:
|
|
124
|
+
return self._always_apply_sampling
|
|
125
|
+
|
|
126
|
+
def get_sampling(self) -> Optional[EventCategorySamplingRates]:
|
|
127
|
+
return self._sampling
|
|
@@ -85,14 +85,25 @@ class SegmentationManager:
|
|
|
85
85
|
|
|
86
86
|
# Check if any holdout requires gateway service
|
|
87
87
|
holdouts = settings.get_holdouts() or []
|
|
88
|
-
is_gateway_service_required_for_holdouts = len(
|
|
89
|
-
|
|
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.
|
|
90
102
|
should_call_gateway_service = (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
SettingsManager.get_instance().is_gateway_service_provided
|
|
104
|
+
and is_segmentation_gateway_required
|
|
105
|
+
)
|
|
106
|
+
|
|
96
107
|
if should_call_gateway_service and context.get_vwo() is None:
|
|
97
108
|
query_params = {}
|
|
98
109
|
if not context.get_user_agent() and not context.get_ip_address():
|
|
@@ -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",
|
{wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.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
|
"""
|
|
@@ -0,0 +1,62 @@
|
|
|
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 random
|
|
16
|
+
from typing import Callable, Optional
|
|
17
|
+
from ..models.settings.settings_model import SettingsModel
|
|
18
|
+
from ..utils.internal_events_sampling_util import (
|
|
19
|
+
should_apply_sampling,
|
|
20
|
+
get_usage_stats_sampling_percent,
|
|
21
|
+
get_debug_event_sampling_percent,
|
|
22
|
+
passes_sampling_percent,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class InternalEventsSamplingService:
|
|
26
|
+
"""
|
|
27
|
+
Applies sampling rules for internal SDK events:
|
|
28
|
+
`vwo_sdkUsageStats` and sampled `vwo_sdkDebug`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, random_value_provider: Optional[Callable[[], float]] = None):
|
|
32
|
+
"""
|
|
33
|
+
Creates a sampling service.
|
|
34
|
+
:param random_value_provider: Optional supplier returning a value in [0, 1) for sampling checks; overridable in tests.
|
|
35
|
+
"""
|
|
36
|
+
self._random_value_provider = random_value_provider if random_value_provider is not None else random.random
|
|
37
|
+
|
|
38
|
+
def should_send_usage_stats_event(self, settings: Optional[SettingsModel]) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Determines whether the usage-stats event should be sent.
|
|
41
|
+
On server, always sends unless `alwaysApplySampling.server` is enabled.
|
|
42
|
+
:param settings: Parsed settings from the server
|
|
43
|
+
:return: `True` when the usage-stats event should be sent
|
|
44
|
+
"""
|
|
45
|
+
if not should_apply_sampling(settings):
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
usage_stats_sampling_percent = get_usage_stats_sampling_percent(settings)
|
|
49
|
+
return passes_sampling_percent(usage_stats_sampling_percent, self._random_value_provider())
|
|
50
|
+
|
|
51
|
+
def should_send_sampled_debug_event(self, settings: Optional[SettingsModel]) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Determines whether a sampled debug event should be sent.
|
|
54
|
+
Only called for `msg_t` keys in the sampled set; always-send keys bypass this.
|
|
55
|
+
:param settings: Parsed settings from the server
|
|
56
|
+
:return: `True` when the sampled debug event should be sent
|
|
57
|
+
"""
|
|
58
|
+
if not should_apply_sampling(settings):
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
debug_sampling_percent = get_debug_event_sampling_percent(settings)
|
|
62
|
+
return passes_sampling_percent(debug_sampling_percent, self._random_value_provider())
|
{wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/campaign_util.py
RENAMED
|
@@ -27,6 +27,69 @@ import math
|
|
|
27
27
|
from ..models.user.context_model import ContextModel
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _resolve_feature_rule_key(
|
|
31
|
+
campaign: CampaignModel, feature_key: Optional[str] = None
|
|
32
|
+
) -> Optional[str]:
|
|
33
|
+
"""
|
|
34
|
+
Resolves ``{featureKey}_{ruleKey}`` when rules share a parent campaign key.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
campaign (CampaignModel): The campaign containing the rule key.
|
|
38
|
+
feature_key (Optional[str]): Feature key; inferred from campaign key if omitted.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Optional[str]: Resolved rule key, or ``None`` if rule key is missing.
|
|
42
|
+
"""
|
|
43
|
+
if not campaign.get_rule_key():
|
|
44
|
+
return None
|
|
45
|
+
resolved_feature_key = feature_key
|
|
46
|
+
if not resolved_feature_key and campaign.get_key():
|
|
47
|
+
resolved_feature_key = campaign.get_key().rsplit("_", 1)[0]
|
|
48
|
+
if resolved_feature_key:
|
|
49
|
+
return f"{resolved_feature_key}_{campaign.get_rule_key()}"
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_experiment_key(
|
|
54
|
+
campaign: CampaignModel, feature_key: Optional[str] = None
|
|
55
|
+
) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Returns the experiment key for integrations callbacks and storage.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
campaign (CampaignModel): The campaign to resolve the key for.
|
|
61
|
+
feature_key (Optional[str]): Feature key for personalize rules.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: ``{featureKey}_{ruleKey}`` for personalize rules; otherwise campaign key.
|
|
65
|
+
"""
|
|
66
|
+
if campaign.get_type() == CampaignTypeEnum.PERSONALIZE.value:
|
|
67
|
+
rule_key = _resolve_feature_rule_key(campaign, feature_key)
|
|
68
|
+
if rule_key:
|
|
69
|
+
return rule_key
|
|
70
|
+
return campaign.get_key()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_rollout_key(
|
|
74
|
+
campaign: CampaignModel, feature_key: Optional[str] = None
|
|
75
|
+
) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Returns the rollout key for integrations callbacks and storage.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
campaign (CampaignModel): The campaign to resolve the key for.
|
|
81
|
+
feature_key (Optional[str]): Feature key for rollout rules.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
str: ``{featureKey}_{ruleKey}`` for rollout rules; otherwise campaign key.
|
|
85
|
+
"""
|
|
86
|
+
if campaign.get_type() == CampaignTypeEnum.ROLLOUT.value:
|
|
87
|
+
rule_key = _resolve_feature_rule_key(campaign, feature_key)
|
|
88
|
+
if rule_key:
|
|
89
|
+
return rule_key
|
|
90
|
+
return campaign.get_key()
|
|
91
|
+
|
|
92
|
+
|
|
30
93
|
def set_variation_allocation(campaign: CampaignModel) -> None:
|
|
31
94
|
"""
|
|
32
95
|
Allocates traffic ranges to variations within a campaign.
|
|
@@ -176,6 +239,24 @@ def get_variation_from_campaign_key(
|
|
|
176
239
|
if variation:
|
|
177
240
|
return variation
|
|
178
241
|
|
|
242
|
+
for feature in settings.get_features() or []:
|
|
243
|
+
feature_key = feature.get_key()
|
|
244
|
+
for linked_campaign in feature.get_rules_linked_campaign() or []:
|
|
245
|
+
if (
|
|
246
|
+
get_experiment_key(linked_campaign, feature_key) == campaign_key
|
|
247
|
+
or get_rollout_key(linked_campaign, feature_key) == campaign_key
|
|
248
|
+
):
|
|
249
|
+
variation = next(
|
|
250
|
+
(
|
|
251
|
+
v
|
|
252
|
+
for v in linked_campaign.get_variations()
|
|
253
|
+
if v.get_id() == variation_id
|
|
254
|
+
),
|
|
255
|
+
None,
|
|
256
|
+
)
|
|
257
|
+
if variation:
|
|
258
|
+
return variation
|
|
259
|
+
|
|
179
260
|
return None
|
|
180
261
|
|
|
181
262
|
|
|
@@ -30,6 +30,22 @@ def send_debug_event_to_vwo(debug_event_props: Dict[str, Any]):
|
|
|
30
30
|
get_debugger_event_payload,
|
|
31
31
|
send_event,
|
|
32
32
|
)
|
|
33
|
+
from ..utils.internal_events_sampling_util import is_sampled_debug_error_template_key
|
|
34
|
+
from ..services.internal_events_sampling_service import InternalEventsSamplingService
|
|
35
|
+
from ..wingify import Wingify
|
|
36
|
+
|
|
37
|
+
# Sampled keys: apply sampling only when alwaysApplySampling.server is true; others are ALWAYS_SEND
|
|
38
|
+
msg_t = debug_event_props.get("msg_t")
|
|
39
|
+
is_sampled_debug_event = isinstance(msg_t, str) and is_sampled_debug_error_template_key(msg_t)
|
|
40
|
+
|
|
41
|
+
instance = Wingify.getInstance()
|
|
42
|
+
settings = getattr(instance, '_settings', None) if instance else None
|
|
43
|
+
# if the event is present in the sampled debug events keys list, then apply sampling
|
|
44
|
+
if is_sampled_debug_event:
|
|
45
|
+
sampling_service = InternalEventsSamplingService()
|
|
46
|
+
if not sampling_service.should_send_sampled_debug_event(settings):
|
|
47
|
+
return
|
|
48
|
+
|
|
33
49
|
# Create the query parameters
|
|
34
50
|
properties = get_events_base_properties(EventEnum.DEBUGGER_EVENT.value)
|
|
35
51
|
# Create the payload with required fields
|
|
@@ -0,0 +1,106 @@
|
|
|
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 math
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from ..constants.Constants import Constants
|
|
18
|
+
from ..constants.sampled_debug_error_template_keys import SampledDebugErrorTemplateKeys
|
|
19
|
+
from ..models.settings.settings_model import SettingsModel
|
|
20
|
+
from ..models.settings.internal_event_sampling_settings_model import PlatformSamplingRates
|
|
21
|
+
|
|
22
|
+
def get_default_sampling_percent() -> float:
|
|
23
|
+
"""
|
|
24
|
+
Returns the default server sampling percentage when settings omit the value.
|
|
25
|
+
:return: Default sampling percentage (10.0)
|
|
26
|
+
"""
|
|
27
|
+
return Constants.INTERNAL_EVENTS_DEFAULT_SAMPLING_PERCENT_SERVER
|
|
28
|
+
|
|
29
|
+
def normalize_sampling_percent(sampling_value: Optional[float]) -> float:
|
|
30
|
+
"""
|
|
31
|
+
Normalizes a sampling value to a valid percentage in the range [0, 100].
|
|
32
|
+
Falls back to the server default when the value is missing or invalid.
|
|
33
|
+
:param sampling_value: Raw value from settings
|
|
34
|
+
:return: Valid sampling percentage between 0 and 100
|
|
35
|
+
"""
|
|
36
|
+
if sampling_value is not None and 0 <= sampling_value <= 100:
|
|
37
|
+
return float(sampling_value)
|
|
38
|
+
|
|
39
|
+
return get_default_sampling_percent()
|
|
40
|
+
|
|
41
|
+
def get_runtime_sampling_percent(runtime_sampling_config: Optional[PlatformSamplingRates]) -> float:
|
|
42
|
+
"""
|
|
43
|
+
Reads the server sampling percentage from a runtime sampling config object.
|
|
44
|
+
:param runtime_sampling_config: Nested config (e.g. `sampling.usage`)
|
|
45
|
+
:return: Sampling percentage for the server runtime
|
|
46
|
+
"""
|
|
47
|
+
if runtime_sampling_config is None:
|
|
48
|
+
return get_default_sampling_percent()
|
|
49
|
+
|
|
50
|
+
return normalize_sampling_percent(runtime_sampling_config.get_server())
|
|
51
|
+
|
|
52
|
+
def get_usage_stats_sampling_percent(settings: Optional[SettingsModel]) -> float:
|
|
53
|
+
"""
|
|
54
|
+
Reads the usage-stats sampling percentage for the server runtime from settings.
|
|
55
|
+
:param settings: Parsed settings from the server
|
|
56
|
+
:return: Configured usage-stats sampling percentage (0-100)
|
|
57
|
+
"""
|
|
58
|
+
sampling = settings.get_sampling() if settings else None
|
|
59
|
+
usage_sampling = sampling.get_usage() if sampling else None
|
|
60
|
+
return get_runtime_sampling_percent(usage_sampling)
|
|
61
|
+
|
|
62
|
+
def get_debug_event_sampling_percent(settings: Optional[SettingsModel]) -> float:
|
|
63
|
+
"""
|
|
64
|
+
Reads the debug-event sampling percentage for the server runtime from settings.
|
|
65
|
+
:param settings: Parsed settings from the server
|
|
66
|
+
:return: Configured debug sampling percentage (0-100)
|
|
67
|
+
"""
|
|
68
|
+
sampling = settings.get_sampling() if settings else None
|
|
69
|
+
debug_sampling = sampling.get_debug() if sampling else None
|
|
70
|
+
return get_runtime_sampling_percent(debug_sampling)
|
|
71
|
+
|
|
72
|
+
def should_apply_sampling(settings: Optional[SettingsModel]) -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Determines whether sampling should be evaluated before send.
|
|
75
|
+
Controlled by `alwaysApplySampling.server`; defaults to `False`.
|
|
76
|
+
When `False`, usage-stats and sampled debug events are always sent.
|
|
77
|
+
:param settings: Parsed settings from the server
|
|
78
|
+
:return: `True` when a sampling check is required before sending
|
|
79
|
+
"""
|
|
80
|
+
if settings is None:
|
|
81
|
+
return Constants.INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING
|
|
82
|
+
|
|
83
|
+
always_apply_sampling = settings.get_always_apply_sampling()
|
|
84
|
+
if always_apply_sampling is None or always_apply_sampling.get_server() is None:
|
|
85
|
+
return Constants.INTERNAL_EVENTS_DEFAULT_ALWAYS_APPLY_SAMPLING
|
|
86
|
+
|
|
87
|
+
return always_apply_sampling.get_server()
|
|
88
|
+
|
|
89
|
+
def passes_sampling_percent(sampling_percent: float, random_value: float) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Evaluates whether an event qualifies under the configured sampling percentage.
|
|
92
|
+
:param sampling_percent: Configured sampling percentage (0-100)
|
|
93
|
+
:param random_value: Random value in the range [0, 1)
|
|
94
|
+
:return: `True` when the random draw falls within the sampling threshold
|
|
95
|
+
"""
|
|
96
|
+
# Map random [0,1) to integer percent bucket [0,100]
|
|
97
|
+
normalized_random_percent = int(math.floor(random_value * 101))
|
|
98
|
+
return normalized_random_percent <= sampling_percent
|
|
99
|
+
|
|
100
|
+
def is_sampled_debug_error_template_key(message_template_key: str) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Checks whether a debug error template key is in the sampled set.
|
|
103
|
+
:param message_template_key: The `msg_t` value from the debug event
|
|
104
|
+
:return: `True` when the key requires sampling before send
|
|
105
|
+
"""
|
|
106
|
+
return SampledDebugErrorTemplateKeys.contains(message_template_key)
|
|
@@ -29,6 +29,8 @@ from ..utils.campaign_util import (
|
|
|
29
29
|
get_bucketing_seed,
|
|
30
30
|
get_campaign_ids_from_feature_key,
|
|
31
31
|
get_campaigns_by_group_id,
|
|
32
|
+
get_experiment_key,
|
|
33
|
+
get_rollout_key,
|
|
32
34
|
get_feature_keys_from_campaign_ids,
|
|
33
35
|
set_campaign_allocation,
|
|
34
36
|
get_variation_from_campaign_key,
|
|
@@ -207,7 +209,7 @@ def _is_rollout_rule_for_feature_passed(
|
|
|
207
209
|
if isinstance(variation, VariationModel) and not None:
|
|
208
210
|
evaluated_feature_map[feature.get_key()] = {
|
|
209
211
|
"rolloutId": rule.get_id(),
|
|
210
|
-
"rolloutKey": rule.get_key(),
|
|
212
|
+
"rolloutKey": get_rollout_key(rule, feature.get_key()),
|
|
211
213
|
"rolloutVariationId": rule.get_variations()[0].get_id(),
|
|
212
214
|
}
|
|
213
215
|
return True
|
|
@@ -241,7 +243,8 @@ def _get_eligible_campaigns(
|
|
|
241
243
|
)
|
|
242
244
|
|
|
243
245
|
if stored_data and stored_data.get("experimentVariationId"):
|
|
244
|
-
|
|
246
|
+
campaign_experiment_key = get_experiment_key(campaign, feature_key)
|
|
247
|
+
if stored_data.get("experimentKey") == campaign_experiment_key:
|
|
245
248
|
variation = get_variation_from_campaign_key(
|
|
246
249
|
settings,
|
|
247
250
|
stored_data["experimentKey"],
|
|
@@ -261,7 +264,8 @@ def _get_eligible_campaigns(
|
|
|
261
264
|
)
|
|
262
265
|
)
|
|
263
266
|
if not any(
|
|
264
|
-
item
|
|
267
|
+
get_experiment_key(item, feature_key)
|
|
268
|
+
== campaign_experiment_key
|
|
265
269
|
for item in eligible_campaigns_with_storage
|
|
266
270
|
):
|
|
267
271
|
eligible_campaigns_with_storage.append(
|
|
@@ -428,7 +432,7 @@ def _normalize_weights_and_find_winning_campaign(
|
|
|
428
432
|
"featureKey": Constants.VWO_META_MEG_KEY + str(group_id),
|
|
429
433
|
"context": context,
|
|
430
434
|
"experimentId": winner_campaign.get_id(),
|
|
431
|
-
"experimentKey": winner_campaign
|
|
435
|
+
"experimentKey": get_experiment_key(winner_campaign),
|
|
432
436
|
"experimentVariationId": (
|
|
433
437
|
winner_campaign.get_variations()[0].get_id()
|
|
434
438
|
if winner_campaign.get_type() == CampaignTypeEnum.PERSONALIZE.value
|
|
@@ -538,7 +542,7 @@ def _get_campaign_using_advanced_algo(
|
|
|
538
542
|
"featureKey": Constants.VWO_META_MEG_KEY + str(group_id),
|
|
539
543
|
"context": context,
|
|
540
544
|
"experimentId": winner_campaign.get_id(),
|
|
541
|
-
"experimentKey": winner_campaign
|
|
545
|
+
"experimentKey": get_experiment_key(winner_campaign),
|
|
542
546
|
"experimentVariationId": (
|
|
543
547
|
winner_campaign.get_variations()[0].get_id()
|
|
544
548
|
if winner_campaign.get_type() == CampaignTypeEnum.PERSONALIZE.value
|
|
@@ -19,6 +19,7 @@ from ..models.campaign.feature_model import FeatureModel
|
|
|
19
19
|
from ..models.user.context_model import ContextModel
|
|
20
20
|
from ..utils.data_type_util import is_object
|
|
21
21
|
from ..utils.decision_util import check_whitelisting_and_pre_seg
|
|
22
|
+
from ..utils.campaign_util import get_experiment_key
|
|
22
23
|
from ..services.storage_service import StorageService
|
|
23
24
|
from ..utils.network_util import get_track_user_payload_data
|
|
24
25
|
from typing import Dict
|
|
@@ -74,7 +75,7 @@ def evaluate_rule(
|
|
|
74
75
|
decision.update(
|
|
75
76
|
{
|
|
76
77
|
"experimentId": campaign.get_id(),
|
|
77
|
-
"experimentKey": campaign.get_key(),
|
|
78
|
+
"experimentKey": get_experiment_key(campaign, feature.get_key()),
|
|
78
79
|
"experimentVariationId": whitelisted_object["variationId"],
|
|
79
80
|
}
|
|
80
81
|
)
|