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.
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/PKG-INFO +1 -1
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/__init__.py +1 -0
- kameleoon-client-python-3.3.3/kameleoon/client_readiness/async_readiness.py +48 -0
- kameleoon-client-python-3.3.3/kameleoon/client_readiness/threading_readiness.py +36 -0
- kameleoon-client-python-3.3.3/kameleoon/data/manager/assigned_variation.py +55 -0
- kameleoon-client-python-3.3.3/kameleoon/data/manager/page_view_visit.py +34 -0
- kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor.py +261 -0
- kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor_manager.py +112 -0
- kameleoon-client-python-3.3.3/kameleoon/data/manager/visitor_slot.py +15 -0
- kameleoon-client-python-3.3.3/kameleoon/hybrid/hybrid_manager.py +7 -0
- kameleoon-client-python-3.3.3/kameleoon/hybrid/hybrid_manager_impl.py +25 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client.py +1 -1
- kameleoon-client-python-3.3.3/kameleoon/managers/remote_data/remote_data_manager.py +66 -0
- kameleoon-client-python-3.3.3/kameleoon/managers/remote_data/remote_visitor_data.py +225 -0
- kameleoon-client-python-3.3.3/kameleoon/managers/warehouse/warehouse_manager.py +55 -0
- kameleoon-client-python-3.3.3/kameleoon/network/access_token_source.py +107 -0
- kameleoon-client-python-3.3.3/kameleoon/network/access_token_source_factory.py +12 -0
- kameleoon-client-python-3.3.3/kameleoon/network/activity_event.py +12 -0
- kameleoon-client-python-3.3.3/kameleoon/network/cookie/cookie_manager.py +75 -0
- kameleoon-client-python-3.3.3/kameleoon/network/net_provider.py +54 -0
- kameleoon-client-python-3.3.3/kameleoon/network/net_provider_impl.py +75 -0
- kameleoon-client-python-3.3.3/kameleoon/network/network_manager.py +42 -0
- kameleoon-client-python-3.3.3/kameleoon/network/network_manager_factory.py +18 -0
- kameleoon-client-python-3.3.3/kameleoon/network/network_manager_factory_impl.py +39 -0
- kameleoon-client-python-3.3.3/kameleoon/network/network_manager_impl.py +60 -0
- kameleoon-client-python-3.3.3/kameleoon/network/query_builder.py +82 -0
- kameleoon-client-python-3.3.3/kameleoon/network/query_encodable.py +3 -0
- kameleoon-client-python-3.3.3/kameleoon/network/sendable.py +49 -0
- kameleoon-client-python-3.3.3/kameleoon/network/service_initialize_context.py +7 -0
- kameleoon-client-python-3.3.3/kameleoon/network/service_initialize_context_impl.py +12 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/__init__.py +0 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/automation_service.py +10 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/automation_service_impl.py +33 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/configuration_service.py +10 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/configuration_service_impl.py +30 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/data_service.py +31 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/data_service_impl.py +56 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/service.py +4 -0
- kameleoon-client-python-3.3.3/kameleoon/network/services/service_impl.py +73 -0
- kameleoon-client-python-3.3.3/kameleoon/network/uri_helper.py +5 -0
- kameleoon-client-python-3.3.3/kameleoon/network/url_provider.py +109 -0
- kameleoon-client-python-3.3.3/kameleoon/real_time/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/sdk_version.py +1 -1
- kameleoon-client-python-3.3.3/kameleoon/storage/__init__.py +0 -0
- kameleoon-client-python-3.3.3/kameleoon/targeting/__init__.py +0 -0
- kameleoon-client-python-3.3.3/kameleoon/targeting/conditions/__init__.py +0 -0
- kameleoon-client-python-3.3.3/kameleoon/types/__init__.py +0 -0
- kameleoon-client-python-3.3.3/kameleoon/types/remote_visitor_data_filter.py +86 -0
- kameleoon-client-python-3.3.3/kameleoon/types/variable.py +26 -0
- kameleoon-client-python-3.3.3/kameleoon/types/variation.py +35 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/PKG-INFO +1 -1
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/SOURCES.txt +50 -0
- kameleoon-client-python-3.3.3/tests/integration/proj/__init__.py +0 -0
- kameleoon-client-python-3.3.3/tests/integration/proj/kameleoon_app/__init__.py +0 -0
- kameleoon-client-python-3.3.3/tests/integration/proj/kameleoon_app/migrations/__init__.py +0 -0
- kameleoon-client-python-3.3.3/tests/integration/proj/proj/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/MANIFEST.in +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/README.md +0 -0
- {kameleoon-client-python-3.3.2/kameleoon/helpers → kameleoon-client-python-3.3.3/kameleoon/client_readiness}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/custom_data_info.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/data_file.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/feature_flag.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/rule.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/rule_type.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/settings.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variable.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variation.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/configuration/variation_by_exposition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/browser.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/conversion.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/cookie.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/custom_data.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/data.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/device.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/geolocation.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/kcs_heat.py +0 -0
- {kameleoon-client-python-3.3.2/kameleoon/real_time → kameleoon-client-python-3.3.3/kameleoon/data/manager}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/operating_system.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/page_view.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/user_agent.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/data/visitor_visits.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/defaults.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/exceptions.py +0 -0
- {kameleoon-client-python-3.3.2/kameleoon/storage → kameleoon-client-python-3.3.3/kameleoon/helpers}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/functions.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/logger.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/multi_threading.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/nonce.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/repeat_timer.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/scheduler.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/helpers/visitor_code.py +0 -0
- {kameleoon-client-python-3.3.2/kameleoon/targeting → kameleoon-client-python-3.3.3/kameleoon/hybrid}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client_config.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client_factory.py +0 -0
- {kameleoon-client-python-3.3.2/kameleoon/targeting/conditions → kameleoon-client-python-3.3.3/kameleoon/managers}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2/tests/integration/proj → kameleoon-client-python-3.3.3/kameleoon/managers/remote_data}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2/tests/integration/proj/kameleoon_app → kameleoon-client-python-3.3.3/kameleoon/managers/warehouse}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/middleware.py +0 -0
- {kameleoon-client-python-3.3.2/tests/integration/proj/kameleoon_app/migrations → kameleoon-client-python-3.3.3/kameleoon/network}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2/tests/integration/proj/proj → kameleoon-client-python-3.3.3/kameleoon/network/cookie}/__init__.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/real_time/real_time_configuration_service.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/real_time/real_time_event.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_factory.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_factory_impl.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/storage/cache_impl.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/browser_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/constants.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/conversion_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/cookie_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/custom_datum.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/device_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/exclusive_feature_flag_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/geolocation_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/kcs_heat_range_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/number_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/operating_system_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_title_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_url_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/page_view_number_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/previous_page_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/sdk_language_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/segment_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/string_value_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/target_feature_flag_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/targeting_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/unknown_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visit_number_today_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visit_number_total_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visitor_code_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/conditions/visitor_new_return_condition.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/models.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/targeting_manager.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/tree_builder.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/targeting/tree_condition_factory.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/dependency_links.txt +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/requires.txt +16 -16
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon_client_python.egg-info/top_level.txt +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/setup.cfg +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/setup.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/admin.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/apps.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/models.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/tests.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/kameleoon_app/views.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/manage.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/asgi.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/settings.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/urls.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/integration/proj/proj/wsgi.py +0 -0
- {kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/tests/resources/config.yml +0 -0
|
@@ -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)
|
{kameleoon-client-python-3.3.2 → kameleoon-client-python-3.3.3}/kameleoon/kameleoon_client.py
RENAMED
|
@@ -1106,7 +1106,7 @@ class KameleoonClient:
|
|
|
1106
1106
|
return (feature_flag, variation_key)
|
|
1107
1107
|
|
|
1108
1108
|
def __assign_feature_variation(
|
|
1109
|
-
|
|
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)
|