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.
Files changed (156) hide show
  1. {wingify_fme_python_sdk-1.60.0/wingify_fme_python_sdk.egg-info → wingify_fme_python_sdk-1.62.0}/PKG-INFO +1 -1
  2. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/setup.py +1 -1
  3. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/get_flag_api.py +15 -5
  4. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/constants/Constants.py +4 -1
  5. wingify_fme_python_sdk-1.62.0/wingify/constants/sampled_debug_error_template_keys.py +34 -0
  6. wingify_fme_python_sdk-1.62.0/wingify/models/settings/internal_event_sampling_settings_model.py +53 -0
  7. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/settings/settings_model.py +19 -2
  8. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/core/segmentation_manager.py +18 -7
  9. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/error-messages.json +3 -1
  10. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/hooks_manager.py +10 -1
  11. wingify_fme_python_sdk-1.62.0/wingify/services/internal_events_sampling_service.py +62 -0
  12. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/campaign_util.py +81 -0
  13. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/debugger_service_util.py +16 -0
  14. wingify_fme_python_sdk-1.62.0/wingify/utils/internal_events_sampling_util.py +106 -0
  15. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/meg_util.py +9 -5
  16. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/rule_evaluation_util.py +2 -1
  17. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify.py +10 -9
  18. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify_client.py +24 -20
  19. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0/wingify_fme_python_sdk.egg-info}/PKG-INFO +1 -1
  20. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/SOURCES.txt +4 -0
  21. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/LICENSE +0 -0
  22. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/MANIFEST.in +0 -0
  23. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/NOTICE +0 -0
  24. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/README.md +0 -0
  25. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/requirements-dev.txt +0 -0
  26. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/requirements.txt +0 -0
  27. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/setup.cfg +0 -0
  28. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/__init__.py +0 -0
  29. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo.py +0 -0
  30. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo_builder.py +0 -0
  31. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/vwo/vwo_client.py +0 -0
  32. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/__init__.py +0 -0
  33. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/__init__.py +0 -0
  34. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/set_attribute_api.py +0 -0
  35. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/api/track_api.py +0 -0
  36. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/constants/__init__.py +0 -0
  37. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/decorators/__init__.py +0 -0
  38. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/decorators/storage_decorator.py +0 -0
  39. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/__init__.py +0 -0
  40. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/api_enum.py +0 -0
  41. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/campaign_type_enum.py +0 -0
  42. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/debug_category_enum.py +0 -0
  43. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/event_enum.py +0 -0
  44. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/headers_enum.py +0 -0
  45. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/hooks_enum.py +0 -0
  46. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/status_enum.py +0 -0
  47. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/storage_enum.py +0 -0
  48. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/enums/url_enum.py +0 -0
  49. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/Alias.py +0 -0
  50. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/AliasSetResponse.py +0 -0
  51. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/__init__.py +0 -0
  52. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/batch_event_data.py +0 -0
  53. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/__init__.py +0 -0
  54. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/campaign_model.py +0 -0
  55. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/feature_model.py +0 -0
  56. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/holdout_model.py +0 -0
  57. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/impact_campaign_model.py +0 -0
  58. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/metric_model.py +0 -0
  59. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/rule_model.py +0 -0
  60. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/variable_model.py +0 -0
  61. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/campaign/variation_model.py +0 -0
  62. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/gateway_service_model.py +0 -0
  63. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/__init__.py +0 -0
  64. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_group_schema.py +0 -0
  65. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_metric_schema.py +0 -0
  66. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_schema.py +0 -0
  67. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/campaign_variation_schema.py +0 -0
  68. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/empty_object_schema.py +0 -0
  69. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/feature_schema.py +0 -0
  70. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/group_schema.py +0 -0
  71. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/holdout_schema.py +0 -0
  72. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/rule_schema.py +0 -0
  73. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/settings_schema.py +0 -0
  74. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/schemas/variable_schema.py +0 -0
  75. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/settings/__init__.py +0 -0
  76. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/storage/__init__.py +0 -0
  77. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/storage/storage_model.py +0 -0
  78. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/__init__.py +0 -0
  79. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/context_model.py +0 -0
  80. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/context_vwo_model.py +0 -0
  81. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/user/get_flag.py +0 -0
  82. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/vwo_options_model.py +0 -0
  83. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/models/wingify_options_model.py +0 -0
  84. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/__init__.py +0 -0
  85. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/decision_maker/__init__.py +0 -0
  86. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/decision_maker/decision_maker.py +0 -0
  87. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/__init__.py +0 -0
  88. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/__init__.py +0 -0
  89. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/log_manager.py +0 -0
  90. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/core/log_transport_manager.py +0 -0
  91. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/enums/__init__.py +0 -0
  92. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/enums/log_level_enum.py +0 -0
  93. {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
  94. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/log_message_builder.py +0 -0
  95. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/logger.py +0 -0
  96. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/transports/__init__.py +0 -0
  97. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/logger/transports/console_transport.py +0 -0
  98. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/__init__.py +0 -0
  99. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/client/__init__.py +0 -0
  100. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/client/network_client.py +0 -0
  101. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/handlers/__init__.py +0 -0
  102. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/handlers/request_handler.py +0 -0
  103. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/manager/__init__.py +0 -0
  104. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/manager/network_manager.py +0 -0
  105. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/__init__.py +0 -0
  106. {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
  107. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/request_model.py +0 -0
  108. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/network_layer/models/response_model.py +0 -0
  109. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/__init__.py +0 -0
  110. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/core/__init__.py +0 -0
  111. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/enums/__init__.py +0 -0
  112. {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
  113. {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
  114. {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
  115. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/evaluators/__init__.py +0 -0
  116. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.py +0 -0
  117. {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
  118. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/utils/__init__.py +0 -0
  119. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/segmentation_evaluator/utils/segment_util.py +0 -0
  120. {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
  121. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/__init__.py +0 -0
  122. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/connector.py +0 -0
  123. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/packages/storage/storage.py +0 -0
  124. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/__init__.py +0 -0
  125. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/debug-messages.json +0 -0
  126. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/info-message.json +0 -0
  127. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/trace-messages.json +0 -0
  128. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/resources/warn-messages.json +0 -0
  129. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/__init__.py +0 -0
  130. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/batch_event_queue.py +0 -0
  131. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/campaign_decision_service.py +0 -0
  132. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/settings_manager.py +0 -0
  133. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/storage_service.py +0 -0
  134. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/services/url_service.py +0 -0
  135. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/__init__.py +0 -0
  136. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/aliasing_util.py +0 -0
  137. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/brand_context.py +0 -0
  138. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/brand_util.py +0 -0
  139. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/data_type_util.py +0 -0
  140. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/decision_util.py +0 -0
  141. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/event_util.py +0 -0
  142. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/function_util.py +0 -0
  143. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/gateway_service_util.py +0 -0
  144. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/holdout_util.py +0 -0
  145. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/impression_util.py +0 -0
  146. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/log_message_util.py +0 -0
  147. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/model_utils.py +0 -0
  148. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/network_util.py +0 -0
  149. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/settings_util.py +0 -0
  150. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/usage_stats_util.py +0 -0
  151. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/utils/uuid_util.py +0 -0
  152. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/vwo_client.py +0 -0
  153. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify/wingify_builder.py +0 -0
  154. {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
  155. {wingify_fme_python_sdk-1.60.0 → wingify_fme_python_sdk-1.62.0}/wingify_fme_python_sdk.egg-info/requires.txt +0 -0
  156. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wingify-fme-python-sdk
3
- Version: 1.60.0
3
+ Version: 1.62.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
@@ -143,7 +143,7 @@ class PostDevelopCommand(develop):
143
143
 
144
144
  setup(
145
145
  name=_brand["name"],
146
- version="1.60.0",
146
+ version="1.62.0",
147
147
  description=_brand["description"],
148
148
  long_description=long_description,
149
149
  long_description_content_type="text/markdown",
@@ -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 get_variation_from_campaign_key
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.get_key(),
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": rule.get_key(),
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": campaign.get_key(),
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": campaign.get_key(),
646
+ "experimentKey": get_experiment_key(
647
+ campaign, decision.get("featureKey")
648
+ ),
639
649
  "experimentVariationId": variation.get_id(),
640
650
  }
641
651
  )
@@ -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.60.0",
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
@@ -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(list(filter(lambda holdout: holdout.get_is_gateway_service_required(), holdouts))) > 0
89
- # Call gateway service if required for segmentation OR if gateway service is provided and user agent is available
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
- (feature.get_is_gateway_service_required() and not SettingsManager.get_instance().is_on_platform_host()) or
92
- (not SettingsManager.get_instance().is_on_platform_host() and
93
- (context.get_user_agent() or context.get_ip_address()))
94
- ) or is_gateway_service_required_for_holdouts
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",
@@ -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
- self.callback(properties)
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())
@@ -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
- if stored_data.get("experimentKey") == campaign.get_key():
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.get_key() == campaign.get_key()
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.get_key(),
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.get_key(),
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
  )