kameleoon-client-python 3.22.0__tar.gz → 3.22.1__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.22.0 → kameleoon_client_python-3.22.1}/PKG-INFO +16 -16
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/visitor_manager.py +11 -13
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/multi_threading.py +34 -14
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/scheduler.py +17 -3
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/string_utils.py +2 -3
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/tracking/tracking_manager.py +60 -17
- kameleoon_client_python-3.22.1/kameleoon/managers/tracking/visitor_tracking_registry.py +112 -0
- kameleoon_client_python-3.22.1/kameleoon/network/access_token_source.py +175 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/net_provider.py +2 -1
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/service_impl.py +3 -1
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/sdk_version.py +1 -1
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon_client_python.egg-info/PKG-INFO +16 -16
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon_client_python.egg-info/SOURCES.txt +1 -5
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon_client_python.egg-info/requires.txt +15 -15
- kameleoon_client_python-3.22.1/tests/benchmarks/visitor_tracking_registry_benchmark.py +416 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/proj/settings.py +0 -2
- kameleoon_client_python-3.22.0/kameleoon/managers/tracking/visitor_tracking_registry.py +0 -89
- kameleoon_client_python-3.22.0/kameleoon/network/access_token_source.py +0 -138
- kameleoon_client_python-3.22.0/kameleoon/storage/cache.py +0 -67
- kameleoon_client_python-3.22.0/kameleoon/storage/cache_factory.py +0 -17
- kameleoon_client_python-3.22.0/kameleoon/storage/cache_factory_impl.py +0 -12
- kameleoon_client_python-3.22.0/kameleoon/storage/cache_impl.py +0 -36
- kameleoon_client_python-3.22.0/tests/integration/proj/proj/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/MANIFEST.in +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/README.md +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/client_readiness/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/client_readiness/async_readiness.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/client_readiness/threading_readiness.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/consent_blocking_behaviour.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/custom_data_info.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/data_file.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/experiment.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/feature_flag.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/me_group.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/rule.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/rule_type.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/settings.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/variable.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/configuration/variation_by_exposition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/application_version.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/browser.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/cbscores.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/conversion.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/cookie.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/custom_data.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/data.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/device.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/geolocation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/kcs_heat.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/assigned_variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/forced_experiment_variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/forced_feature_variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/forced_variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/legal_consent.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/page_view_visit.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/visitor.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/manager/visitor_slot.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/mapping_identifier.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/operating_system.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/page_view.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/personalization.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/targeted_segment.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/unique_identifier.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/user_agent.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/data/visitor_visits.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/defaults.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/data_file_update_event.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/data_file_update_handler.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/event_handler.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/event_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/event_type.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/http_request_failure.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/http_request_handler.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/events/request_type.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/exceptions.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/domain.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/functions.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/hasher.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/logger.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/nonce.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/repeat_timer.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/sem_version.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/time.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/visitor_code.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/hybrid/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/hybrid/hybrid_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/hybrid/hybrid_manager_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/kameleoon_client.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/kameleoon_client_config.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/kameleoon_client_factory.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/logging/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/logging/kameleoon_logger.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/logging/log_level.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/logging/logger.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/asyncexec/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/asyncexec/async_executor.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/data/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/data/data_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/evaluation/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/evaluation/evaluated_experiment.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/remote_data/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/remote_data/remote_data_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/remote_data/remote_visitor_data.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/tracking/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/tracking/tracking_builder.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/warehouse/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/managers/warehouse/warehouse_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/middleware.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/access_token_source_factory.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/activity_event.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/cookie/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/cookie/cookie_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/net_provider_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/network_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/network_manager_factory.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/network_manager_factory_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/network_manager_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/query_builder.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/query_encodable.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/sendable.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/service_initialize_context.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/service_initialize_context_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/automation_service.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/automation_service_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/configuration_service.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/configuration_service_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/data_service.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/data_service_impl.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/services/service.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/uri_helper.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/network/url_provider.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/real_time/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/real_time/real_time_event.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/real_time/real_time_event_service.py +0 -0
- {kameleoon_client_python-3.22.0/kameleoon/storage → kameleoon_client_python-3.22.1/kameleoon/targeting}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0/kameleoon/targeting → kameleoon_client_python-3.22.1/kameleoon/targeting/conditions}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/browser_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/constants.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/conversion_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/cookie_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/custom_datum.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/device_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/exclusive_experiment_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/geolocation_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/kcs_heat_range_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/number_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/operating_system_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/page_title_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/page_url_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/page_view_number_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/previous_page_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/sdk_language_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/segment_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/string_value_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/target_experiment_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/target_feature_flag_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/target_personalization_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/targeting_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/unknown_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/version_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/visit_number_today_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/visit_number_total_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/visitor_code_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/visitor_new_return_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/conditions/visitor_scope_condition.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/models.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/targeting_manager.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/tree_builder.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/targeting/tree_condition_factory.py +0 -0
- {kameleoon_client_python-3.22.0/kameleoon/targeting/conditions → kameleoon_client_python-3.22.1/kameleoon/types}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/data_file.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/feature_flag.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/remote_visitor_data_filter.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/rule.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/variable.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/types/variation.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon_client_python.egg-info/dependency_links.txt +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon_client_python.egg-info/top_level.txt +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/setup.cfg +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/setup.py +0 -0
- {kameleoon_client_python-3.22.0/kameleoon/types → kameleoon_client_python-3.22.1/tests/integration/proj}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0/tests/integration/proj → kameleoon_client_python-3.22.1/tests/integration/proj/kameleoon_app}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/kameleoon_app/admin.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/kameleoon_app/apps.py +0 -0
- {kameleoon_client_python-3.22.0/tests/integration/proj/kameleoon_app → kameleoon_client_python-3.22.1/tests/integration/proj/kameleoon_app/migrations}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/kameleoon_app/models.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/kameleoon_app/tests.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/kameleoon_app/views.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/manage.py +0 -0
- {kameleoon_client_python-3.22.0/tests/integration/proj/kameleoon_app/migrations → kameleoon_client_python-3.22.1/tests/integration/proj/proj}/__init__.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/proj/asgi.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/proj/urls.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/integration/proj/proj/wsgi.py +0 -0
- {kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/tests/resources/config.yml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kameleoon-client-python
|
|
3
|
-
Version: 3.22.
|
|
3
|
+
Version: 3.22.1
|
|
4
4
|
Summary: Kameleoon Client Python Software Development Kit.
|
|
5
5
|
Home-page: https://developers.kameleoon.com/python-sdk.html
|
|
6
6
|
Author: Kameleoon
|
|
@@ -20,27 +20,27 @@ Requires-Dist: requests<3.0.0,>=2.25
|
|
|
20
20
|
Requires-Dist: sseclient-py<1.9.0,>=1.7.0
|
|
21
21
|
Requires-Dist: aiohttp<4.0.0,>=3.8.0
|
|
22
22
|
Provides-Extra: test
|
|
23
|
-
Requires-Dist: types-
|
|
24
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: types-python-dateutil==2.8.2; extra == "test"
|
|
24
|
+
Requires-Dist: twine==3.4.1; extra == "test"
|
|
25
25
|
Requires-Dist: sseclient-py<1.9.0,>=1.7.0; extra == "test"
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist: pytest-pythonpath==0.7.3; extra == "test"
|
|
28
|
-
Requires-Dist: pytest-ordering==0.6; extra == "test"
|
|
26
|
+
Requires-Dist: types-PyYAML==5.4.3; extra == "test"
|
|
29
27
|
Requires-Dist: aiohttp<4.0.0,>=3.8.0; extra == "test"
|
|
30
|
-
Requires-Dist: flake8==4.0.1; extra == "test"
|
|
31
|
-
Requires-Dist: pytest-asyncio==0.21.*; extra == "test"
|
|
32
|
-
Requires-Dist: pytest-cov==2.12.1; extra == "test"
|
|
33
|
-
Requires-Dist: mypy==0.991; extra == "test"
|
|
34
|
-
Requires-Dist: pylint==2.12.*; extra == "test"
|
|
35
|
-
Requires-Dist: Werkzeug==2.0.1; extra == "test"
|
|
36
28
|
Requires-Dist: pyyaml<6.1.0,>=5.4.0; extra == "test"
|
|
37
|
-
Requires-Dist: freezegun==1.1.0; extra == "test"
|
|
38
|
-
Requires-Dist: twine==3.4.1; extra == "test"
|
|
39
|
-
Requires-Dist: types-PyYAML==5.4.3; extra == "test"
|
|
40
29
|
Requires-Dist: requests<3.0.0,>=2.25; extra == "test"
|
|
30
|
+
Requires-Dist: pylint==2.12.*; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-pythonpath==0.7.3; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-django==4.5.2; extra == "test"
|
|
33
|
+
Requires-Dist: flake8==4.0.1; extra == "test"
|
|
34
|
+
Requires-Dist: pytest-cov==2.12.1; extra == "test"
|
|
41
35
|
Requires-Dist: wheel==0.36.2; extra == "test"
|
|
42
|
-
Requires-Dist:
|
|
36
|
+
Requires-Dist: freezegun==1.1.0; extra == "test"
|
|
37
|
+
Requires-Dist: types-requests==2.25.0; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-ordering==0.6; extra == "test"
|
|
43
39
|
Requires-Dist: aioresponses==0.7.*; extra == "test"
|
|
40
|
+
Requires-Dist: mypy==0.991; extra == "test"
|
|
41
|
+
Requires-Dist: Werkzeug==2.0.1; extra == "test"
|
|
42
|
+
Requires-Dist: pytest-asyncio==0.21.*; extra == "test"
|
|
43
|
+
Requires-Dist: Django==4.2.*; extra == "test"
|
|
44
44
|
Dynamic: author
|
|
45
45
|
Dynamic: author-email
|
|
46
46
|
Dynamic: classifier
|
|
@@ -67,17 +67,15 @@ class VisitorManager:
|
|
|
67
67
|
KameleoonLogger.debug("RETURN: VisitorManager.get_or_create_visitor(visitor_code: %s)", visitor_code)
|
|
68
68
|
|
|
69
69
|
def peek_visitor(self, visitor_code: str) -> Optional[Visitor]:
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
finally:
|
|
78
|
-
slot.lock.release()
|
|
70
|
+
"""
|
|
71
|
+
Retrieves a visitor by their code if it exists, otherwise returns `None`. Unlike `get_visitor`, does not
|
|
72
|
+
count as visitor activity and takes no lock: a plain read needs no protection against the purge.
|
|
73
|
+
"""
|
|
74
|
+
KameleoonLogger.debug("CALL: VisitorManager.peek_visitor(visitor_code: %s)", visitor_code)
|
|
75
|
+
slot = self._slots.get(visitor_code)
|
|
76
|
+
visitor = slot.visitor if slot is not None else None
|
|
79
77
|
KameleoonLogger.debug(
|
|
80
|
-
|
|
78
|
+
"RETURN: VisitorManager.peek_visitor(visitor_code: %s) -> (visitor: %s)", visitor_code, visitor
|
|
81
79
|
)
|
|
82
80
|
return visitor
|
|
83
81
|
|
|
@@ -180,7 +178,7 @@ class VisitorManager:
|
|
|
180
178
|
while True:
|
|
181
179
|
slot = self._slots[visitor_code]
|
|
182
180
|
slot.lock.acquire()
|
|
183
|
-
if slot
|
|
181
|
+
if slot is self._slots.get(visitor_code):
|
|
184
182
|
return slot
|
|
185
183
|
slot.lock.release()
|
|
186
184
|
|
|
@@ -190,7 +188,7 @@ class VisitorManager:
|
|
|
190
188
|
if slot is None:
|
|
191
189
|
return None
|
|
192
190
|
slot.lock.acquire()
|
|
193
|
-
if slot
|
|
191
|
+
if slot is self._slots.get(visitor_code):
|
|
194
192
|
return slot
|
|
195
193
|
slot.lock.release()
|
|
196
194
|
|
|
@@ -199,7 +197,7 @@ class VisitorManager:
|
|
|
199
197
|
if slot is None:
|
|
200
198
|
return None
|
|
201
199
|
slot.lock.acquire()
|
|
202
|
-
if slot
|
|
200
|
+
if slot is self._slots.get(visitor_code):
|
|
203
201
|
return slot
|
|
204
202
|
slot.lock.release()
|
|
205
203
|
return None
|
|
@@ -12,6 +12,10 @@ class ThreadEventLoop:
|
|
|
12
12
|
ThreadEventLoop is helper object to handle asynchronous tasks execution in a separate thread.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
# Upper bound for `stop` to wait for the running tasks and for the thread to exit; a task that never
|
|
16
|
+
# completes (or a loop thread blocked by a synchronous call) must not hang the disposal forever.
|
|
17
|
+
STOP_TIMEOUT = 10.0
|
|
18
|
+
|
|
15
19
|
def __init__(self) -> None:
|
|
16
20
|
self.loop: Optional[asyncio.AbstractEventLoop] = None
|
|
17
21
|
self.thread: Optional[threading.Thread] = None
|
|
@@ -40,17 +44,38 @@ class ThreadEventLoop:
|
|
|
40
44
|
return self.loop
|
|
41
45
|
|
|
42
46
|
def stop(self) -> None:
|
|
43
|
-
"""
|
|
47
|
+
"""
|
|
48
|
+
Stops the thread and the event loop.
|
|
49
|
+
|
|
50
|
+
Waits (up to `STOP_TIMEOUT`) for the already scheduled tasks to complete before stopping the loop.
|
|
51
|
+
"""
|
|
44
52
|
if self.thread is None:
|
|
45
53
|
return
|
|
46
54
|
self.running_allowed = False
|
|
47
|
-
time.sleep(0.001)
|
|
48
|
-
while self.count_tasks() > 0:
|
|
49
|
-
time.sleep(0.01)
|
|
50
55
|
loop = self.get_loop()
|
|
56
|
+
if threading.current_thread() is self.thread:
|
|
57
|
+
# Called from the loop's own thread (e.g. a GC-triggered `__del__`): it cannot wait for or join itself.
|
|
58
|
+
loop.call_soon(loop.stop)
|
|
59
|
+
return
|
|
60
|
+
# The draining runs on the loop itself, after everything already queued (e.g. tasks being created by
|
|
61
|
+
# `run_coroutine_threadsafe`), so it observes the tasks a cross-thread `all_tasks` poll could miss.
|
|
62
|
+
drained = asyncio.run_coroutine_threadsafe(self.__wait_for_tasks(), loop)
|
|
63
|
+
try:
|
|
64
|
+
drained.result(timeout=self.STOP_TIMEOUT)
|
|
65
|
+
except Exception: # pylint: disable=W0703
|
|
66
|
+
drained.cancel()
|
|
51
67
|
loop.call_soon_threadsafe(loop.stop)
|
|
52
|
-
self.thread.join()
|
|
53
|
-
|
|
68
|
+
self.thread.join(timeout=self.STOP_TIMEOUT)
|
|
69
|
+
if not self.thread.is_alive():
|
|
70
|
+
loop.close()
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
async def __wait_for_tasks() -> None:
|
|
74
|
+
while True:
|
|
75
|
+
tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
|
|
76
|
+
if not tasks:
|
|
77
|
+
return
|
|
78
|
+
await asyncio.wait(tasks, timeout=ThreadEventLoop.STOP_TIMEOUT)
|
|
54
79
|
|
|
55
80
|
def run_coro(self, coro: Coroutine[Any, Any, Any]) -> Optional[concurrent.futures.Future[Any]]:
|
|
56
81
|
"""
|
|
@@ -66,10 +91,6 @@ class ThreadEventLoop:
|
|
|
66
91
|
self.start()
|
|
67
92
|
return asyncio.run_coroutine_threadsafe(coro, self.get_loop())
|
|
68
93
|
|
|
69
|
-
def count_tasks(self) -> int:
|
|
70
|
-
"""Returns the number of running tasks."""
|
|
71
|
-
return len(asyncio.all_tasks(self.get_loop()))
|
|
72
|
-
|
|
73
94
|
|
|
74
95
|
def run_in_thread(
|
|
75
96
|
func: Callable[[], Any], thread_name: Optional[str] = None, with_event_loop=False
|
|
@@ -86,16 +107,15 @@ def run_in_thread(
|
|
|
86
107
|
:rtype: threading.Thread
|
|
87
108
|
"""
|
|
88
109
|
if with_event_loop:
|
|
110
|
+
|
|
89
111
|
def target():
|
|
90
112
|
loop = asyncio.new_event_loop()
|
|
91
113
|
func()
|
|
92
114
|
loop.close()
|
|
115
|
+
|
|
93
116
|
else:
|
|
94
117
|
target = func
|
|
95
|
-
thread = threading.Thread(target=target)
|
|
96
|
-
thread.daemon = True
|
|
97
|
-
if thread_name:
|
|
98
|
-
thread.setName(thread_name)
|
|
118
|
+
thread = threading.Thread(target=target, name=thread_name or None, daemon=True)
|
|
99
119
|
thread.start()
|
|
100
120
|
return thread
|
|
101
121
|
|
{kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/scheduler.py
RENAMED
|
@@ -42,10 +42,14 @@ class Scheduler:
|
|
|
42
42
|
|
|
43
43
|
def stop(self) -> None:
|
|
44
44
|
"""
|
|
45
|
-
|
|
45
|
+
Stops all scheduled jobs.
|
|
46
46
|
|
|
47
47
|
Should not be called more than once.
|
|
48
48
|
"""
|
|
49
|
+
if self._loop.is_closed():
|
|
50
|
+
# The jobs died with the loop; nothing left to stop (e.g. disposal after the host app closed its loop).
|
|
51
|
+
KameleoonLogger.debug("Scheduler is not stopped: event loop is already closed")
|
|
52
|
+
return
|
|
49
53
|
try:
|
|
50
54
|
self._loop.call_soon_threadsafe(self._stop)
|
|
51
55
|
except Exception as err: # pylint: disable=W0703
|
|
@@ -53,8 +57,7 @@ class Scheduler:
|
|
|
53
57
|
|
|
54
58
|
def _stop(self) -> None:
|
|
55
59
|
for job in self._jobs.values():
|
|
56
|
-
|
|
57
|
-
job.stop_signal.set()
|
|
60
|
+
job.stop()
|
|
58
61
|
|
|
59
62
|
class Job:
|
|
60
63
|
"""`Job` represents a scheduled job"""
|
|
@@ -65,13 +68,24 @@ class Scheduler:
|
|
|
65
68
|
self.func = func
|
|
66
69
|
self.stop_signal: Optional[asyncio.Event] = None
|
|
67
70
|
self.future: Optional[concurrent.futures.Future[Any]] = None
|
|
71
|
+
self._stop_requested = False
|
|
68
72
|
|
|
69
73
|
def run(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
70
74
|
"""Starts the job execution in the given event loop."""
|
|
71
75
|
if self.future is None:
|
|
72
76
|
self.future = asyncio.run_coroutine_threadsafe(self._cycle(), loop)
|
|
73
77
|
|
|
78
|
+
def stop(self) -> None:
|
|
79
|
+
"""Requests the job to stop. Must be called on the job's event loop."""
|
|
80
|
+
# `stop_signal` only exists once `_cycle` has started; a stop posted with `call_soon_threadsafe` right
|
|
81
|
+
# after `run` is processed before the task's first step, so the request has to be remembered as well.
|
|
82
|
+
self._stop_requested = True
|
|
83
|
+
if self.stop_signal is not None:
|
|
84
|
+
self.stop_signal.set()
|
|
85
|
+
|
|
74
86
|
async def _cycle(self) -> None:
|
|
87
|
+
if self._stop_requested:
|
|
88
|
+
return
|
|
75
89
|
self.stop_signal = asyncio.Event()
|
|
76
90
|
interval = self.interval
|
|
77
91
|
stop_task = asyncio.create_task(self.stop_signal.wait())
|
{kameleoon_client_python-3.22.0 → kameleoon_client_python-3.22.1}/kameleoon/helpers/string_utils.py
RENAMED
|
@@ -9,7 +9,7 @@ class StringUtils:
|
|
|
9
9
|
|
|
10
10
|
@staticmethod
|
|
11
11
|
def secret(secret: Optional[str]) -> str:
|
|
12
|
-
"""
|
|
12
|
+
"""Hides the string behind a fixed-width mask, keeping only a few trailing characters visible."""
|
|
13
13
|
hid_ch = '*'
|
|
14
14
|
vis_count = 4
|
|
15
15
|
|
|
@@ -21,8 +21,7 @@ class StringUtils:
|
|
|
21
21
|
if length <= vis_count:
|
|
22
22
|
return hid_ch * length
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
return secret[:length - hidden_length] + hid_ch * hidden_length
|
|
24
|
+
return hid_ch * vis_count + secret[max(length - vis_count, vis_count):]
|
|
26
25
|
|
|
27
26
|
@staticmethod
|
|
28
27
|
def bool_to_string_lower_case(data: bool) -> str:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tracking"""
|
|
2
|
-
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Iterable, List, Optional
|
|
3
4
|
from kameleoon.data.manager.visitor_manager import VisitorManager
|
|
4
5
|
from kameleoon.logging.kameleoon_logger import KameleoonLogger
|
|
5
6
|
from kameleoon.managers.asyncexec.async_executor import AsyncExecutor
|
|
@@ -7,7 +8,7 @@ from kameleoon.managers.data.data_manager import DataManager
|
|
|
7
8
|
from kameleoon.managers.tracking.tracking_builder import TrackingBuilder
|
|
8
9
|
from kameleoon.managers.tracking.visitor_tracking_registry import (
|
|
9
10
|
VisitorTrackingRegistry,
|
|
10
|
-
|
|
11
|
+
ConcurrentVisitorTrackingRegistry,
|
|
11
12
|
)
|
|
12
13
|
from kameleoon.network.net_provider import Response
|
|
13
14
|
from kameleoon.network.network_manager import NetworkManager
|
|
@@ -19,6 +20,7 @@ class TrackingManager:
|
|
|
19
20
|
"""Tracking manager"""
|
|
20
21
|
LINES_DELIMITER = "\n"
|
|
21
22
|
REQUEST_SIZE_LIMIT = 2560 * 1024 # 2.5 * 1024^2 characters
|
|
23
|
+
PROBE_MAX_VISITORS = 1
|
|
22
24
|
|
|
23
25
|
# pylint: disable=R0913
|
|
24
26
|
def __init__(
|
|
@@ -30,7 +32,8 @@ class TrackingManager:
|
|
|
30
32
|
"CALL: TrackingManager(data_manager, network_manager. visitor_manager, "
|
|
31
33
|
"track_interval_seconds: %s, scheduler)", track_interval_seconds
|
|
32
34
|
)
|
|
33
|
-
self._tracking_visitors: VisitorTrackingRegistry =
|
|
35
|
+
self._tracking_visitors: VisitorTrackingRegistry = ConcurrentVisitorTrackingRegistry(visitor_manager)
|
|
36
|
+
self._probe_mode = False
|
|
34
37
|
self._data_manager = data_manager
|
|
35
38
|
self._network_manager = network_manager
|
|
36
39
|
self._visitor_manager = visitor_manager
|
|
@@ -41,6 +44,11 @@ class TrackingManager:
|
|
|
41
44
|
"track_interval_seconds: %s, scheduler)", track_interval_seconds
|
|
42
45
|
)
|
|
43
46
|
|
|
47
|
+
@property
|
|
48
|
+
def probe_mode(self) -> bool:
|
|
49
|
+
"""Test purpose only"""
|
|
50
|
+
return self._probe_mode
|
|
51
|
+
|
|
44
52
|
def add_visitor_code(self, visitor_code: str) -> None:
|
|
45
53
|
"""Adds a visitor code to the tracking registry."""
|
|
46
54
|
KameleoonLogger.debug("CALL: TrackingManager.add_visitor_code(visitor_code: %s)", visitor_code)
|
|
@@ -54,7 +62,10 @@ class TrackingManager:
|
|
|
54
62
|
`track_all` is triggered by the interval tracking timer.
|
|
55
63
|
"""
|
|
56
64
|
KameleoonLogger.debug("CALL: TrackingManager.track_all()")
|
|
57
|
-
self.
|
|
65
|
+
if self._probe_mode:
|
|
66
|
+
self.__track_probe()
|
|
67
|
+
else:
|
|
68
|
+
self.__track(self._tracking_visitors.extract())
|
|
58
69
|
KameleoonLogger.debug("RETURN: TrackingManager.track_all()")
|
|
59
70
|
|
|
60
71
|
def track_visitor(self, visitor_code: str) -> None:
|
|
@@ -63,7 +74,14 @@ class TrackingManager:
|
|
|
63
74
|
self.__track([visitor_code])
|
|
64
75
|
KameleoonLogger.debug("RETURN: TrackingManager.track_visitor(visitor_code: %s)", visitor_code)
|
|
65
76
|
|
|
66
|
-
def
|
|
77
|
+
def __track_probe(self) -> None:
|
|
78
|
+
# Sends one visitor's data; visitors without data are dropped (as on the normal path) until one is found.
|
|
79
|
+
while visitor_codes := self._tracking_visitors.extract(self.PROBE_MAX_VISITORS):
|
|
80
|
+
if self.__track(visitor_codes):
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
def __track(self, visitor_codes: Iterable[str]) -> bool:
|
|
84
|
+
"""Returns whether a request was sent."""
|
|
67
85
|
builder = TrackingBuilder(
|
|
68
86
|
visitor_codes, self._data_manager.data_file, self._visitor_manager, self.REQUEST_SIZE_LIMIT
|
|
69
87
|
)
|
|
@@ -75,33 +93,58 @@ class TrackingManager:
|
|
|
75
93
|
"If it is not caused by the peak load, decreasing the tracking interval is recommended."
|
|
76
94
|
)
|
|
77
95
|
self._tracking_visitors.add_all(builder.visitor_codes_to_keep)
|
|
78
|
-
self.__perform_tracking_request(
|
|
96
|
+
return self.__perform_tracking_request(
|
|
79
97
|
builder.visitor_codes_to_send, builder.unsent_visitor_data, builder.tracking_lines
|
|
80
98
|
)
|
|
81
99
|
|
|
82
100
|
def __perform_tracking_request(
|
|
83
101
|
self, visitor_codes: List[str], visitors_data: List[Sendable], tracking_lines: List[str]
|
|
84
|
-
) ->
|
|
102
|
+
) -> bool:
|
|
103
|
+
"""Returns whether a request was sent."""
|
|
85
104
|
if not tracking_lines:
|
|
86
|
-
return
|
|
105
|
+
return False
|
|
87
106
|
lines = self.LINES_DELIMITER.join(tracking_lines)
|
|
88
107
|
# Mark unsent data as transmitted
|
|
89
108
|
for sendable_data in visitors_data:
|
|
90
109
|
sendable_data.mark_as_transmitting()
|
|
91
110
|
|
|
92
|
-
async def call() -> Response:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
async def call() -> Optional[Response]:
|
|
112
|
+
response: Optional[Response] = None
|
|
113
|
+
try:
|
|
114
|
+
service = self._network_manager.get_service(DataService)
|
|
115
|
+
response = await service.send_tracking_data(lines)
|
|
116
|
+
except asyncio.CancelledError:
|
|
117
|
+
# Not a Data API failure: roll the data back for the next request without entering probe mode.
|
|
118
|
+
self.__handle_failed_request(visitor_codes, visitors_data)
|
|
119
|
+
raise
|
|
120
|
+
except Exception as exception: # pylint: disable=W0703
|
|
121
|
+
KameleoonLogger.error("Tracking request failed: %s", exception)
|
|
122
|
+
if (response is not None) and response.success:
|
|
96
123
|
KameleoonLogger.debug("Successful request for tracking visitors: %s, data: %s",
|
|
97
124
|
visitor_codes, visitors_data)
|
|
98
125
|
for sendable_data in visitors_data:
|
|
99
126
|
sendable_data.mark_as_sent()
|
|
127
|
+
self.__leave_probe_mode()
|
|
100
128
|
else:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for sendable_data in visitors_data:
|
|
104
|
-
sendable_data.mark_as_unsent()
|
|
105
|
-
self._tracking_visitors.add_all(visitor_codes)
|
|
129
|
+
self.__handle_failed_request(visitor_codes, visitors_data)
|
|
130
|
+
self.__enter_probe_mode()
|
|
106
131
|
return response
|
|
107
132
|
self._async_executor.run_coro(call(), "send_tracking_data")
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def __handle_failed_request(self, visitor_codes: List[str], visitors_data: List[Sendable]) -> None:
|
|
136
|
+
KameleoonLogger.debug("Failed request for tracking visitors: %s, data: %s", visitor_codes, visitors_data)
|
|
137
|
+
for sendable_data in visitors_data:
|
|
138
|
+
sendable_data.mark_as_unsent()
|
|
139
|
+
self._tracking_visitors.add_all(visitor_codes)
|
|
140
|
+
|
|
141
|
+
def __enter_probe_mode(self) -> None:
|
|
142
|
+
if not self._probe_mode:
|
|
143
|
+
self._probe_mode = True
|
|
144
|
+
KameleoonLogger.info("Tracking request failed: only one visitor's data per tracking request"
|
|
145
|
+
" will be sent until a request succeeds")
|
|
146
|
+
|
|
147
|
+
def __leave_probe_mode(self) -> None:
|
|
148
|
+
if self._probe_mode:
|
|
149
|
+
self._probe_mode = False
|
|
150
|
+
KameleoonLogger.info("Tracking request succeeded: full-size tracking requests are restored")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Tracking"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from threading import Lock
|
|
5
|
+
from typing import Dict, Iterable, List, Set
|
|
6
|
+
from kameleoon.data.manager.visitor_manager import VisitorManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VisitorTrackingRegistry:
|
|
10
|
+
"""
|
|
11
|
+
Abstract visitor tracking registry.
|
|
12
|
+
|
|
13
|
+
Must be thread-safe
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
UNLIMITED = sys.maxsize
|
|
17
|
+
DEFAULT_STORAGE_LIMIT = 1_000_000
|
|
18
|
+
DEFAULT_EXTRACTION_LIMIT = 20_000
|
|
19
|
+
LIMITED_EXTRACTION_THRESHOLD_COEFFICIENT = 2
|
|
20
|
+
REMOVAL_FACTOR = 0.8
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
visitor_manager: VisitorManager,
|
|
25
|
+
storage_limit=DEFAULT_STORAGE_LIMIT,
|
|
26
|
+
extraction_limit=DEFAULT_EXTRACTION_LIMIT,
|
|
27
|
+
) -> None:
|
|
28
|
+
self._visitor_manager = visitor_manager
|
|
29
|
+
self._storage_limit = storage_limit
|
|
30
|
+
self._extraction_limit = extraction_limit
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def visitors(self) -> Set[str]:
|
|
34
|
+
"""Test purpose only"""
|
|
35
|
+
raise NotImplementedError()
|
|
36
|
+
|
|
37
|
+
def add(self, visitor_code: str) -> None:
|
|
38
|
+
"""Adds a single visitor code to the registry."""
|
|
39
|
+
raise NotImplementedError()
|
|
40
|
+
|
|
41
|
+
def add_all(self, visitor_codes: Iterable[str]) -> None:
|
|
42
|
+
"""Adds multiple visitor codes to the registry."""
|
|
43
|
+
raise NotImplementedError()
|
|
44
|
+
|
|
45
|
+
def extract(self, limit: int = UNLIMITED) -> List[str]:
|
|
46
|
+
"""
|
|
47
|
+
Removes and returns visitor codes. A backlog under the doubled `extraction_limit` is returned at once
|
|
48
|
+
(unless `limit` is smaller); otherwise at most `min(limit, extraction_limit)` codes are returned.
|
|
49
|
+
Codes are extracted in no particular order.
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError()
|
|
52
|
+
|
|
53
|
+
def _extraction_count(self, size: int, limit: int) -> int:
|
|
54
|
+
if (size <= limit) and (size < self._extraction_limit * self.LIMITED_EXTRACTION_THRESHOLD_COEFFICIENT):
|
|
55
|
+
return size
|
|
56
|
+
return min(limit, self._extraction_limit)
|
|
57
|
+
|
|
58
|
+
def _storage_limit_after_removal(self) -> int:
|
|
59
|
+
return int(self._storage_limit * self.REMOVAL_FACTOR)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ConcurrentVisitorTrackingRegistry(VisitorTrackingRegistry):
|
|
63
|
+
"""
|
|
64
|
+
Lock-free on the hot path: `add` is a single `dict` operation, atomic under the GIL (CPython, PyPy) and under the
|
|
65
|
+
free-threaded build's per-object locking, so request threads never block on the registry - with the GIL a lock
|
|
66
|
+
there causes a convoy on every interpreter switch (~5x lower throughput, p99 of hundreds of microseconds at
|
|
67
|
+
8 threads; see `tests/benchmarks/visitor_tracking_registry_benchmark.py`). The dict is never swapped out;
|
|
68
|
+
`extract` removes with `popitem()` (O(1)) under a lock that only serializes removers, so a concurrently added
|
|
69
|
+
code is either returned now or kept for the next extraction, never lost. Nothing here depends on the order in
|
|
70
|
+
which concurrent operations interleave, only on single operations not corrupting or losing entries.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
visitor_manager: VisitorManager,
|
|
76
|
+
storage_limit=VisitorTrackingRegistry.DEFAULT_STORAGE_LIMIT,
|
|
77
|
+
extraction_limit=VisitorTrackingRegistry.DEFAULT_EXTRACTION_LIMIT,
|
|
78
|
+
) -> None:
|
|
79
|
+
super().__init__(visitor_manager, storage_limit, extraction_limit)
|
|
80
|
+
self._visitors: Dict[str, None] = {}
|
|
81
|
+
self._extract_lock = Lock()
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def visitors(self) -> Set[str]:
|
|
85
|
+
return set(self._visitors)
|
|
86
|
+
|
|
87
|
+
def add(self, visitor_code: str) -> None:
|
|
88
|
+
self._visitors[visitor_code] = None
|
|
89
|
+
|
|
90
|
+
def add_all(self, visitor_codes: Iterable[str]) -> None:
|
|
91
|
+
self._visitors.update(dict.fromkeys(visitor_codes))
|
|
92
|
+
if len(self._visitors) > self._storage_limit:
|
|
93
|
+
self.__erase_to_storage_limit()
|
|
94
|
+
|
|
95
|
+
def __erase_to_storage_limit(self) -> None:
|
|
96
|
+
with self._extract_lock:
|
|
97
|
+
# Drain with `popitem()` instead of a `for` loop over the live dict: request threads keep calling `add`
|
|
98
|
+
# meanwhile, and a loop over a dict that gets a new key fails with "dictionary changed size during
|
|
99
|
+
# iteration" - which would abort the eviction. Codes added while draining stay in the live dict.
|
|
100
|
+
drained = [self._visitors.popitem()[0] for _ in range(len(self._visitors))]
|
|
101
|
+
# `peek_visitor`: an eviction pass must not count as activity and extend the visitors' sessions.
|
|
102
|
+
self._visitors.update(
|
|
103
|
+
dict.fromkeys(vc for vc in drained if self._visitor_manager.peek_visitor(vc) is not None)
|
|
104
|
+
)
|
|
105
|
+
visitors_to_remove_count = len(self._visitors) - self._storage_limit_after_removal()
|
|
106
|
+
for _ in range(visitors_to_remove_count):
|
|
107
|
+
self._visitors.popitem()
|
|
108
|
+
|
|
109
|
+
def extract(self, limit: int = VisitorTrackingRegistry.UNLIMITED) -> List[str]:
|
|
110
|
+
with self._extract_lock:
|
|
111
|
+
count = self._extraction_count(len(self._visitors), limit)
|
|
112
|
+
return [self._visitors.popitem()[0] for _ in range(count)]
|