kameleoon-client-python 3.6.0__tar.gz → 3.7.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 (174) hide show
  1. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/PKG-INFO +1 -1
  2. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/data_file.py +24 -0
  3. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/rule.py +11 -0
  4. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/data.py +2 -0
  5. kameleoon-client-python-3.7.0/kameleoon/data/manager/forced_experiment_variation.py +49 -0
  6. kameleoon-client-python-3.7.0/kameleoon/data/manager/forced_feature_variation.py +46 -0
  7. kameleoon-client-python-3.7.0/kameleoon/data/manager/forced_variation.py +33 -0
  8. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/visitor.py +59 -2
  9. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/exceptions.py +9 -0
  10. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/domain.py +6 -3
  11. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/kameleoon_client.py +93 -26
  12. kameleoon-client-python-3.7.0/kameleoon/network/cookie/cookie_manager.py +198 -0
  13. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/uri_helper.py +6 -1
  14. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/sdk_version.py +1 -1
  15. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon_client_python.egg-info/PKG-INFO +1 -1
  16. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon_client_python.egg-info/SOURCES.txt +3 -0
  17. kameleoon-client-python-3.6.0/kameleoon/network/cookie/cookie_manager.py +0 -107
  18. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/MANIFEST.in +0 -0
  19. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/README.md +0 -0
  20. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/__init__.py +0 -0
  21. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/client_readiness/__init__.py +0 -0
  22. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/client_readiness/async_readiness.py +0 -0
  23. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/client_readiness/threading_readiness.py +0 -0
  24. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/__init__.py +0 -0
  25. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/custom_data_info.py +0 -0
  26. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/feature_flag.py +0 -0
  27. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/rule_type.py +0 -0
  28. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/settings.py +0 -0
  29. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/variable.py +0 -0
  30. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/variation.py +0 -0
  31. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/configuration/variation_by_exposition.py +0 -0
  32. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/__init__.py +0 -0
  33. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/browser.py +0 -0
  34. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/conversion.py +0 -0
  35. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/cookie.py +0 -0
  36. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/custom_data.py +0 -0
  37. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/device.py +0 -0
  38. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/geolocation.py +0 -0
  39. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/kcs_heat.py +0 -0
  40. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/__init__.py +0 -0
  41. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/assigned_variation.py +0 -0
  42. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/page_view_visit.py +0 -0
  43. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/visitor_manager.py +0 -0
  44. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/manager/visitor_slot.py +0 -0
  45. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/mapping_identifier.py +0 -0
  46. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/operating_system.py +0 -0
  47. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/page_view.py +0 -0
  48. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/unique_identifier.py +0 -0
  49. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/user_agent.py +0 -0
  50. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/data/visitor_visits.py +0 -0
  51. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/defaults.py +0 -0
  52. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/__init__.py +0 -0
  53. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/functions.py +0 -0
  54. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/logger.py +0 -0
  55. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/multi_threading.py +0 -0
  56. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/nonce.py +0 -0
  57. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/repeat_timer.py +0 -0
  58. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/scheduler.py +0 -0
  59. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/string_utils.py +0 -0
  60. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/helpers/visitor_code.py +0 -0
  61. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/hybrid/__init__.py +0 -0
  62. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/hybrid/hybrid_manager.py +0 -0
  63. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/hybrid/hybrid_manager_impl.py +0 -0
  64. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/kameleoon_client_config.py +0 -0
  65. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/kameleoon_client_factory.py +0 -0
  66. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/logging/__init__.py +0 -0
  67. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/logging/kameleoon_logger.py +0 -0
  68. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/logging/log_level.py +0 -0
  69. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/logging/logger.py +0 -0
  70. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/__init__.py +0 -0
  71. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/asyncexec/__init__.py +0 -0
  72. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/asyncexec/async_executor.py +0 -0
  73. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/data/__init__.py +0 -0
  74. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/data/data_manager.py +0 -0
  75. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/remote_data/__init__.py +0 -0
  76. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/remote_data/remote_data_manager.py +0 -0
  77. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/remote_data/remote_visitor_data.py +0 -0
  78. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/tracking/__init__.py +0 -0
  79. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/tracking/tracking_builder.py +0 -0
  80. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/tracking/tracking_manager.py +0 -0
  81. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/tracking/visitor_tracking_registry.py +0 -0
  82. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/warehouse/__init__.py +0 -0
  83. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/managers/warehouse/warehouse_manager.py +0 -0
  84. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/middleware.py +0 -0
  85. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/__init__.py +0 -0
  86. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/access_token_source.py +0 -0
  87. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/access_token_source_factory.py +0 -0
  88. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/activity_event.py +0 -0
  89. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/cookie/__init__.py +0 -0
  90. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/net_provider.py +0 -0
  91. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/net_provider_impl.py +0 -0
  92. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/network_manager.py +0 -0
  93. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/network_manager_factory.py +0 -0
  94. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/network_manager_factory_impl.py +0 -0
  95. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/network_manager_impl.py +0 -0
  96. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/query_builder.py +0 -0
  97. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/query_encodable.py +0 -0
  98. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/sendable.py +0 -0
  99. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/service_initialize_context.py +0 -0
  100. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/service_initialize_context_impl.py +0 -0
  101. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/__init__.py +0 -0
  102. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/automation_service.py +0 -0
  103. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/automation_service_impl.py +0 -0
  104. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/configuration_service.py +0 -0
  105. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/configuration_service_impl.py +0 -0
  106. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/data_service.py +0 -0
  107. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/data_service_impl.py +0 -0
  108. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/service.py +0 -0
  109. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/services/service_impl.py +0 -0
  110. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/network/url_provider.py +0 -0
  111. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/real_time/__init__.py +0 -0
  112. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/real_time/real_time_configuration_service.py +0 -0
  113. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/real_time/real_time_event.py +0 -0
  114. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/storage/__init__.py +0 -0
  115. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/storage/cache.py +0 -0
  116. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/storage/cache_factory.py +0 -0
  117. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/storage/cache_factory_impl.py +0 -0
  118. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/storage/cache_impl.py +0 -0
  119. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/__init__.py +0 -0
  120. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/__init__.py +0 -0
  121. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/browser_condition.py +0 -0
  122. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/constants.py +0 -0
  123. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/conversion_condition.py +0 -0
  124. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/cookie_condition.py +0 -0
  125. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/custom_datum.py +0 -0
  126. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/device_condition.py +0 -0
  127. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/exclusive_feature_flag_condition.py +0 -0
  128. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/geolocation_condition.py +0 -0
  129. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/kcs_heat_range_condition.py +0 -0
  130. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/number_condition.py +0 -0
  131. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/operating_system_condition.py +0 -0
  132. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/page_title_condition.py +0 -0
  133. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/page_url_condition.py +0 -0
  134. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/page_view_number_condition.py +0 -0
  135. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/previous_page_condition.py +0 -0
  136. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/sdk_language_condition.py +0 -0
  137. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/segment_condition.py +0 -0
  138. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/string_value_condition.py +0 -0
  139. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/target_feature_flag_condition.py +0 -0
  140. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/targeting_condition.py +0 -0
  141. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.py +0 -0
  142. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/unknown_condition.py +0 -0
  143. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/visit_number_today_condition.py +0 -0
  144. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/visit_number_total_condition.py +0 -0
  145. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/visitor_code_condition.py +0 -0
  146. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/conditions/visitor_new_return_condition.py +0 -0
  147. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/models.py +0 -0
  148. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/targeting_manager.py +0 -0
  149. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/tree_builder.py +0 -0
  150. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/targeting/tree_condition_factory.py +0 -0
  151. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/types/__init__.py +0 -0
  152. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/types/remote_visitor_data_filter.py +0 -0
  153. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/types/variable.py +0 -0
  154. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon/types/variation.py +0 -0
  155. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon_client_python.egg-info/dependency_links.txt +0 -0
  156. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon_client_python.egg-info/requires.txt +15 -15
  157. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/kameleoon_client_python.egg-info/top_level.txt +0 -0
  158. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/setup.cfg +0 -0
  159. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/setup.py +0 -0
  160. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/__init__.py +0 -0
  161. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/__init__.py +0 -0
  162. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/admin.py +0 -0
  163. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/apps.py +0 -0
  164. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/migrations/__init__.py +0 -0
  165. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/models.py +0 -0
  166. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/tests.py +0 -0
  167. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/kameleoon_app/views.py +0 -0
  168. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/manage.py +0 -0
  169. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/proj/__init__.py +0 -0
  170. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/proj/asgi.py +0 -0
  171. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/proj/settings.py +0 -0
  172. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/proj/urls.py +0 -0
  173. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/integration/proj/proj/wsgi.py +0 -0
  174. {kameleoon-client-python-3.6.0 → kameleoon-client-python-3.7.0}/tests/resources/config.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kameleoon-client-python
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: Kameleoon Client Python Software Development Kit.
5
5
  Home-page: https://developers.kameleoon.com/python-sdk.html
6
6
  Author: Kameleoon
@@ -56,6 +56,7 @@ class DataFile:
56
56
  )
57
57
  self.__variation_by_id: Dict[int, VariationByExposition] = {}
58
58
  self.__rule_by_segment_id: Dict[int, Rule] = {}
59
+ self.__rule_info_by_exp_id: Dict[int, DataFile.RuleInfo] = {}
59
60
  self.__feature_flag_by_id: Dict[int, FeatureFlag] = {}
60
61
  self.__experiment_ids_with_js_css_variable: Set[int] = set()
61
62
  self.__collect_indices()
@@ -84,6 +85,11 @@ class DataFile:
84
85
  """Returns dictionary of all rule stored by segment id"""
85
86
  return self.__rule_by_segment_id
86
87
 
88
+ @property
89
+ def rule_info_by_exp_id(self) -> Dict[int, "DataFile.RuleInfo"]:
90
+ """Returns dictionary of all rule related info stored by experiment id"""
91
+ return self.__rule_info_by_exp_id
92
+
87
93
  @property
88
94
  def variation_by_id(self) -> Dict[int, VariationByExposition]:
89
95
  """Returns dictionary of all variations stored by id"""
@@ -122,6 +128,7 @@ class DataFile:
122
128
  has_feature_flag_variable_js_css = self.__has_feature_flag_variable_js_css(feature_flag)
123
129
  for rule in feature_flag.rules:
124
130
  self.__rule_by_segment_id[rule.segment_id] = rule
131
+ self.__rule_info_by_exp_id[rule.experiment_id] = DataFile.RuleInfo(feature_flag, rule)
125
132
  if has_feature_flag_variable_js_css:
126
133
  self.__experiment_ids_with_js_css_variable.add(rule.experiment_id)
127
134
  for variation in rule.variation_by_exposition:
@@ -146,3 +153,20 @@ class DataFile:
146
153
  f"settings:{self.__settings}"
147
154
  f"}}"
148
155
  )
156
+
157
+ class RuleInfo:
158
+ """Aggregates some information related to a specific rule"""
159
+
160
+ def __init__(self, feature_flag: FeatureFlag, rule: Rule) -> None:
161
+ self.__feature_flag = feature_flag
162
+ self.__rule = rule
163
+
164
+ @property
165
+ def feature_flag(self) -> FeatureFlag:
166
+ """Returns the feature flag"""
167
+ return self.__feature_flag
168
+
169
+ @property
170
+ def rule(self) -> Rule:
171
+ """Returns the rule"""
172
+ return self.__rule
@@ -3,6 +3,7 @@
3
3
  from typing import Any, List, Dict, Optional
4
4
  from kameleoon.configuration.rule_type import RuleType, rule_type_from_literal
5
5
  from kameleoon.configuration.variation_by_exposition import VariationByExposition
6
+ from kameleoon.exceptions import FeatureVariationNotFound
6
7
  from kameleoon.targeting.models import Segment
7
8
 
8
9
 
@@ -43,6 +44,16 @@ class Rule:
43
44
  return var_by_exp
44
45
  return None
45
46
 
47
+ def get_variation_by_key(self, variation_key: str) -> VariationByExposition:
48
+ """
49
+ Finds variation with a passed variation key.
50
+ Raises the `FeatureVariationNotFound` exception if such variation does not exist.
51
+ """
52
+ for var_by_exp in self.variation_by_exposition:
53
+ if var_by_exp.variation_key == variation_key:
54
+ return var_by_exp
55
+ raise FeatureVariationNotFound("'#{variation_key}'")
56
+
46
57
  @property
47
58
  def is_experimentation(self) -> bool:
48
59
  """Return `true` if rule is `experimentation` type"""
@@ -20,6 +20,8 @@ class DataType(IntEnum):
20
20
  VISITOR_VISITS = 11
21
21
  KCS_HEAT = 12
22
22
  UNIQUE_IDENTIFIER = 13
23
+ FORCED_EXPERIMENT_VARIATION = 14
24
+ FORCED_FEATURE_VARIATION = 15
23
25
 
24
26
 
25
27
  class BaseData:
@@ -0,0 +1,49 @@
1
+ """Forced Experiment Variation"""
2
+
3
+ from typing import Any, cast
4
+ from kameleoon.configuration.rule import Rule
5
+ from kameleoon.configuration.variation_by_exposition import VariationByExposition
6
+ from kameleoon.data.data import DataType
7
+ from kameleoon.data.manager.forced_variation import ForcedVariation
8
+
9
+
10
+ class ForcedExperimentVariation(ForcedVariation):
11
+ """Represents a forced experiment variation"""
12
+
13
+ def __init__(
14
+ self, rule: Rule, var_by_exp: VariationByExposition, force_targeting: bool
15
+ ) -> None:
16
+ super().__init__(rule, var_by_exp)
17
+ self.__force_targeting = force_targeting
18
+
19
+ @property
20
+ def data_type(self) -> DataType:
21
+ return DataType.FORCED_EXPERIMENT_VARIATION
22
+
23
+ @property
24
+ def rule(self) -> Rule:
25
+ """Returns the rule"""
26
+ return self._rule or cast(Rule, self._rule)
27
+
28
+ @property
29
+ def var_by_exp(self) -> VariationByExposition:
30
+ """Returns the variation by exposition"""
31
+ return self._var_by_exp or cast(VariationByExposition, self._var_by_exp)
32
+
33
+ @property
34
+ def force_targeting(self) -> bool:
35
+ """Returns the force targeting flag's state"""
36
+ return self.__force_targeting
37
+
38
+ def __eq__(self, value: Any) -> bool:
39
+ return super().__eq__(value) and isinstance(value, ForcedExperimentVariation) and \
40
+ (self.__force_targeting == value.force_targeting)
41
+
42
+ def __str__(self) -> str:
43
+ return (
44
+ "ForcedExperimentVariation{"
45
+ f"rule:{self._rule},"
46
+ f"var_by_exp:{self._var_by_exp},"
47
+ f"force_targeting:{self.__force_targeting},"
48
+ "}"
49
+ )
@@ -0,0 +1,46 @@
1
+ """Forced Feature Variation"""
2
+
3
+ from typing import Any, Optional
4
+ from kameleoon.configuration.rule import Rule
5
+ from kameleoon.configuration.variation_by_exposition import VariationByExposition
6
+ from kameleoon.data.data import DataType
7
+ from kameleoon.data.manager.forced_variation import ForcedVariation
8
+
9
+
10
+ class ForcedFeatureVariation(ForcedVariation):
11
+ """Represents a forced feature variation"""
12
+
13
+ def __init__(
14
+ self, feature_key: str, rule: Optional[Rule], var_by_exp: Optional[VariationByExposition], simulated: bool
15
+ ) -> None:
16
+ super().__init__(rule, var_by_exp)
17
+ self.__feature_key = feature_key
18
+ self.__simulated = simulated
19
+
20
+ @property
21
+ def data_type(self) -> DataType:
22
+ return DataType.FORCED_FEATURE_VARIATION
23
+
24
+ @property
25
+ def feature_key(self) -> str:
26
+ """Returns the feature key"""
27
+ return self.__feature_key
28
+
29
+ @property
30
+ def simulated(self) -> bool:
31
+ """Returns the simulated flag's state"""
32
+ return self.__simulated
33
+
34
+ def __eq__(self, value: Any) -> bool:
35
+ return super().__eq__(value) and isinstance(value, ForcedFeatureVariation) and \
36
+ (self.__feature_key == value.feature_key) and (self.__simulated == value.simulated)
37
+
38
+ def __str__(self) -> str:
39
+ return (
40
+ "ForcedFeatureVariation{"
41
+ f"feature_key:'{self.__feature_key}',"
42
+ f"rule:{self._rule},"
43
+ f"var_by_exp:{self._var_by_exp},"
44
+ f"simulated:{self.__simulated},"
45
+ "}"
46
+ )
@@ -0,0 +1,33 @@
1
+ """Forced Variation"""
2
+
3
+ from typing import Any, Optional
4
+ from kameleoon.configuration.rule import Rule
5
+ from kameleoon.configuration.variation_by_exposition import VariationByExposition
6
+ from kameleoon.data.data import BaseData, DataType
7
+
8
+
9
+ class ForcedVariation(BaseData):
10
+ """Represents a forced variation"""
11
+
12
+ def __init__(self, rule: Optional[Rule], var_by_exp: Optional[VariationByExposition]) -> None:
13
+ super().__init__()
14
+ self._rule = rule
15
+ self._var_by_exp = var_by_exp
16
+
17
+ @property
18
+ def data_type(self) -> DataType:
19
+ raise NotImplementedError
20
+
21
+ @property
22
+ def rule(self) -> Optional[Rule]:
23
+ """Returns the rule"""
24
+ return self._rule
25
+
26
+ @property
27
+ def var_by_exp(self) -> Optional[VariationByExposition]:
28
+ """Returns the variation by exposition"""
29
+ return self._var_by_exp
30
+
31
+ def __eq__(self, value: Any) -> bool:
32
+ return isinstance(value, ForcedVariation) and \
33
+ (self._rule == value._rule) and (self._var_by_exp == value._var_by_exp)
@@ -1,6 +1,6 @@
1
1
  """Visitor"""
2
2
  from threading import Lock
3
- from typing import Any, Callable, Optional, Dict, List, Iterator, cast
3
+ from typing import Any, Callable, Optional, Dict, List, Iterator, Iterable, cast
4
4
 
5
5
  from kameleoon.data.visitor_visits import VisitorVisits
6
6
  from kameleoon.data.data import Data
@@ -17,6 +17,8 @@ from kameleoon.data.page_view import PageView
17
17
  from kameleoon.data.unique_identifier import UniqueIdentifier
18
18
  from kameleoon.data.user_agent import UserAgent
19
19
  from kameleoon.data.manager.assigned_variation import AssignedVariation
20
+ from kameleoon.data.manager.forced_experiment_variation import ForcedExperimentVariation
21
+ from kameleoon.data.manager.forced_feature_variation import ForcedFeatureVariation
20
22
  from kameleoon.data.manager.page_view_visit import PageViewVisit
21
23
  from kameleoon.logging.kameleoon_logger import KameleoonLogger
22
24
  from kameleoon.network.sendable import Sendable
@@ -26,7 +28,7 @@ EMPTY_DICT: Dict[Any, Any] = {}
26
28
  EMPTY_LIST: List[Any] = []
27
29
 
28
30
 
29
- class Visitor:
31
+ class Visitor: # pylint: disable=R0904
30
32
  """Visitor"""
31
33
  def __init__(self, source: Optional["Visitor"] = None) -> None:
32
34
  if source is None:
@@ -124,6 +126,45 @@ class Visitor:
124
126
  KameleoonLogger.debug('CALL/RETURN: Visitor.variations -> (variations: %s)', variations)
125
127
  return variations
126
128
 
129
+ def get_forced_feature_variation(self, feature_key: str) -> Optional[ForcedFeatureVariation]:
130
+ """
131
+ Returns an associated forced feature variation by its feature key if it is present.
132
+ Otherwise returns `None`.
133
+ """
134
+ KameleoonLogger.debug('CALL: Visitor.get_forced_feature_variation(feature_key: %s)', feature_key)
135
+ ffv = self._data.simulated_variations.get(feature_key) if self._data.simulated_variations else None
136
+ KameleoonLogger.debug(
137
+ 'RETURN: Visitor.get_forced_feature_variation(feature_key: %s) -> (variation: %s)', feature_key, ffv
138
+ )
139
+ return ffv
140
+
141
+ def get_forced_experiment_variation(self, experiment_id: int) -> Optional[ForcedExperimentVariation]:
142
+ """
143
+ Returns an associated forced experiment variation by its experiment id if it is present.
144
+ Otherwise returns `None`.
145
+ """
146
+ KameleoonLogger.debug('CALL: Visitor.get_forced_experiment_variation(experiment_id: %s)', experiment_id)
147
+ fev = self._data.forced_variations.get(experiment_id) if self._data.forced_variations else None
148
+ KameleoonLogger.debug(
149
+ 'RETURN: Visitor.get_forced_experiment_variation(experiment_id: %s) -> (variation: %s)', experiment_id, fev
150
+ )
151
+ return fev
152
+
153
+ def reset_forced_experiment_variation(self, experiment_id: int) -> None:
154
+ """Resets an associated forced experiment variation for some experiment id"""
155
+ KameleoonLogger.debug('CALL: Visitor.reset_forced_experiment_variation(experiment_id: %s)', experiment_id)
156
+ if self._data.forced_variations:
157
+ self._data.forced_variations.pop(experiment_id, None)
158
+ KameleoonLogger.debug('RETURN: Visitor.reset_forced_experiment_variation(experiment_id: %s)', experiment_id)
159
+
160
+ def update_simulated_variations(self, variations: Iterable[ForcedFeatureVariation]) -> None:
161
+ """Updates the associated simulated variations"""
162
+ if not (self._data.simulated_variations or variations):
163
+ return
164
+ KameleoonLogger.debug('CALL: Visitor.update_simulated_variations(variations: %s)', variations)
165
+ self._data.simulated_variations = {sv.feature_key: sv for sv in variations}
166
+ KameleoonLogger.debug('RETURN: Visitor.update_simulated_variations(variations: %s)', variations)
167
+
127
168
  @property
128
169
  def kcs_heat(self) -> Optional[KcsHeat]:
129
170
  """Returns the KCS heat data associated with this Visitor."""
@@ -217,6 +258,8 @@ class Visitor:
217
258
  self.page_view_visits: Optional[Dict[str, PageViewVisit]] = None
218
259
  self.conversions: Optional[List[Conversion]] = None
219
260
  self.variations: Optional[Dict[int, AssignedVariation]] = None
261
+ self.forced_variations: Optional[Dict[int, ForcedExperimentVariation]] = None
262
+ self.simulated_variations: Optional[Dict[str, ForcedFeatureVariation]] = None
220
263
  self.kcs_heat: Optional[KcsHeat] = None
221
264
  self.visitor_visits: Optional[VisitorVisits] = None
222
265
  self.mapping_identifier: Optional[str] = None
@@ -294,6 +337,18 @@ class Visitor:
294
337
  if context.overwrite or variation.experiment_id not in self.variations:
295
338
  self._data.variations[variation.experiment_id] = variation
296
339
 
340
+ def _add_forced_feature_variation(self, context: DataAddingContext) -> None:
341
+ ffv = cast(ForcedFeatureVariation, context.data)
342
+ if self._data.simulated_variations is None:
343
+ self._data.simulated_variations = {}
344
+ self._data.simulated_variations[ffv.feature_key] = ffv
345
+
346
+ def _add_forced_experiment_variation(self, context: DataAddingContext) -> None:
347
+ fev = cast(ForcedExperimentVariation, context.data)
348
+ if self._data.forced_variations is None:
349
+ self._data.forced_variations = {}
350
+ self._data.forced_variations[fev.rule.experiment_id] = fev
351
+
297
352
  def _set_browser(self, context: DataAddingContext) -> None:
298
353
  if context.overwrite or self._data.browser is None:
299
354
  self._data.browser = cast(Browser, context.data)
@@ -372,5 +427,7 @@ class Visitor:
372
427
  KcsHeat: _set_kcs_heat,
373
428
  VisitorVisits: _set_visitor_visits,
374
429
  AssignedVariation: _add_variation,
430
+ ForcedFeatureVariation: _add_forced_feature_variation,
431
+ ForcedExperimentVariation: _add_forced_experiment_variation,
375
432
  UniqueIdentifier: _set_is_unique_identifier,
376
433
  }
@@ -70,6 +70,15 @@ class FeatureNotFound(NotFoundError):
70
70
  super().__init__(self.message)
71
71
 
72
72
 
73
+ class FeatureExperimentNotFound(NotFoundError): # //~ Derived from FeatureError in other SDKs
74
+ """Exception indicating that the requested experiment has not been found.
75
+ Check that the experiment's ID matches the provided one."""
76
+
77
+ def __init__(self, message=None) -> None:
78
+ self.message = "Experiment " + str(message)
79
+ super().__init__(self.message)
80
+
81
+
73
82
  class FeatureVariableNotFound(NotFoundError):
74
83
  """Exception indicating that the requested variable has not been found.
75
84
  Check that the variable's ID (or key) matches the one in your code."""
@@ -9,6 +9,7 @@ REGEX_DOMAIN = (
9
9
  r"^(\.?(([a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d])|[a-zA-Z\d]))"
10
10
  r"(\.(([a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d])|[a-zA-Z\d])){1,126}$"
11
11
  )
12
+ LOCALHOST = "localhost"
12
13
 
13
14
 
14
15
  def validate_top_level_domain(top_level_domain: str) -> str:
@@ -35,8 +36,10 @@ def validate_top_level_domain(top_level_domain: str) -> str:
35
36
  protocol, top_level_domain)
36
37
  break
37
38
 
38
- if not re.match(REGEX_DOMAIN, top_level_domain):
39
- KameleoonLogger.error(f"The top-level domain '{top_level_domain}' is invalid.")
40
- return ""
39
+ if not re.match(REGEX_DOMAIN, top_level_domain) and top_level_domain != LOCALHOST:
40
+ KameleoonLogger.error(
41
+ f"The top-level domain '{top_level_domain}' is invalid. The value has been set as provided, but it does "
42
+ f"not meet the required format for proper SDK functionality. Please check the domain for correctness.")
43
+ return top_level_domain
41
44
 
42
45
  return top_level_domain
@@ -14,10 +14,12 @@ from kameleoon.configuration.variation_by_exposition import VariationByExpositio
14
14
 
15
15
  from kameleoon.data import Conversion, CustomData, Data, UniqueIdentifier
16
16
  from kameleoon.data.manager.assigned_variation import AssignedVariation
17
+ from kameleoon.data.manager.forced_experiment_variation import ForcedExperimentVariation
17
18
  from kameleoon.data.manager.visitor_manager import VisitorManager
18
19
 
19
20
  from kameleoon.exceptions import (
20
21
  FeatureVariationNotFound,
22
+ FeatureExperimentNotFound,
21
23
  FeatureVariableNotFound,
22
24
  FeatureEnvironmentDisabled,
23
25
  SiteCodeIsEmpty,
@@ -130,7 +132,7 @@ class KameleoonClient:
130
132
  )
131
133
  self._hybrid_manager = HybridManagerImpl(HYBRID_EXPIRATION_TIME, self._data_manager)
132
134
 
133
- self._cookie_manager = CookieManager(self._data_manager, config.top_level_domain)
135
+ self._cookie_manager = CookieManager(self._data_manager, self._visitor_manager, config.top_level_domain)
134
136
  atsf = AccessTokenSourceFactory(config.client_id, config.client_secret)
135
137
  self._network_manager = self._network_manager_factory.create(
136
138
  self.site_code,
@@ -589,9 +591,13 @@ class KameleoonClient:
589
591
  "CALL: KameleoonClient.__get_variation_info(visitor_code: %s, feature_flag: %s, track: %s)",
590
592
  visitor_code, feature_flag, track
591
593
  )
592
- var_by_exp, rule = self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
594
+ visitor = self._visitor_manager.get_visitor(visitor_code)
595
+ forced_variation = visitor.get_forced_feature_variation(feature_flag.feature_key) if visitor else None
596
+ var_by_exp, rule = (forced_variation.var_by_exp, forced_variation.rule) if forced_variation else \
597
+ self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
598
+ if not (forced_variation and forced_variation.simulated):
599
+ self.__save_variation(visitor_code, rule, var_by_exp, track)
593
600
  variation_key = self.__calculate_variation_key(var_by_exp, rule, feature_flag)
594
- self.__save_variation(visitor_code, rule, var_by_exp, track)
595
601
  KameleoonLogger.debug(
596
602
  "RETURN: KameleoonClient.__get_variation_info(visitor_code: %s, feature_flag: %s, track: %s)"
597
603
  " -> (variation_key: %s, variation_by_exposition: %s, rule: %s)",
@@ -942,11 +948,14 @@ class KameleoonClient:
942
948
  "Call to deprecated function `get_active_feature_list_for_visitor`. "
943
949
  "Please use `get_active_features` instead."
944
950
  )
951
+ visitor = self._visitor_manager.get_visitor(visitor_code)
945
952
 
946
953
  def filter_conditions(feature_flag: FeatureFlag) -> bool:
947
954
  if not feature_flag.environment_enabled:
948
955
  return False
949
- (variation, rule) = self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
956
+ forced_variation = visitor.get_forced_feature_variation(feature_flag.feature_key) if visitor else None
957
+ variation, rule = (forced_variation.var_by_exp, forced_variation.rule) if forced_variation else \
958
+ self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
950
959
  variation_key = self.__calculate_variation_key(variation, rule, feature_flag)
951
960
  return variation_key != configuration.Variation.Type.OFF.value
952
961
 
@@ -987,11 +996,14 @@ class KameleoonClient:
987
996
  )
988
997
  KameleoonLogger.info("CALL: KameleoonClient.get_active_features(visitor_code: %s)", visitor_code)
989
998
  validate_visitor_code(visitor_code)
999
+ visitor = self._visitor_manager.get_visitor(visitor_code)
990
1000
  map_active_features: Dict[str, Variation] = {}
991
1001
  for feature_flag in self._data_manager.data_file.feature_flags.values():
992
1002
  if not feature_flag.environment_enabled:
993
1003
  continue
994
- (var_by_exp, rule) = self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
1004
+ forced_variation = visitor.get_forced_feature_variation(feature_flag.feature_key) if visitor else None
1005
+ var_by_exp, rule = (forced_variation.var_by_exp, forced_variation.rule) if forced_variation else \
1006
+ self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
995
1007
  variation_key = self.__calculate_variation_key(var_by_exp, rule, feature_flag)
996
1008
  if variation_key == configuration.Variation.Type.OFF.value:
997
1009
  continue
@@ -1303,9 +1315,13 @@ class KameleoonClient:
1303
1315
  "CALL: KameleoonClient.__get_feature_variation_key(visitor_code: %s, feature_key: %s)",
1304
1316
  visitor_code, feature_key)
1305
1317
  feature_flag = self._data_manager.data_file.get_feature_flag(feature_key)
1306
- (variation, rule) = self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
1318
+ visitor = self._visitor_manager.get_visitor(visitor_code)
1319
+ forced_variation = visitor.get_forced_feature_variation(feature_key) if visitor else None
1320
+ variation, rule = (forced_variation.var_by_exp, forced_variation.rule) if forced_variation else \
1321
+ self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
1322
+ if not (forced_variation and forced_variation.simulated):
1323
+ self.__save_variation(visitor_code, rule, variation)
1307
1324
  variation_key = self.__calculate_variation_key(variation, rule, feature_flag)
1308
- self.__save_variation(visitor_code, rule, variation)
1309
1325
  self._tracking_manager.add_visitor_code(visitor_code)
1310
1326
  KameleoonLogger.debug(
1311
1327
  "RETURN: KameleoonClient.__get_feature_variation_key(visitor_code: %s, feature_key: %s) -> "
@@ -1338,28 +1354,36 @@ class KameleoonClient:
1338
1354
  KameleoonLogger.debug(
1339
1355
  "CALL: KameleoonClient.__calculate_variation_rule_for_feature(visitor_code: %s, feature_flag: %s)",
1340
1356
  visitor_code, feature_flag)
1357
+ visitor = self._visitor_manager.get_visitor(visitor_code)
1358
+ # use mappingIdentifier instead of visitorCode if it was set up
1359
+ code_for_hash = (
1360
+ visitor.mapping_identifier
1361
+ if visitor is not None and visitor.mapping_identifier is not None
1362
+ else visitor_code
1363
+ )
1364
+ selected_variation = None
1365
+ selected_rule = None
1341
1366
  for rule in feature_flag.rules:
1367
+ forced_variation = visitor.get_forced_experiment_variation(rule.experiment_id) if visitor else None
1368
+ if forced_variation and forced_variation.force_targeting:
1369
+ # Forcing experiment variation in force-targeting mode
1370
+ selected_variation, selected_rule = forced_variation.var_by_exp, rule
1371
+ break
1342
1372
  # check if visitor is targeted for rule, else next rule
1343
1373
  if not self.__check_targeting(visitor_code, rule.experiment_id, rule):
1344
1374
  continue
1345
- visitor = self._visitor_manager.get_visitor(visitor_code)
1346
- # use mappingIdentifier instead of visitorCode if it was set up
1347
- code_for_hash = (
1348
- visitor.mapping_identifier
1349
- if visitor is not None and visitor.mapping_identifier is not None
1350
- else visitor_code
1351
- )
1375
+ if forced_variation:
1376
+ # Forcing experiment variation in targeting-only mode
1377
+ selected_variation, selected_rule = forced_variation.var_by_exp, rule
1378
+ break
1352
1379
  # uses for rule exposition
1353
1380
  hash_rule = obtain_hash_double_rule(code_for_hash, rule.id_, rule.respool_time)
1354
1381
  KameleoonLogger.debug("Calculated hash_rule: %s for visitor_code: %s", hash_rule, code_for_hash)
1355
1382
  # check main expostion for rule with hashRule
1356
1383
  if hash_rule <= rule.exposition:
1357
1384
  if rule.is_targeted_delivery:
1358
- KameleoonLogger.debug(
1359
- "RETURN: KameleoonClient.__calculate_variation_rule_for_feature(visitor_code: %s, "
1360
- "feature_flag: %s) -> (variation: %s, rule: %s)",
1361
- visitor_code, feature_flag, rule.first_variation, rule)
1362
- return (rule.first_variation, rule)
1385
+ selected_variation, selected_rule = rule.first_variation, rule
1386
+ break
1363
1387
 
1364
1388
  # uses for variation's expositions
1365
1389
  hash_variation = obtain_hash_double_rule(code_for_hash, rule.experiment_id, rule.respool_time)
@@ -1368,17 +1392,14 @@ class KameleoonClient:
1368
1392
  # get variation with hash_variation
1369
1393
  variation = rule.get_variation(hash_variation)
1370
1394
  if variation:
1371
- KameleoonLogger.debug(
1372
- "RETURN: KameleoonClient.__calculate_variation_rule_for_feature(visitor_code: %s, "
1373
- "feature_flag: %s) -> (variation: %s, rule: %s)",
1374
- visitor_code, feature_flag, variation, rule)
1375
- return (variation, rule)
1395
+ selected_variation, selected_rule = variation, rule
1396
+ break
1376
1397
  elif rule.is_targeted_delivery:
1377
1398
  break
1378
1399
  KameleoonLogger.debug(
1379
1400
  "RETURN: KameleoonClient.__calculate_variation_rule_for_feature(visitor_code: %s, feature_flag: %s) "
1380
- "-> (variation: None, rule: None)", visitor_code, feature_flag)
1381
- return None, None
1401
+ "-> (variation: %s, rule: %s)", visitor_code, feature_flag, selected_variation, selected_rule)
1402
+ return selected_variation, selected_rule
1382
1403
 
1383
1404
  @staticmethod
1384
1405
  def __calculate_variation_key(
@@ -1409,3 +1430,49 @@ class KameleoonClient:
1409
1430
  def _is_consent_given(self, visitor_code: str) -> bool:
1410
1431
  return not self._data_manager.data_file.settings.is_consent_required or \
1411
1432
  ((visitor := self._visitor_manager.get_visitor(visitor_code)) is not None) and visitor.legal_consent
1433
+
1434
+ def set_forced_variation(
1435
+ self, visitor_code: str, experiment_id: int, variation_key: Optional[str], force_targeting=True
1436
+ ) -> None:
1437
+ """
1438
+ Sets or resets a forced variation for a visitor in a specific experiment,
1439
+ so the experiment will be evaluated to the variation for the visitor.
1440
+
1441
+ In order to reset the forced variation set the `variation_key` parameter to `None`.
1442
+ If the forced variation you want to reset does not exist, the method will have no effect.
1443
+
1444
+ :param visitor_code: The unique visitor code identifying the visitor.
1445
+ :type visitor_code: str
1446
+ :param experiment_id: The identifier of the experiment you want to set/reset the forced variation for.
1447
+ :type experiment_id: int
1448
+ :param variation_key: The identifier of the variation you want the experiment to be evaluated to. \
1449
+ Set to `None` to reset the forced variation.
1450
+ :type variation_key: Optional[str]
1451
+ :param force_targeting: If `True`, the visitor will be targeted to the experiment regardless its conditions. \
1452
+ Otherwise, the normal targeting logic will be preserved. Optional (defaults to `True`).
1453
+ :type force_targeting: bool
1454
+
1455
+ :raises VisitorCodeInvalid: The provided **visitor code** is invalid.
1456
+ :raises FeatureExperimentNotFound: The provided **experiment id** does not exist in the feature flag.
1457
+ :raises FeatureVariationNotFound: The provided **variation key** does not belong to the experiment.
1458
+ """
1459
+ KameleoonLogger.info(
1460
+ "CALL: KameleoonClient.set_forced_variation(visitor_code: %s, experiment_id: %s, variation_key: %s, "
1461
+ "force_targeting: %s)", visitor_code, experiment_id, variation_key, force_targeting
1462
+ )
1463
+ validate_visitor_code(visitor_code)
1464
+ if variation_key is None:
1465
+ visitor = self._visitor_manager.get_visitor(visitor_code)
1466
+ if visitor:
1467
+ visitor.reset_forced_experiment_variation(experiment_id)
1468
+ else:
1469
+ rule_info = self._data_manager.data_file.rule_info_by_exp_id.get(experiment_id)
1470
+ if rule_info is None:
1471
+ raise FeatureExperimentNotFound(experiment_id)
1472
+ var_by_exp = rule_info.rule.get_variation_by_key(variation_key)
1473
+ forced_variation = ForcedExperimentVariation(rule_info.rule, var_by_exp, force_targeting)
1474
+ self._visitor_manager.add_data(visitor_code, forced_variation)
1475
+ KameleoonLogger.info(
1476
+ "RETURN: KameleoonClient.set_forced_variation(visitor_code: %s, experiment_id: %s, variation_key: %s, "
1477
+ "force_targeting: %s)", visitor_code, experiment_id, variation_key, force_targeting
1478
+ )