kameleoon-client-python 3.3.2__tar.gz → 3.3.3__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 (154) hide show
  1. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/PKG-INFO +1 -1
  2. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/__init__.py +1 -0
  3. kameleoon-client-python-3.3.3/kameleoon/client_readiness/async_readiness.py +48 -0
  4. kameleoon-client-python-3.3.3/kameleoon/client_readiness/threading_readiness.py +36 -0
  5. kameleoon-client-python-3.3.3/kameleoon/data/manager/assigned_variation.py +55 -0
  6. kameleoon-client-python-3.3.3/kameleoon/data/manager/page_view_visit.py +34 -0
  7. kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor.py +261 -0
  8. kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor_manager.py +112 -0
  9. kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor_slot.py +15 -0
  10. kameleoon-client-python-3.3.3/kameleoon/hybrid/hybrid_manager.py +7 -0
  11. kameleoon-client-python-3.3.3/kameleoon/hybrid/hybrid_manager_impl.py +25 -0
  12. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client.py +1 -1
  13. kameleoon-client-python-3.3.3/kameleoon/managers/remote_data/remote_data_manager.py +66 -0
  14. kameleoon-client-python-3.3.3/kameleoon/managers/remote_data/remote_visitor_data.py +225 -0
  15. kameleoon-client-python-3.3.3/kameleoon/managers/warehouse/warehouse_manager.py +55 -0
  16. kameleoon-client-python-3.3.3/kameleoon/network/access_token_source.py +107 -0
  17. kameleoon-client-python-3.3.3/kameleoon/network/access_token_source_factory.py +12 -0
  18. kameleoon-client-python-3.3.3/kameleoon/network/activity_event.py +12 -0
  19. kameleoon-client-python-3.3.3/kameleoon/network/cookie/cookie_manager.py +75 -0
  20. kameleoon-client-python-3.3.3/kameleoon/network/net_provider.py +54 -0
  21. kameleoon-client-python-3.3.3/kameleoon/network/net_provider_impl.py +75 -0
  22. kameleoon-client-python-3.3.3/kameleoon/network/network_manager.py +42 -0
  23. kameleoon-client-python-3.3.3/kameleoon/network/network_manager_factory.py +18 -0
  24. kameleoon-client-python-3.3.3/kameleoon/network/network_manager_factory_impl.py +39 -0
  25. kameleoon-client-python-3.3.3/kameleoon/network/network_manager_impl.py +60 -0
  26. kameleoon-client-python-3.3.3/kameleoon/network/query_builder.py +82 -0
  27. kameleoon-client-python-3.3.3/kameleoon/network/query_encodable.py +3 -0
  28. kameleoon-client-python-3.3.3/kameleoon/network/sendable.py +49 -0
  29. kameleoon-client-python-3.3.3/kameleoon/network/service_initialize_context.py +7 -0
  30. kameleoon-client-python-3.3.3/kameleoon/network/service_initialize_context_impl.py +12 -0
  31. kameleoon-client-python-3.3.3/kameleoon/network/services/__init__.py +0 -0
  32. kameleoon-client-python-3.3.3/kameleoon/network/services/automation_service.py +10 -0
  33. kameleoon-client-python-3.3.3/kameleoon/network/services/automation_service_impl.py +33 -0
  34. kameleoon-client-python-3.3.3/kameleoon/network/services/configuration_service.py +10 -0
  35. kameleoon-client-python-3.3.3/kameleoon/network/services/configuration_service_impl.py +30 -0
  36. kameleoon-client-python-3.3.3/kameleoon/network/services/data_service.py +31 -0
  37. kameleoon-client-python-3.3.3/kameleoon/network/services/data_service_impl.py +56 -0
  38. kameleoon-client-python-3.3.3/kameleoon/network/services/service.py +4 -0
  39. kameleoon-client-python-3.3.3/kameleoon/network/services/service_impl.py +73 -0
  40. kameleoon-client-python-3.3.3/kameleoon/network/uri_helper.py +5 -0
  41. kameleoon-client-python-3.3.3/kameleoon/network/url_provider.py +109 -0
  42. kameleoon-client-python-3.3.3/kameleoon/real_time/__init__.py +0 -0
  43. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/sdk_version.py +1 -1
  44. kameleoon-client-python-3.3.3/kameleoon/storage/__init__.py +0 -0
  45. kameleoon-client-python-3.3.3/kameleoon/targeting/__init__.py +0 -0
  46. kameleoon-client-python-3.3.3/kameleoon/targeting/conditions/__init__.py +0 -0
  47. kameleoon-client-python-3.3.3/kameleoon/types/__init__.py +0 -0
  48. kameleoon-client-python-3.3.3/kameleoon/types/remote_visitor_data_filter.py +86 -0
  49. kameleoon-client-python-3.3.3/kameleoon/types/variable.py +26 -0
  50. kameleoon-client-python-3.3.3/kameleoon/types/variation.py +35 -0
  51. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/PKG-INFO +1 -1
  52. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/SOURCES.txt +50 -0
  53. kameleoon-client-python-3.3.3/tests/integration/proj/__init__.py +0 -0
  54. kameleoon-client-python-3.3.3/tests/integration/proj/kameleoon_app/__init__.py +0 -0
  55. kameleoon-client-python-3.3.3/tests/integration/proj/kameleoon_app/migrations/__init__.py +0 -0
  56. kameleoon-client-python-3.3.3/tests/integration/proj/proj/__init__.py +0 -0
  57. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/MANIFEST.in +0 -0
  58. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/README.md +0 -0
  59. {kameleoon-client-python-3.3.2/kameleoon/helpers → kameleoon-client-python-3.3.3/kameleoon/client_readiness}/__init__.py +0 -0
  60. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/__init__.py +0 -0
  61. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/custom_data_info.py +0 -0
  62. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/data_file.py +0 -0
  63. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/feature_flag.py +0 -0
  64. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/rule.py +0 -0
  65. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/rule_type.py +0 -0
  66. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/settings.py +0 -0
  67. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variable.py +0 -0
  68. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variation.py +0 -0
  69. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variation_by_exposition.py +0 -0
  70. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/__init__.py +0 -0
  71. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/browser.py +0 -0
  72. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/conversion.py +0 -0
  73. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/cookie.py +0 -0
  74. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/custom_data.py +0 -0
  75. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/data.py +0 -0
  76. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/device.py +0 -0
  77. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/geolocation.py +0 -0
  78. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/kcs_heat.py +0 -0
  79. {kameleoon-client-python-3.3.2/kameleoon/real_time → kameleoon-client-python-3.3.3/kameleoon/data/manager}/__init__.py +0 -0
  80. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/operating_system.py +0 -0
  81. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/page_view.py +0 -0
  82. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/user_agent.py +0 -0
  83. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/visitor_visits.py +0 -0
  84. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/defaults.py +0 -0
  85. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/exceptions.py +0 -0
  86. {kameleoon-client-python-3.3.2/kameleoon/storage → kameleoon-client-python-3.3.3/kameleoon/helpers}/__init__.py +0 -0
  87. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/functions.py +0 -0
  88. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/logger.py +0 -0
  89. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/multi_threading.py +0 -0
  90. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/nonce.py +0 -0
  91. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/repeat_timer.py +0 -0
  92. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/scheduler.py +0 -0
  93. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/visitor_code.py +0 -0
  94. {kameleoon-client-python-3.3.2/kameleoon/targeting → kameleoon-client-python-3.3.3/kameleoon/hybrid}/__init__.py +0 -0
  95. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client_config.py +0 -0
  96. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client_factory.py +0 -0
  97. {kameleoon-client-python-3.3.2/kameleoon/targeting/conditions → kameleoon-client-python-3.3.3/kameleoon/managers}/__init__.py +0 -0
  98. {kameleoon-client-python-3.3.2/tests/integration/proj → kameleoon-client-python-3.3.3/kameleoon/managers/remote_data}/__init__.py +0 -0
  99. {kameleoon-client-python-3.3.2/tests/integration/proj/kameleoon_app → kameleoon-client-python-3.3.3/kameleoon/managers/warehouse}/__init__.py +0 -0
  100. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/middleware.py +0 -0
  101. {kameleoon-client-python-3.3.2/tests/integration/proj/kameleoon_app/migrations → kameleoon-client-python-3.3.3/kameleoon/network}/__init__.py +0 -0
  102. {kameleoon-client-python-3.3.2/tests/integration/proj/proj → kameleoon-client-python-3.3.3/kameleoon/network/cookie}/__init__.py +0 -0
  103. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/real_time/real_time_configuration_service.py +0 -0
  104. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/real_time/real_time_event.py +0 -0
  105. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache.py +0 -0
  106. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_factory.py +0 -0
  107. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_factory_impl.py +0 -0
  108. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_impl.py +0 -0
  109. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/browser_condition.py +0 -0
  110. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/constants.py +0 -0
  111. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/conversion_condition.py +0 -0
  112. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/cookie_condition.py +0 -0
  113. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/custom_datum.py +0 -0
  114. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/device_condition.py +0 -0
  115. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/exclusive_feature_flag_condition.py +0 -0
  116. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/geolocation_condition.py +0 -0
  117. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/kcs_heat_range_condition.py +0 -0
  118. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/number_condition.py +0 -0
  119. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/operating_system_condition.py +0 -0
  120. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_title_condition.py +0 -0
  121. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_url_condition.py +0 -0
  122. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_view_number_condition.py +0 -0
  123. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/previous_page_condition.py +0 -0
  124. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/sdk_language_condition.py +0 -0
  125. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/segment_condition.py +0 -0
  126. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/string_value_condition.py +0 -0
  127. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/target_feature_flag_condition.py +0 -0
  128. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/targeting_condition.py +0 -0
  129. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.py +0 -0
  130. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/unknown_condition.py +0 -0
  131. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visit_number_today_condition.py +0 -0
  132. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visit_number_total_condition.py +0 -0
  133. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visitor_code_condition.py +0 -0
  134. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visitor_new_return_condition.py +0 -0
  135. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/models.py +0 -0
  136. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/targeting_manager.py +0 -0
  137. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/tree_builder.py +0 -0
  138. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/tree_condition_factory.py +0 -0
  139. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/dependency_links.txt +0 -0
  140. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/requires.txt +16 -16
  141. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/top_level.txt +0 -0
  142. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/setup.cfg +0 -0
  143. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/setup.py +0 -0
  144. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/admin.py +0 -0
  145. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/apps.py +0 -0
  146. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/models.py +0 -0
  147. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/tests.py +0 -0
  148. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/views.py +0 -0
  149. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/manage.py +0 -0
  150. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/asgi.py +0 -0
  151. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/settings.py +0 -0
  152. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/urls.py +0 -0
  153. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/wsgi.py +0 -0
  154. {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/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.3.2
3
+ Version: 3.3.3
4
4
  Summary: Kameleoon Client Python Software Development Kit.
5
5
  Home-page: https://developers.kameleoon.com/python-sdk.html
6
6
  Author: Kameleoon
@@ -3,4 +3,5 @@
3
3
  from .sdk_version import __version__ # noqa: F401
4
4
  from .kameleoon_client import KameleoonClient # noqa: F401
5
5
  from .kameleoon_client_config import KameleoonClientConfig # noqa: F401
6
+ from .kameleoon_client_factory import KameleoonClientFactory # noqa: F401
6
7
  from .middleware import KameleoonWSGIMiddleware # noqa: F401
@@ -0,0 +1,48 @@
1
+ "Async Client Readiness"
2
+
3
+ import asyncio
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from collections import defaultdict
6
+ from typing import Dict
7
+ from kameleoon.client_readiness.threading_readiness import ThreadingClientReadiness
8
+
9
+
10
+ class AsyncClientReadiness:
11
+ """
12
+ A class that provides an asynchronous interface for checking client readiness.
13
+
14
+ This class wraps around a `ThreadingClientReadiness` object to provide asynchronous
15
+ methods for waiting for the client to be ready. It uses a thread pool executor to
16
+ run blocking operations in a separate thread.
17
+ """
18
+
19
+ def __init__(self, inner: ThreadingClientReadiness) -> None:
20
+ """
21
+ Initializes an AsyncClientReadiness instance.
22
+ """
23
+ super().__init__()
24
+ self._inner = inner
25
+ self._executor: Dict[int, ThreadPoolExecutor] = defaultdict(ThreadPoolExecutor)
26
+
27
+ @property
28
+ def inner(self) -> ThreadingClientReadiness:
29
+ """
30
+ Returns the inner ThreadingClientReadiness instance.
31
+ """
32
+ return self._inner
33
+
34
+ def dispose_on_set(self) -> None:
35
+ """
36
+ Disposes of the executors by clearing the executor dictionary.
37
+ """
38
+ self._executor.clear()
39
+
40
+ async def wait(self) -> bool:
41
+ """
42
+ Asynchronously waits for the client readiness operation to complete.
43
+ """
44
+ if self._inner.is_initializing:
45
+ executor = self._executor[0]
46
+ loop = asyncio.get_event_loop()
47
+ await loop.run_in_executor(executor, self._inner.wait)
48
+ return self._inner.success
@@ -0,0 +1,36 @@
1
+ "ThreadingClientReadiness"
2
+ from threading import Lock
3
+
4
+
5
+ class ThreadingClientReadiness:
6
+ def __init__(self) -> None:
7
+ self._is_initializing = False
8
+ self._success = False
9
+ self._condition = Lock()
10
+ self.reset()
11
+
12
+ @property
13
+ def is_initializing(self) -> bool:
14
+ return self._is_initializing
15
+
16
+ @property
17
+ def success(self) -> bool:
18
+ return self._success
19
+
20
+ def reset(self) -> None:
21
+ self._success = False
22
+ if not self._is_initializing:
23
+ self._is_initializing = True
24
+ self._condition.acquire()
25
+
26
+ def set(self, success: bool) -> None:
27
+ self._success = success
28
+ if self._is_initializing:
29
+ self._condition.release()
30
+ self._is_initializing = False
31
+
32
+ def wait(self) -> bool:
33
+ if self._is_initializing:
34
+ with self._condition:
35
+ pass
36
+ return self._success
@@ -0,0 +1,55 @@
1
+ import time
2
+ from typing import Optional
3
+ from kameleoon.configuration.rule_type import RuleType
4
+ from kameleoon.data.data import BaseData, DataType
5
+ from kameleoon.network.sendable import DuplicationUnsafeSendableBase
6
+ from kameleoon.network.query_builder import QueryBuilder, QueryParam, QueryParams
7
+
8
+
9
+ class AssignedVariation(BaseData, DuplicationUnsafeSendableBase):
10
+ EVENT_TYPE = "experiment"
11
+
12
+ def __init__(
13
+ self,
14
+ experiment_id: int,
15
+ variation_id: int,
16
+ rule_type: RuleType = RuleType.UNKNOWN,
17
+ assignment_time: Optional[float] = None,
18
+ ) -> None:
19
+ super().__init__()
20
+ self.__experiment_id = experiment_id
21
+ self.__variation_id = variation_id
22
+ self.__rule_type = rule_type
23
+ self.__assignment_time = assignment_time or time.time()
24
+
25
+ @property
26
+ def experiment_id(self) -> int:
27
+ return self.__experiment_id
28
+
29
+ @property
30
+ def variation_id(self) -> int:
31
+ return self.__variation_id
32
+
33
+ @property
34
+ def rule_type(self) -> RuleType:
35
+ return self.__rule_type
36
+
37
+ @property
38
+ def assignment_time(self) -> float:
39
+ return self.__assignment_time
40
+
41
+ @property
42
+ def data_type(self) -> DataType:
43
+ return DataType.ASSIGNED_VARIATION
44
+
45
+ def _add_query_params(self, qb: QueryBuilder) -> None:
46
+ # fmt: off
47
+ qb.extend(
48
+ QueryParam(QueryParams.EVENT_TYPE, self.EVENT_TYPE),
49
+ QueryParam(QueryParams.EXPERIMENT_ID, str(self.__experiment_id)),
50
+ QueryParam(QueryParams.VARIATION_ID, str(self.__variation_id)),
51
+ )
52
+ # fmt: on
53
+
54
+ def is_valid(self, respool_time: Optional[int]) -> bool:
55
+ return (respool_time is None) or (self.assignment_time >= respool_time)
@@ -0,0 +1,34 @@
1
+ """Page view visit"""
2
+ import time
3
+ from typing import Optional
4
+ from kameleoon.data import DataType
5
+ from kameleoon.data.data import BaseData
6
+ from kameleoon.data.page_view import PageView
7
+
8
+
9
+ class PageViewVisit(BaseData):
10
+ """Page view visit"""
11
+ def __init__(self, page_view: PageView, count=1, last_timestamp: Optional[int] = None) -> None:
12
+ super().__init__()
13
+ self.page_view = page_view
14
+ self.count = count
15
+ self.last_timestamp = last_timestamp or int(time.time() * 1000)
16
+
17
+ def overwrite(self, new_page_view: PageView) -> None:
18
+ self.page_view = new_page_view
19
+ self.count += 1
20
+
21
+ def merge(self, page_view_visit) -> None:
22
+ self.count += page_view_visit.count
23
+ self.last_timestamp = max(self.last_timestamp, page_view_visit.last_timestamp)
24
+
25
+ def increase_page_visits(self) -> None:
26
+ self.count += 1
27
+
28
+ @property
29
+ def data_type(self) -> DataType:
30
+ return DataType.PAGE_VIEW_VISIT
31
+
32
+ def __eq__(self, other: object) -> bool:
33
+ return isinstance(other, PageViewVisit) and (other.page_view == self.page_view) and (
34
+ other.count == self.count) and (other.last_timestamp == self.last_timestamp)
@@ -0,0 +1,261 @@
1
+ from threading import Lock
2
+ from typing import Any, Callable, Optional, Dict, List, Iterator, cast
3
+
4
+ from logging import Logger
5
+ from kameleoon.data.visitor_visits import VisitorVisits
6
+ from kameleoon.data.data import Data
7
+ from kameleoon.data.browser import Browser
8
+ from kameleoon.data.conversion import Conversion
9
+ from kameleoon.data.cookie import Cookie
10
+ from kameleoon.data.custom_data import CustomData
11
+ from kameleoon.data.device import Device
12
+ from kameleoon.data.geolocation import Geolocation
13
+ from kameleoon.data.kcs_heat import KcsHeat
14
+ from kameleoon.data.operating_system import OperatingSystem
15
+ from kameleoon.data.page_view import PageView
16
+ from kameleoon.data.user_agent import UserAgent
17
+ from kameleoon.data.manager.assigned_variation import AssignedVariation
18
+ from kameleoon.data.manager.page_view_visit import PageViewVisit
19
+ from kameleoon.network.sendable import Sendable
20
+
21
+
22
+ EMPTY_DICT: Dict[Any, Any] = {}
23
+ EMPTY_LIST: List[Any] = []
24
+
25
+
26
+ class Visitor:
27
+ def __init__(self) -> None:
28
+ self._lock = Lock()
29
+ self._user_agent: Optional[str] = None
30
+ self._device: Optional[Device] = None
31
+ self._browser: Optional[Browser] = None
32
+ self._geolocation: Optional[Geolocation] = None
33
+ self._operating_system: Optional[OperatingSystem] = None
34
+ self._cookie: Optional[Cookie] = None
35
+ self._custom_data_dict: Optional[Dict[int, CustomData]] = None
36
+ self._page_view_visits: Optional[Dict[str, PageViewVisit]] = None
37
+ self._conversions: Optional[List[Conversion]] = None
38
+ self._variations: Optional[Dict[int, AssignedVariation]] = None
39
+ self._kcs_heat: Optional[KcsHeat] = None
40
+ self._visitor_visits: Optional[VisitorVisits] = None
41
+ self._mapping_identifier: Optional[str] = None
42
+ self.legal_consent = False
43
+
44
+ def enumerate_sendable_data(self) -> Iterator[Sendable]:
45
+ if self._device:
46
+ yield self._device
47
+ if self._browser:
48
+ yield self._browser
49
+ if self._geolocation:
50
+ yield self._geolocation
51
+ if self._operating_system:
52
+ yield self._operating_system
53
+ if self._custom_data_dict is not None:
54
+ yield from list(self._custom_data_dict.values())
55
+ if self._page_view_visits is not None:
56
+ yield from (visit.page_view for visit in list(self._page_view_visits.values()))
57
+ if self._conversions is not None:
58
+ yield from list(self._conversions)
59
+ if self._variations is not None:
60
+ yield from list(self._variations.values())
61
+
62
+ def count_sendable_data(self) -> int:
63
+ count = 0
64
+ if self._device is not None:
65
+ count += 1
66
+ if self._browser is not None:
67
+ count += 1
68
+ if self._geolocation is not None:
69
+ count += 1
70
+ if self._operating_system is not None:
71
+ count += 1
72
+ if self._custom_data_dict is not None:
73
+ count += len(self._custom_data_dict)
74
+ if self._page_view_visits is not None:
75
+ count += len(self._page_view_visits)
76
+ if self._conversions is not None:
77
+ count += len(self._conversions)
78
+ if self._variations is not None:
79
+ count += len(self._variations)
80
+ return count
81
+
82
+ @property
83
+ def user_agent(self) -> Optional[str]:
84
+ return self._user_agent
85
+
86
+ @property
87
+ def device(self) -> Optional[Device]:
88
+ return self._device
89
+
90
+ @property
91
+ def browser(self) -> Optional[Browser]:
92
+ return self._browser
93
+
94
+ @property
95
+ def geolocation(self) -> Optional[Geolocation]:
96
+ return self._geolocation
97
+
98
+ @property
99
+ def operating_system(self) -> Optional[OperatingSystem]:
100
+ return self._operating_system
101
+
102
+ @property
103
+ def cookie(self) -> Optional[Cookie]:
104
+ return self._cookie
105
+
106
+ @property
107
+ def custom_data(self) -> Dict[int, CustomData]:
108
+ return EMPTY_DICT if self._custom_data_dict is None else self._custom_data_dict.copy()
109
+
110
+ @property
111
+ def page_view_visits(self) -> Dict[str, PageViewVisit]:
112
+ return EMPTY_DICT if self._page_view_visits is None else self._page_view_visits.copy()
113
+
114
+ @property
115
+ def conversions(self) -> List[Conversion]:
116
+ return EMPTY_LIST if self._conversions is None else self._conversions.copy()
117
+
118
+ @property
119
+ def variations(self) -> Dict[int, AssignedVariation]:
120
+ return EMPTY_DICT if self._variations is None else self._variations.copy()
121
+
122
+ @property
123
+ def kcs_heat(self) -> Optional[KcsHeat]:
124
+ return self._kcs_heat
125
+
126
+ @property
127
+ def visitor_visits(self) -> Optional[VisitorVisits]:
128
+ return self._visitor_visits
129
+
130
+ def assign_variation(self, variation: AssignedVariation) -> None:
131
+ if self._variations is None:
132
+ with self._lock:
133
+ if self._variations is None:
134
+ self._variations = {variation.experiment_id: variation}
135
+ return
136
+ self._variations[variation.experiment_id] = variation
137
+
138
+ def add_data(self, *args, overwrite: bool = True, logger: Optional[Logger] = None) -> None:
139
+ with self._lock:
140
+ for data in args:
141
+ data_type = type(data)
142
+ data_adder = DATA_ADDERS.get(data_type)
143
+ if data_adder:
144
+ data_adder(self.DataAddingContext(self, data, overwrite, logger))
145
+ elif logger:
146
+ logger.error(f"Data has unsupported type '{data_type}'")
147
+
148
+ @property
149
+ def mapping_identifier(self):
150
+ return self._mapping_identifier
151
+
152
+ class DataAddingContext:
153
+ def __init__(
154
+ self, visitor: "Visitor", data: Data, overwrite: bool = True, logger: Optional[Logger] = None
155
+ ) -> None:
156
+ self.visitor = visitor
157
+ self.data = data
158
+ self.overwrite = overwrite
159
+ self.logger = logger
160
+
161
+ @staticmethod
162
+ def _set_user_agent(c: DataAddingContext) -> None:
163
+ c.visitor._user_agent = cast(UserAgent, c.data).value
164
+
165
+ @staticmethod
166
+ def _set_device(c: DataAddingContext) -> None:
167
+ if c.overwrite or c.visitor._device is None:
168
+ c.visitor._device = cast(Device, c.data)
169
+
170
+ @staticmethod
171
+ def _add_variation(c: DataAddingContext) -> None:
172
+ variation = cast(AssignedVariation, c.data)
173
+ if c.visitor._variations is None:
174
+ c.visitor._variations = {}
175
+ if c.overwrite or variation.experiment_id not in c.visitor.variations:
176
+ c.visitor._variations[variation.experiment_id] = variation
177
+
178
+ @staticmethod
179
+ def _set_browser(c: DataAddingContext) -> None:
180
+ if c.overwrite or c.visitor._browser is None:
181
+ c.visitor._browser = cast(Browser, c.data)
182
+
183
+ @staticmethod
184
+ def _set_geolocation(c: DataAddingContext) -> None:
185
+ if c.overwrite or c.visitor._geolocation is None:
186
+ c.visitor._geolocation = cast(Geolocation, c.data)
187
+
188
+ @staticmethod
189
+ def _set_operating_system(c: DataAddingContext) -> None:
190
+ if c.overwrite or c.visitor._operating_system is None:
191
+ c.visitor._operating_system = cast(OperatingSystem, c.data)
192
+
193
+ @staticmethod
194
+ def _set_cookie(c: DataAddingContext) -> None:
195
+ c.visitor._cookie = cast(Cookie, c.data)
196
+
197
+ @staticmethod
198
+ def _add_custom_data(c: DataAddingContext) -> None:
199
+ custom_data = cast(CustomData, c.data)
200
+ if c.visitor._custom_data_dict is None:
201
+ c.visitor._custom_data_dict = {}
202
+ if c.overwrite or custom_data.id not in c.visitor._custom_data_dict:
203
+ c.visitor._custom_data_dict[custom_data.id] = custom_data
204
+
205
+ @staticmethod
206
+ def _add_page_view(c: DataAddingContext) -> None:
207
+ page_view = cast(PageView, c.data)
208
+ if len(page_view.url) == 0:
209
+ if c.logger:
210
+ c.logger.error("Passed PageView data is invalid because of empty 'url' field; the data was ignored.")
211
+ return
212
+ if c.visitor._page_view_visits is None:
213
+ c.visitor._page_view_visits = {page_view.url: PageViewVisit(page_view)}
214
+ else:
215
+ if visit := c.visitor._page_view_visits.get(page_view.url):
216
+ visit.overwrite(page_view)
217
+ else:
218
+ c.visitor._page_view_visits[page_view.url] = PageViewVisit(page_view)
219
+
220
+ @staticmethod
221
+ def _add_page_view_visit(c: DataAddingContext) -> None:
222
+ page_view_visit = cast(PageViewVisit, c.data)
223
+ if c.visitor._page_view_visits is None:
224
+ c.visitor._page_view_visits = {}
225
+ if visit := c.visitor._page_view_visits.get(page_view_visit.page_view.url):
226
+ visit.merge(page_view_visit)
227
+ else:
228
+ c.visitor._page_view_visits[page_view_visit.page_view.url] = page_view_visit
229
+
230
+ @staticmethod
231
+ def _add_conversion(c: DataAddingContext) -> None:
232
+ conversion = cast(Conversion, c.data)
233
+ if c.visitor._conversions is None:
234
+ c.visitor._conversions = [conversion]
235
+ else:
236
+ c.visitor._conversions.append(conversion)
237
+
238
+ @staticmethod
239
+ def _set_kcs_heat(c: DataAddingContext) -> None:
240
+ c.visitor._kcs_heat = cast(KcsHeat, c.data)
241
+
242
+ @staticmethod
243
+ def _set_visitor_visits(c: DataAddingContext) -> None:
244
+ c.visitor._visitor_visits = cast(VisitorVisits, c.data)
245
+
246
+
247
+ DATA_ADDERS: Dict[type, Callable[[Visitor.DataAddingContext], None]] = {
248
+ UserAgent: Visitor._set_user_agent,
249
+ Device: Visitor._set_device,
250
+ Browser: Visitor._set_browser,
251
+ Geolocation: Visitor._set_geolocation,
252
+ OperatingSystem: Visitor._set_operating_system,
253
+ Cookie: Visitor._set_cookie,
254
+ CustomData: Visitor._add_custom_data,
255
+ PageView: Visitor._add_page_view,
256
+ PageViewVisit: Visitor._add_page_view_visit,
257
+ Conversion: Visitor._add_conversion,
258
+ KcsHeat: Visitor._set_kcs_heat,
259
+ VisitorVisits: Visitor._set_visitor_visits,
260
+ AssignedVariation: Visitor._add_variation,
261
+ }
@@ -0,0 +1,112 @@
1
+ import collections
2
+ import time
3
+ from typing import Dict, Iterator, Optional
4
+ from logging import Logger
5
+ from kameleoon.data import CustomData
6
+ from kameleoon.configuration.custom_data_info import CustomDataInfo
7
+ from kameleoon.data.manager.visitor import Visitor
8
+ from kameleoon.data.manager.visitor_slot import VisitorSlot
9
+ from kameleoon.helpers.scheduler import Scheduler
10
+
11
+
12
+ class VisitorManager:
13
+
14
+ def __init__(self, expiration_period: float, scheduler: Scheduler) -> None:
15
+ self.__expiration_period = expiration_period
16
+ self._slots: Dict[str, VisitorSlot] = collections.defaultdict(VisitorSlot)
17
+ self.custom_data_info: Optional[CustomDataInfo] = None
18
+ scheduler.schedule_job("VisitorManager.purge", expiration_period, self.__purge)
19
+
20
+ def __iter__(self) -> Iterator[str]:
21
+ yield from list(self._slots)
22
+
23
+ def __len__(self) -> int:
24
+ return len(self._slots)
25
+
26
+ def get_visitor(self, visitor_code: str) -> Optional[Visitor]:
27
+ if (slot := self.__try_acquire_slot(visitor_code)) is None:
28
+ return None
29
+ try:
30
+ if slot.visitor is not None:
31
+ slot.update_last_activity_time()
32
+ return slot.visitor
33
+ finally:
34
+ slot.lock.release()
35
+
36
+ def get_or_create_visitor(self, visitor_code: str) -> Visitor:
37
+ slot = self.__acquire_slot(visitor_code)
38
+ try:
39
+ if slot.visitor is None:
40
+ slot.visitor = Visitor()
41
+ else:
42
+ slot.update_last_activity_time()
43
+ return slot.visitor
44
+ finally:
45
+ slot.lock.release()
46
+
47
+ def add_data(self, visitor_code: str, *args, logger: Optional[Logger] = None):
48
+ visitor = self.get_or_create_visitor(visitor_code)
49
+ for custom_data in args:
50
+ if not isinstance(custom_data, CustomData):
51
+ continue
52
+ # We shouldn't send custom data with local only type
53
+ if self.custom_data_info and self.custom_data_info.is_local_only(custom_data.id):
54
+ custom_data.mark_as_sent()
55
+ # If mappingIdentifier is passed, we should link anonymous visitor with real unique userId.
56
+ # After authorization, customer must be able to continue work with userId, but hash for variation
57
+ # should be calculated based on anonymous visitor code, that's why set MappingIdentifier to visitor.
58
+ if (
59
+ self.custom_data_info is not None
60
+ and self.custom_data_info.is_mapping_identifier(custom_data.id)
61
+ and len(custom_data.values) > 0
62
+ and len(custom_data.values[0]) > 0
63
+ ):
64
+ custom_data.is_mapping_identifier = True
65
+ visitor._mapping_identifier = visitor_code
66
+ target_visitor_code = custom_data.values[0]
67
+ if visitor_code != target_visitor_code:
68
+ slot = self.__acquire_slot(target_visitor_code)
69
+ try:
70
+ slot.visitor = visitor
71
+ finally:
72
+ slot.lock.release()
73
+ visitor.add_data(*args, logger=logger)
74
+ return visitor
75
+
76
+ def __purge(self) -> None:
77
+ expired_time = time.time() - self.__expiration_period
78
+ for visitor_code in list(self._slots):
79
+ if slot := self.__try_acquire_slot_once(visitor_code):
80
+ try:
81
+ if (slot.visitor is None) or (slot.last_activity_time < expired_time):
82
+ self._slots.pop(visitor_code, None)
83
+ finally:
84
+ slot.lock.release()
85
+
86
+ def __acquire_slot(self, visitor_code: str) -> VisitorSlot:
87
+ while True:
88
+ slot = self._slots[visitor_code]
89
+ slot.lock.acquire()
90
+ if slot == self._slots.get(visitor_code):
91
+ return slot
92
+ slot.lock.release()
93
+
94
+ def __try_acquire_slot(self, visitor_code: str) -> Optional[VisitorSlot]:
95
+ while True:
96
+ slot = self._slots.get(visitor_code)
97
+ if slot is None:
98
+ return None
99
+ slot.lock.acquire()
100
+ if slot == self._slots.get(visitor_code):
101
+ return slot
102
+ slot.lock.release()
103
+
104
+ def __try_acquire_slot_once(self, visitor_code: str) -> Optional[VisitorSlot]:
105
+ slot = self._slots.get(visitor_code)
106
+ if slot is None:
107
+ return None
108
+ slot.lock.acquire()
109
+ if slot == self._slots.get(visitor_code):
110
+ return slot
111
+ slot.lock.release()
112
+ return None
@@ -0,0 +1,15 @@
1
+ import time
2
+ from threading import Lock
3
+ from typing import Optional
4
+ from kameleoon.data.manager.visitor import Visitor
5
+
6
+
7
+ class VisitorSlot:
8
+ def __init__(self) -> None:
9
+ self.lock = Lock()
10
+ self.visitor: Optional[Visitor] = None
11
+ self.last_activity_time = 0.0
12
+ self.update_last_activity_time()
13
+
14
+ def update_last_activity_time(self) -> None:
15
+ self.last_activity_time = time.time()
@@ -0,0 +1,7 @@
1
+ from typing import Dict, Optional
2
+ from kameleoon.data.manager.assigned_variation import AssignedVariation
3
+
4
+
5
+ class HybridManager:
6
+ def get_engine_tracking_code(self, visitor_variations: Optional[Dict[int, AssignedVariation]]) -> str:
7
+ raise NotImplementedError()
@@ -0,0 +1,25 @@
1
+ import time
2
+ from typing import Dict, Optional
3
+ from kameleoon.data.manager.assigned_variation import AssignedVariation
4
+ from kameleoon.hybrid.hybrid_manager import HybridManager
5
+
6
+
7
+ class HybridManagerImpl(HybridManager):
8
+ TC_INIT = "window.kameleoonQueue=window.kameleoonQueue||[];"
9
+ TC_ASSIGN_VARIATION_F = "window.kameleoonQueue.push(['Experiments.assignVariation',{0},{1}]);"
10
+ TC_TRIGGER_F = "window.kameleoonQueue.push(['Experiments.trigger',{0},true]);"
11
+ TC_ASSIGN_VARIATION_TRIGGER_F = TC_ASSIGN_VARIATION_F + TC_TRIGGER_F
12
+
13
+ def __init__(self, expiration_period: float) -> None:
14
+ super().__init__()
15
+ self._expiration_period = expiration_period
16
+
17
+ def get_engine_tracking_code(self, visitor_variations: Optional[Dict[int, AssignedVariation]]) -> str:
18
+ lines = [self.TC_INIT]
19
+ if visitor_variations:
20
+ expiration_time = time.time() - self._expiration_period
21
+ for variation in visitor_variations.values():
22
+ if variation.assignment_time > expiration_time:
23
+ line = self.TC_ASSIGN_VARIATION_TRIGGER_F.format(variation.experiment_id, variation.variation_id)
24
+ lines.append(line)
25
+ return "".join(lines)
@@ -1106,7 +1106,7 @@ class KameleoonClient:
1106
1106
  return (feature_flag, variation_key)
1107
1107
 
1108
1108
  def __assign_feature_variation(
1109
- self, visitor_code: str, rule: Optional[Rule], variation: Optional[VariationByExposition]
1109
+ self, visitor_code: str, rule: Optional[Rule], variation: Optional[VariationByExposition]
1110
1110
  ) -> None:
1111
1111
  if rule is not None:
1112
1112
  visitor = self._visitor_manager.get_or_create_visitor(visitor_code)