kameleoon-client-python 3.12.1__tar.gz → 3.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kameleoon-client-python-3.12.1/kameleoon_client_python.egg-info → kameleoon-client-python-3.14.0}/PKG-INFO +1 -1
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/custom_data_info.py +7 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/data_file.py +38 -13
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/feature_flag.py +8 -8
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/rule.py +7 -10
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/visitor.py +17 -4
- kameleoon-client-python-3.14.0/kameleoon/data/visitor_visits.py +117 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/kameleoon_client.py +48 -45
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/remote_data/remote_data_manager.py +4 -4
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/remote_data/remote_visitor_data.py +25 -15
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/net_provider.py +6 -2
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/net_provider_impl.py +3 -2
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/query_builder.py +17 -15
- kameleoon-client-python-3.14.0/kameleoon/network/services/configuration_service.py +24 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/configuration_service_impl.py +30 -15
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/data_service_impl.py +1 -1
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/url_provider.py +4 -8
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/sdk_version.py +1 -1
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/targeting_condition.py +0 -1
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.py +9 -9
- kameleoon-client-python-3.14.0/kameleoon/targeting/conditions/visit_number_today_condition.py +40 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/visit_number_total_condition.py +7 -5
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/visitor_new_return_condition.py +9 -5
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/targeting_manager.py +6 -7
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/tree_condition_factory.py +0 -4
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0/kameleoon_client_python.egg-info}/PKG-INFO +1 -1
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon_client_python.egg-info/SOURCES.txt +0 -1
- kameleoon-client-python-3.12.1/kameleoon/data/visitor_visits.py +0 -32
- kameleoon-client-python-3.12.1/kameleoon/network/services/configuration_service.py +0 -13
- kameleoon-client-python-3.12.1/kameleoon/targeting/conditions/exclusive_feature_flag_condition.py +0 -42
- kameleoon-client-python-3.12.1/kameleoon/targeting/conditions/visit_number_today_condition.py +0 -28
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/MANIFEST.in +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/README.md +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/client_readiness/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/client_readiness/async_readiness.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/client_readiness/threading_readiness.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/experiment.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/me_group.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/rule_type.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/settings.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/variable.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/variation_by_exposition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/browser.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/cbscores.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/conversion.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/cookie.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/custom_data.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/data.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/device.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/geolocation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/kcs_heat.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/assigned_variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/forced_experiment_variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/forced_feature_variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/forced_variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/page_view_visit.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/visitor_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/visitor_slot.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/mapping_identifier.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/operating_system.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/page_view.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/personalization.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/unique_identifier.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/user_agent.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/defaults.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/exceptions.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/domain.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/functions.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/hasher.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/logger.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/multi_threading.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/nonce.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/repeat_timer.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/scheduler.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/string_utils.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/helpers/visitor_code.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/hybrid/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/hybrid/hybrid_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/hybrid/hybrid_manager_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/kameleoon_client_config.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/kameleoon_client_factory.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/logging/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/logging/kameleoon_logger.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/logging/log_level.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/logging/logger.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/asyncexec/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/asyncexec/async_executor.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/data/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/data/data_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/evaluation/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/evaluation/evaluated_experiment.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/remote_data/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/tracking/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/tracking/tracking_builder.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/tracking/tracking_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/tracking/visitor_tracking_registry.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/warehouse/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/managers/warehouse/warehouse_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/middleware.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/access_token_source.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/access_token_source_factory.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/activity_event.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/cookie/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/cookie/cookie_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/network_manager.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/network_manager_factory.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/network_manager_factory_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/network_manager_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/query_encodable.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/sendable.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/service_initialize_context.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/service_initialize_context_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/automation_service.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/automation_service_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/data_service.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/service.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/services/service_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/network/uri_helper.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/real_time/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/real_time/real_time_configuration_service.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/real_time/real_time_event.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/storage/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/storage/cache.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/storage/cache_factory.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/storage/cache_factory_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/storage/cache_impl.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/browser_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/constants.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/conversion_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/cookie_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/custom_datum.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/device_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/exclusive_experiment_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/geolocation_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/kcs_heat_range_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/number_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/operating_system_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/page_title_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/page_url_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/page_view_number_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/previous_page_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/sdk_language_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/segment_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/string_value_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/target_experiment_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/target_feature_flag_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/target_personalization_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/unknown_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/conditions/visitor_code_condition.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/models.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/targeting/tree_builder.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/types/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/types/remote_visitor_data_filter.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/types/variable.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/types/variation.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon_client_python.egg-info/dependency_links.txt +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon_client_python.egg-info/requires.txt +13 -13
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon_client_python.egg-info/top_level.txt +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/setup.cfg +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/setup.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/admin.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/apps.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/migrations/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/models.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/tests.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/kameleoon_app/views.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/manage.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/proj/__init__.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/proj/asgi.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/proj/settings.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/proj/urls.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/integration/proj/proj/wsgi.py +0 -0
- {kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/tests/resources/config.yml +0 -0
|
@@ -17,6 +17,7 @@ class CustomDataInfo:
|
|
|
17
17
|
self.__mapping_identifier_index = None
|
|
18
18
|
self.__local_only = set()
|
|
19
19
|
self.__visitor_scope = set()
|
|
20
|
+
self.__custom_data_index_by_id = {}
|
|
20
21
|
if custom_data is not None:
|
|
21
22
|
for data in custom_data:
|
|
22
23
|
index = data.get("index")
|
|
@@ -26,6 +27,8 @@ class CustomDataInfo:
|
|
|
26
27
|
self.__local_only.add(index)
|
|
27
28
|
if data.get("scope") == self.SCOPE_VISITOR:
|
|
28
29
|
self.__visitor_scope.add(index)
|
|
30
|
+
if (id_ := data.get("id")) is not None:
|
|
31
|
+
self.__custom_data_index_by_id[id_] = index
|
|
29
32
|
if data.get("isMappingIdentifier"):
|
|
30
33
|
if bool(self.__mapping_identifier_index is not None):
|
|
31
34
|
KameleoonLogger.warning(
|
|
@@ -50,3 +53,7 @@ class CustomDataInfo:
|
|
|
50
53
|
def mapping_identifier_index(self) -> Optional[int]:
|
|
51
54
|
"""Take mapping identifier index"""
|
|
52
55
|
return self.__mapping_identifier_index
|
|
56
|
+
|
|
57
|
+
def get_custom_data_index_by_id(self, custom_data_id: int) -> Optional[int]:
|
|
58
|
+
"""Get corresponding custom data id by custom data index"""
|
|
59
|
+
return self.__custom_data_index_by_id.get(custom_data_id)
|
|
@@ -11,6 +11,7 @@ from kameleoon.configuration.settings import Settings
|
|
|
11
11
|
from kameleoon.configuration.variation_by_exposition import VariationByExposition
|
|
12
12
|
from kameleoon.exceptions import FeatureNotFound, FeatureEnvironmentDisabled
|
|
13
13
|
from kameleoon.logging.kameleoon_logger import KameleoonLogger
|
|
14
|
+
from kameleoon.targeting.models import Segment
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class DataFile:
|
|
@@ -19,38 +20,55 @@ class DataFile:
|
|
|
19
20
|
@staticmethod
|
|
20
21
|
def default(environment: Optional[str]) -> "DataFile":
|
|
21
22
|
"""Creates new instance of `DataFile` initialized with default values"""
|
|
22
|
-
data_file = DataFile(environment, Settings(), {}, CustomDataInfo(None))
|
|
23
|
+
data_file = DataFile(environment, None, Settings(), {}, CustomDataInfo(None))
|
|
23
24
|
return data_file
|
|
24
25
|
|
|
25
26
|
@staticmethod
|
|
26
27
|
def from_json(
|
|
27
|
-
environment: Optional[str], configuration: Dict[str, Any]
|
|
28
|
+
environment: Optional[str], last_modified: Optional[str], configuration: Dict[str, Any]
|
|
28
29
|
) -> "DataFile":
|
|
29
30
|
"""Creates new instance of `DataFile` initialized from the specified configuration JSON"""
|
|
30
31
|
settings = Settings(configuration.get("configuration"))
|
|
31
|
-
|
|
32
|
-
(feature_flag := FeatureFlag(jobj)).feature_key: feature_flag
|
|
33
|
-
for jobj in configuration.get("featureFlags") or []
|
|
34
|
-
}
|
|
32
|
+
segments = DataFile.__parse_segments(configuration)
|
|
35
33
|
custom_data_info = CustomDataInfo(configuration.get("customData"))
|
|
34
|
+
feature_flags = DataFile.__parse_feature_flags(configuration, segments, custom_data_info)
|
|
36
35
|
holdout = Experiment.from_json(configuration["holdout"]) if "holdout" in configuration else None
|
|
37
|
-
data_file = DataFile(environment, settings, feature_flags, custom_data_info, holdout)
|
|
36
|
+
data_file = DataFile(environment, last_modified, settings, feature_flags, custom_data_info, holdout)
|
|
38
37
|
return data_file
|
|
39
38
|
|
|
39
|
+
@staticmethod
|
|
40
|
+
def __parse_segments(configuration: Dict[str, Any]) -> Dict[int, Segment]:
|
|
41
|
+
return {
|
|
42
|
+
(seg := Segment(jobj)).id_: seg
|
|
43
|
+
for jobj in configuration.get("segments") or []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def __parse_feature_flags(
|
|
48
|
+
configuration: Dict[str, Any], segments: Dict[int, Segment], cdi: CustomDataInfo
|
|
49
|
+
) -> Dict[str, FeatureFlag]:
|
|
50
|
+
return {
|
|
51
|
+
(ff := FeatureFlag(jobj, segments, cdi)).feature_key: ff # pylint: disable=C0103
|
|
52
|
+
for jobj in configuration.get("featureFlags") or []
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
# pylint: disable=R0913
|
|
41
56
|
def __init__(
|
|
42
57
|
self,
|
|
43
58
|
environment: Optional[str],
|
|
59
|
+
last_modified: Optional[str],
|
|
44
60
|
settings: Settings,
|
|
45
61
|
feature_flags: Dict[str, FeatureFlag],
|
|
46
62
|
custom_data_info: CustomDataInfo,
|
|
47
63
|
holdout: Optional[Experiment] = None,
|
|
48
64
|
) -> None:
|
|
49
65
|
KameleoonLogger.debug(
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
"CALL: DataFile(environment: %s, last_modified: %s, settings: %s, feature_flags: %s,"
|
|
67
|
+
" custom_data_info: %s, holdout: %s)",
|
|
68
|
+
environment, last_modified, settings, feature_flags, custom_data_info, holdout
|
|
52
69
|
)
|
|
53
70
|
self.__environment = environment # pylint: disable=W0238
|
|
71
|
+
self.__last_modified = last_modified
|
|
54
72
|
self.__settings = settings
|
|
55
73
|
self.__feature_flags: Dict[str, FeatureFlag] = feature_flags
|
|
56
74
|
self.__me_groups = self.__make_me_groups(feature_flags.values())
|
|
@@ -69,10 +87,16 @@ class DataFile:
|
|
|
69
87
|
self.__custom_data_info = custom_data_info
|
|
70
88
|
self.__holdout = holdout
|
|
71
89
|
KameleoonLogger.debug(
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
"RETURN: DataFile(environment: %s, last_modified: %s, settings: %s, feature_flags: %s,"
|
|
91
|
+
" custom_data_info: %s, holdout: %s)",
|
|
92
|
+
environment, last_modified, settings, feature_flags, custom_data_info, holdout
|
|
74
93
|
)
|
|
75
94
|
|
|
95
|
+
@property
|
|
96
|
+
def last_modified(self) -> Optional[str]:
|
|
97
|
+
"""Returns last modified string"""
|
|
98
|
+
return self.__last_modified
|
|
99
|
+
|
|
76
100
|
@property
|
|
77
101
|
def settings(self) -> Settings:
|
|
78
102
|
"""Returns settings"""
|
|
@@ -177,11 +201,12 @@ class DataFile:
|
|
|
177
201
|
|
|
178
202
|
def __str__(self):
|
|
179
203
|
return (
|
|
180
|
-
|
|
204
|
+
"DataFile{"
|
|
181
205
|
f"environment:'{self.__environment}',"
|
|
206
|
+
f"last_modified:'{self.__last_modified}',"
|
|
182
207
|
f"feature_flags:{len(self.__feature_flags)},"
|
|
183
208
|
f"settings:{self.__settings}"
|
|
184
|
-
|
|
209
|
+
"}"
|
|
185
210
|
)
|
|
186
211
|
|
|
187
212
|
class RuleInfo:
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, List, Dict, Optional
|
|
4
4
|
|
|
5
|
+
from kameleoon.configuration.custom_data_info import CustomDataInfo
|
|
5
6
|
from kameleoon.configuration.rule import Rule
|
|
6
7
|
from kameleoon.configuration.variation import Variation
|
|
7
8
|
from kameleoon.helpers.string_utils import StringUtils
|
|
9
|
+
from kameleoon.targeting.models import Segment
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class FeatureFlag:
|
|
@@ -12,12 +14,7 @@ class FeatureFlag:
|
|
|
12
14
|
FeatureFlag is used for operating feature flags with rules
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
def from_array(array: List[Dict[str, Any]]) -> List["FeatureFlag"]:
|
|
17
|
-
"""Create a list of FeatureFlags from the json array"""
|
|
18
|
-
return [FeatureFlag(item) for item in array]
|
|
19
|
-
|
|
20
|
-
def __init__(self, dict_json: Dict[str, Any]) -> None:
|
|
17
|
+
def __init__(self, dict_json: Dict[str, Any], segments: Dict[int, Segment], cdi: CustomDataInfo) -> None:
|
|
21
18
|
self.id_: int = dict_json.get("id", 0)
|
|
22
19
|
self.feature_key: str = dict_json.get("featureKey", "")
|
|
23
20
|
self.default_variation_key: str = dict_json.get("defaultVariationKey", "")
|
|
@@ -26,7 +23,9 @@ class FeatureFlag:
|
|
|
26
23
|
dict_json.get("variations", [])
|
|
27
24
|
)
|
|
28
25
|
self.environment_enabled = dict_json.get("environmentEnabled", False)
|
|
29
|
-
self.rules: List[Rule] = Rule.from_array(dict_json.get("rules", []))
|
|
26
|
+
self.rules: List[Rule] = Rule.from_array(dict_json.get("rules", []), segments)
|
|
27
|
+
bucketing_cd_id = dict_json.get("bucketingCustomDataId")
|
|
28
|
+
self.bucketing_custom_data_index = cdi.get_custom_data_index_by_id(bucketing_cd_id) if bucketing_cd_id else None
|
|
30
29
|
|
|
31
30
|
def get_variation(self, key: str) -> Optional[Variation]:
|
|
32
31
|
"""Retrun a variation for the given key"""
|
|
@@ -41,6 +40,7 @@ class FeatureFlag:
|
|
|
41
40
|
f"variations:{StringUtils.object_to_string(self.variations)},"
|
|
42
41
|
f"default_variation_key:'{self.default_variation_key}',"
|
|
43
42
|
f"me_group_name:'{self.me_group_name}',"
|
|
44
|
-
f"rules:{len(self.rules)}"
|
|
43
|
+
f"rules:{len(self.rules)},"
|
|
44
|
+
f"bucketing_custom_data_index:{self.bucketing_custom_data_index}"
|
|
45
45
|
f"}}"
|
|
46
46
|
)
|
{kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/configuration/rule.py
RENAMED
|
@@ -12,12 +12,11 @@ class Rule:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
@staticmethod
|
|
15
|
-
def from_array(array: List[Dict[str, Any]]) -> List["Rule"]:
|
|
15
|
+
def from_array(array: List[Dict[str, Any]], segments: Dict[int, Segment]) -> List["Rule"]:
|
|
16
16
|
"""Create a list of Rules from the json array"""
|
|
17
|
+
return [Rule(item, segments) for item in array]
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def __init__(self, dict_json: Dict[str, Any]):
|
|
19
|
+
def __init__(self, dict_json: Dict[str, Any], segments: Dict[int, Segment]) -> None:
|
|
21
20
|
self.id_: int = dict_json["id"]
|
|
22
21
|
self.order: int = dict_json.get("order", 0)
|
|
23
22
|
type_literal: str = dict_json.get("type", "")
|
|
@@ -25,21 +24,19 @@ class Rule:
|
|
|
25
24
|
self.exposition: float = dict_json.get("exposition", 0.0)
|
|
26
25
|
self.experiment = Experiment.from_json(dict_json)
|
|
27
26
|
self.respool_time: Optional[int] = dict_json.get("respoolTime", None)
|
|
28
|
-
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
27
|
+
segment_id = dict_json.get("segmentId")
|
|
28
|
+
self.segment_id: int = segment_id or -1
|
|
29
|
+
self.targeting_segment: Optional[Segment] = segments.get(segment_id) if segment_id else None
|
|
31
30
|
|
|
32
31
|
@property
|
|
33
32
|
def is_experimentation(self) -> bool:
|
|
34
33
|
"""Return `true` if rule is `experimentation` type"""
|
|
35
|
-
|
|
36
34
|
return self.type == RuleType.EXPERIMENTATION
|
|
37
35
|
|
|
38
36
|
@property
|
|
39
37
|
def is_targeted_delivery(self) -> bool:
|
|
40
38
|
"""Return `true` if rule is `targeted delivery` type"""
|
|
41
|
-
|
|
42
39
|
return self.type == RuleType.TARGETED_DELIVERY
|
|
43
40
|
|
|
44
|
-
def __str__(self):
|
|
41
|
+
def __str__(self) -> str:
|
|
45
42
|
return f"Rule{{id:{self.id_}}}"
|
{kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/data/manager/visitor.py
RENAMED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Visitor"""
|
|
2
|
+
import time
|
|
3
|
+
|
|
2
4
|
from threading import Lock
|
|
3
5
|
from typing import Any, Callable, Optional, Dict, List, Iterator, Iterable, cast
|
|
4
6
|
|
|
@@ -44,6 +46,11 @@ class Visitor: # pylint: disable=R0904
|
|
|
44
46
|
"""Creates and returns a clone of the current Visitor instance which shares all the visitor data."""
|
|
45
47
|
return Visitor(self)
|
|
46
48
|
|
|
49
|
+
@property
|
|
50
|
+
def time_started(self) -> int:
|
|
51
|
+
"""Returns the current visit's start time"""
|
|
52
|
+
return self._data.time_started
|
|
53
|
+
|
|
47
54
|
def enumerate_sendable_data(self) -> Iterator[Sendable]:
|
|
48
55
|
"""Enumerates all sendable data associated with this Visitor."""
|
|
49
56
|
KameleoonLogger.debug('CALL: Visitor.enumerate_sendable_data()')
|
|
@@ -263,6 +270,7 @@ class Visitor: # pylint: disable=R0904
|
|
|
263
270
|
class VisitorData:
|
|
264
271
|
"""Visitor Data"""
|
|
265
272
|
def __init__(self) -> None:
|
|
273
|
+
self.time_started = int(time.time() * 1000)
|
|
266
274
|
self.lock = Lock()
|
|
267
275
|
self.user_agent: Optional[str] = None
|
|
268
276
|
self.device: Optional[Device] = None
|
|
@@ -293,6 +301,8 @@ class Visitor: # pylint: disable=R0904
|
|
|
293
301
|
yield self.operating_system
|
|
294
302
|
if self.geolocation:
|
|
295
303
|
yield self.geolocation
|
|
304
|
+
if self.visitor_visits:
|
|
305
|
+
yield self.visitor_visits
|
|
296
306
|
if self.custom_data_dict is not None:
|
|
297
307
|
yield from list(self.custom_data_dict.values())
|
|
298
308
|
if self.page_view_visits is not None:
|
|
@@ -309,18 +319,20 @@ class Visitor: # pylint: disable=R0904
|
|
|
309
319
|
count += 1
|
|
310
320
|
if self.browser is not None:
|
|
311
321
|
count += 1
|
|
322
|
+
if self.operating_system is not None:
|
|
323
|
+
count += 1
|
|
312
324
|
if self.geolocation is not None:
|
|
313
325
|
count += 1
|
|
314
|
-
if self.
|
|
326
|
+
if self.visitor_visits:
|
|
315
327
|
count += 1
|
|
316
328
|
if self.custom_data_dict is not None:
|
|
317
329
|
count += len(self.custom_data_dict)
|
|
318
330
|
if self.page_view_visits is not None:
|
|
319
331
|
count += len(self.page_view_visits)
|
|
320
|
-
if self.conversions is not None:
|
|
321
|
-
count += len(self.conversions)
|
|
322
332
|
if self.variations is not None:
|
|
323
333
|
count += len(self.variations)
|
|
334
|
+
if self.conversions is not None:
|
|
335
|
+
count += len(self.conversions)
|
|
324
336
|
return count
|
|
325
337
|
|
|
326
338
|
class DataAddingContext:
|
|
@@ -433,7 +445,8 @@ class Visitor: # pylint: disable=R0904
|
|
|
433
445
|
self._data.cbscores = cast(CBScores, context.data)
|
|
434
446
|
|
|
435
447
|
def _set_visitor_visits(self, context: DataAddingContext) -> None:
|
|
436
|
-
self._data.visitor_visits
|
|
448
|
+
if context.overwrite or (self._data.visitor_visits is None):
|
|
449
|
+
self._data.visitor_visits = cast(VisitorVisits, context.data).localize(self._data.time_started)
|
|
437
450
|
|
|
438
451
|
def _set_is_unique_identifier(self, context: DataAddingContext) -> None:
|
|
439
452
|
self._is_unique_identifier = cast(UniqueIdentifier, context.data).value
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Visitor visits"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from kameleoon.data import DataType
|
|
6
|
+
from kameleoon.network.sendable import DuplicationUnsafeSendableBase
|
|
7
|
+
from kameleoon.network.query_builder import QueryBuilder, QueryParam, QueryParams
|
|
8
|
+
|
|
9
|
+
from kameleoon.data.data import BaseData
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VisitorVisits(BaseData, DuplicationUnsafeSendableBase):
|
|
13
|
+
"""Visitor visits"""
|
|
14
|
+
|
|
15
|
+
EVENT_TYPE = "staticData"
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, prev_visits: List["Visit"], visit_number=0, *, time_started=0, time_since_previous_visit=0
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.__visit_number = max(visit_number, len(prev_visits))
|
|
22
|
+
self.__prev_visits = prev_visits
|
|
23
|
+
self.__time_started = time_started
|
|
24
|
+
self.__time_since_previous_visit = time_since_previous_visit
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def data_type(self) -> DataType:
|
|
28
|
+
return DataType.VISITOR_VISITS
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def visit_number(self) -> int:
|
|
32
|
+
"""Returns the visit number"""
|
|
33
|
+
return self.__visit_number
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def prev_visits(self) -> List["Visit"]:
|
|
37
|
+
"""Returns the previous visit list"""
|
|
38
|
+
return self.__prev_visits
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def time_started(self) -> int:
|
|
42
|
+
"""Returns the current visit's start time"""
|
|
43
|
+
return self.__time_started
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def time_since_previous_visit(self) -> int:
|
|
47
|
+
"""Returns the time since the previous visit"""
|
|
48
|
+
return self.__time_since_previous_visit
|
|
49
|
+
|
|
50
|
+
def localize(self, time_started: int) -> "VisitorVisits":
|
|
51
|
+
"""
|
|
52
|
+
Localizes a remote instance of `VisitorVisits` creating a new instance
|
|
53
|
+
with a specific current visit's start time.
|
|
54
|
+
"""
|
|
55
|
+
time_since_previous_visit = 0
|
|
56
|
+
for visit in self.__prev_visits:
|
|
57
|
+
time_delta = time_started - visit.time_last_activity
|
|
58
|
+
if time_delta >= 0:
|
|
59
|
+
time_since_previous_visit = time_delta
|
|
60
|
+
break
|
|
61
|
+
return VisitorVisits(
|
|
62
|
+
self.__prev_visits, self.__visit_number,
|
|
63
|
+
time_started=time_started, time_since_previous_visit=time_since_previous_visit,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _add_query_params(self, query_builder: QueryBuilder) -> None:
|
|
67
|
+
query_builder.extend(
|
|
68
|
+
QueryParam(QueryParams.EVENT_TYPE, self.EVENT_TYPE),
|
|
69
|
+
QueryParam(QueryParams.VISIT_NUMBER, str(self.__visit_number)),
|
|
70
|
+
QueryParam(QueryParams.TIME_SINCE_PREVIOUS_VISIT, str(self.__time_since_previous_visit)),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def __eq__(self, value: Any) -> bool:
|
|
74
|
+
return isinstance(value, VisitorVisits) and (self.__visit_number == value.visit_number) and \
|
|
75
|
+
(self.__prev_visits == value.prev_visits) and (self.__time_started == value.time_started) and \
|
|
76
|
+
(self.__time_since_previous_visit == value.time_since_previous_visit)
|
|
77
|
+
|
|
78
|
+
def __str__(self) -> str:
|
|
79
|
+
return (
|
|
80
|
+
"VisitorVisits{"
|
|
81
|
+
f"visit_number:{self.__visit_number},"
|
|
82
|
+
f"prev_visits:{[str(v) for v in self.__prev_visits]},"
|
|
83
|
+
f"time_started:{self.__time_started},"
|
|
84
|
+
f"time_since_previous_visit:{self.__time_since_previous_visit}"
|
|
85
|
+
"}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_visitor_visits(obj: Any) -> Tuple["VisitorVisits", bool]:
|
|
90
|
+
"""Determines if an object is `VisitorVisits` or `None` and returns a valid instance of `VisitorVisits`."""
|
|
91
|
+
if isinstance(obj, VisitorVisits):
|
|
92
|
+
return obj, True
|
|
93
|
+
return VisitorVisits([]), obj is None
|
|
94
|
+
|
|
95
|
+
class Visit:
|
|
96
|
+
"""Visit"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, time_started: int, time_last_activity: Optional[int] = None) -> None:
|
|
99
|
+
self.__time_started = time_started
|
|
100
|
+
self.__time_last_activity = time_last_activity or time_started
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def time_started(self) -> int:
|
|
104
|
+
"""Returns the start time"""
|
|
105
|
+
return self.__time_started
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def time_last_activity(self) -> int:
|
|
109
|
+
"""Returns the last activity time"""
|
|
110
|
+
return self.__time_last_activity
|
|
111
|
+
|
|
112
|
+
def __eq__(self, value: Any) -> bool:
|
|
113
|
+
return isinstance(value, VisitorVisits.Visit) and (self.__time_started == value.time_started) and \
|
|
114
|
+
(self.__time_last_activity == value.time_last_activity)
|
|
115
|
+
|
|
116
|
+
def __str__(self) -> str:
|
|
117
|
+
return f"Visit{{time_started:{self.__time_started},time_last_activity:{self.__time_last_activity}}}"
|
{kameleoon-client-python-3.12.1 → kameleoon-client-python-3.14.0}/kameleoon/kameleoon_client.py
RENAMED
|
@@ -47,7 +47,7 @@ from kameleoon.network.net_provider_impl import NetProviderImpl
|
|
|
47
47
|
from kameleoon.network.network_manager_factory import NetworkManagerFactory
|
|
48
48
|
from kameleoon.network.network_manager_factory_impl import NetworkManagerFactoryImpl
|
|
49
49
|
from kameleoon.network.sendable import Sendable
|
|
50
|
-
from kameleoon.network.services.configuration_service import ConfigurationService
|
|
50
|
+
from kameleoon.network.services.configuration_service import ConfigurationService, FetchedConfiguration
|
|
51
51
|
from kameleoon.network.cookie.cookie_manager import CookieManager
|
|
52
52
|
|
|
53
53
|
from kameleoon.real_time.real_time_configuration_service import RealTimeConfigurationService
|
|
@@ -674,7 +674,7 @@ class KameleoonClient:
|
|
|
674
674
|
if forced_variation:
|
|
675
675
|
eval_exp = EvaluatedExperiment.from_forced_variation(forced_variation)
|
|
676
676
|
elif self.__is_visitor_not_in_holdout(
|
|
677
|
-
visitor, visitor_code, track, save
|
|
677
|
+
visitor, visitor_code, track, save, feature_flag.bucketing_custom_data_index
|
|
678
678
|
) and self.__is_ff_unrestricted_by_me_group(visitor, visitor_code, feature_flag):
|
|
679
679
|
eval_exp = self.__calculate_variation_rule_for_feature(visitor_code, feature_flag)
|
|
680
680
|
if save and not (forced_variation and forced_variation.simulated):
|
|
@@ -703,7 +703,7 @@ class KameleoonClient:
|
|
|
703
703
|
unrestricted = True
|
|
704
704
|
me_group = self._data_manager.data_file.me_groups.get(feature_flag.me_group_name)
|
|
705
705
|
if me_group:
|
|
706
|
-
code_for_hash = self.__get_code_for_hash(visitor, visitor_code)
|
|
706
|
+
code_for_hash = self.__get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
|
707
707
|
me_group_hash = Hasher.obtain_hash_for_me_group(code_for_hash, feature_flag.me_group_name)
|
|
708
708
|
KameleoonLogger.debug(
|
|
709
709
|
"Calculated ME group hash %s for code: %s, meGroup: %s",
|
|
@@ -722,20 +722,18 @@ class KameleoonClient:
|
|
|
722
722
|
return unrestricted
|
|
723
723
|
|
|
724
724
|
def __is_visitor_not_in_holdout(
|
|
725
|
-
self, visitor: Optional[Visitor], visitor_code: str, track: bool, save: bool
|
|
725
|
+
self, visitor: Optional[Visitor], visitor_code: str, track: bool, save: bool, bucketing_cd_index: Optional[int]
|
|
726
726
|
) -> bool:
|
|
727
727
|
holdout = self._data_manager.data_file.holdout
|
|
728
728
|
if holdout is None:
|
|
729
729
|
return True
|
|
730
730
|
in_holdout_variation_key = "in-holdout"
|
|
731
731
|
KameleoonLogger.debug(
|
|
732
|
-
"CALL: KameleoonClient.__is_visitor_not_in_holdout(visitor, visitor_code: %s, track: %s, save: %s
|
|
733
|
-
visitor_code,
|
|
734
|
-
track,
|
|
735
|
-
save,
|
|
732
|
+
"CALL: KameleoonClient.__is_visitor_not_in_holdout(visitor, visitor_code: %s, track: %s, save: %s, "
|
|
733
|
+
"bucketing_cd_index: %s)", visitor_code, track, save, bucketing_cd_index
|
|
736
734
|
)
|
|
737
735
|
is_not_in_holdout = True
|
|
738
|
-
code_for_hash = self.__get_code_for_hash(visitor, visitor_code)
|
|
736
|
+
code_for_hash = self.__get_code_for_hash(visitor, visitor_code, bucketing_cd_index)
|
|
739
737
|
variation_hash = Hasher.obtain(code_for_hash, holdout.id_)
|
|
740
738
|
KameleoonLogger.debug("Calculated holdout hash %s for code %s", variation_hash, code_for_hash)
|
|
741
739
|
var_by_exp = holdout.get_variation(variation_hash)
|
|
@@ -745,12 +743,9 @@ class KameleoonClient:
|
|
|
745
743
|
eval_exp = EvaluatedExperiment(var_by_exp, holdout, RuleType.EXPERIMENTATION)
|
|
746
744
|
self.__save_variation(visitor_code, eval_exp)
|
|
747
745
|
KameleoonLogger.debug(
|
|
748
|
-
"RETURN: KameleoonClient.__is_visitor_not_in_holdout(visitor, visitor_code: %s, track: %s, save: %s
|
|
749
|
-
" -> (is_not_in_holdout: %s)",
|
|
750
|
-
visitor_code,
|
|
751
|
-
track,
|
|
752
|
-
save,
|
|
753
|
-
is_not_in_holdout,
|
|
746
|
+
"RETURN: KameleoonClient.__is_visitor_not_in_holdout(visitor, visitor_code: %s, track: %s, save: %s, "
|
|
747
|
+
"bucketing_cd_index: %s) -> (is_not_in_holdout: %s)",
|
|
748
|
+
visitor_code, track, save, bucketing_cd_index, is_not_in_holdout
|
|
754
749
|
)
|
|
755
750
|
return is_not_in_holdout
|
|
756
751
|
|
|
@@ -1435,13 +1430,18 @@ class KameleoonClient:
|
|
|
1435
1430
|
KameleoonLogger.debug("CALL: KameleoonClient._fetch_configuration(time_stamp: %s)", time_stamp)
|
|
1436
1431
|
success = False
|
|
1437
1432
|
try:
|
|
1438
|
-
|
|
1439
|
-
if
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1433
|
+
fetched_configuration = await self._obtain_configuration(time_stamp)
|
|
1434
|
+
if fetched_configuration:
|
|
1435
|
+
if fetched_configuration.configuration:
|
|
1436
|
+
data_file = DataFile.from_json(
|
|
1437
|
+
self._config.environment,
|
|
1438
|
+
fetched_configuration.last_modified,
|
|
1439
|
+
fetched_configuration.configuration
|
|
1440
|
+
)
|
|
1441
|
+
self._data_manager.data_file = data_file
|
|
1442
|
+
self._network_manager.url_provider.apply_data_api_domain(data_file.settings.data_api_domain)
|
|
1443
|
+
self._call_update_handler_if_needed(time_stamp is not None)
|
|
1444
|
+
success = True
|
|
1445
1445
|
except Exception as ex:
|
|
1446
1446
|
KameleoonLogger.error("Error occurred during configuration fetching: %s", ex)
|
|
1447
1447
|
self._manage_configuration_update(self._data_manager.data_file.settings.real_time_update)
|
|
@@ -1532,7 +1532,7 @@ class KameleoonClient:
|
|
|
1532
1532
|
if not self._data_manager.data_file.settings.real_time_update:
|
|
1533
1533
|
await self._fetch_configuration()
|
|
1534
1534
|
|
|
1535
|
-
async def _obtain_configuration(self, time_stamp: Optional[int]) -> Optional[
|
|
1535
|
+
async def _obtain_configuration(self, time_stamp: Optional[int]) -> Optional[FetchedConfiguration]:
|
|
1536
1536
|
"""
|
|
1537
1537
|
Obtaining configuration from CDN service.
|
|
1538
1538
|
Should be run in a separate thead.
|
|
@@ -1541,15 +1541,14 @@ class KameleoonClient:
|
|
|
1541
1541
|
:return: None
|
|
1542
1542
|
"""
|
|
1543
1543
|
KameleoonLogger.debug("CALL: KameleoonClient._obtain_configuration(time_stamp: %s)", time_stamp)
|
|
1544
|
+
last_modified = self._data_manager.data_file.last_modified
|
|
1544
1545
|
service: ConfigurationService = self._network_manager.get_service(ConfigurationService)
|
|
1545
|
-
|
|
1546
|
-
content = None
|
|
1547
|
-
if response.code and (response.code // 100 == 2):
|
|
1548
|
-
content = response.content
|
|
1546
|
+
fetched_configuration = await service.fetch_configuration(self._config.environment, time_stamp, last_modified)
|
|
1549
1547
|
KameleoonLogger.debug(
|
|
1550
|
-
"RETURN: KameleoonClient._obtain_configuration(time_stamp: %s) -> (
|
|
1548
|
+
"RETURN: KameleoonClient._obtain_configuration(time_stamp: %s) -> (fetched_configuration: %s)",
|
|
1549
|
+
time_stamp, fetched_configuration
|
|
1551
1550
|
)
|
|
1552
|
-
return
|
|
1551
|
+
return fetched_configuration
|
|
1553
1552
|
|
|
1554
1553
|
def __get_feature_variation_key(self, visitor_code: str, feature_key: str) -> Tuple[FeatureFlag, str]:
|
|
1555
1554
|
"""
|
|
@@ -1597,21 +1596,25 @@ class KameleoonClient:
|
|
|
1597
1596
|
)
|
|
1598
1597
|
|
|
1599
1598
|
@staticmethod
|
|
1600
|
-
def __get_code_for_hash(visitor: Optional[Visitor], visitor_code: str) -> str:
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1599
|
+
def __get_code_for_hash(visitor: Optional[Visitor], visitor_code: str, bucketing_cd_index: Optional[int]) -> str:
|
|
1600
|
+
if visitor is None:
|
|
1601
|
+
return visitor_code
|
|
1602
|
+
# 1. Try to use the bucketing custom data's value if bucketingCustomDataId is defined
|
|
1603
|
+
if bucketing_cd_index:
|
|
1604
|
+
bucketing_custom_data = visitor.custom_data.get(bucketing_cd_index)
|
|
1605
|
+
if bucketing_custom_data and (len(bucketing_custom_data.values) > 0):
|
|
1606
|
+
return bucketing_custom_data.values[0]
|
|
1607
|
+
# 2. Use mappingIdentifier instead of visitorCode if it was set up
|
|
1608
|
+
return visitor.mapping_identifier or visitor_code
|
|
1607
1609
|
|
|
1608
1610
|
def __evaluate_cbscores(
|
|
1609
|
-
self, visitor: Optional[Visitor], visitor_code: str, rule: Rule
|
|
1611
|
+
self, visitor: Optional[Visitor], visitor_code: str, rule: Rule, bucketing_cd_index: Optional[int]
|
|
1610
1612
|
) -> Optional[EvaluatedExperiment]:
|
|
1611
1613
|
if (visitor is None) or (visitor.cbscores is None):
|
|
1612
1614
|
return None
|
|
1613
1615
|
KameleoonLogger.debug(
|
|
1614
|
-
"CALL: KameleoonClient.__evaluate_cbscores(visitor, visitor_code: %s, rule: %s)",
|
|
1616
|
+
"CALL: KameleoonClient.__evaluate_cbscores(visitor, visitor_code: %s, rule: %s, bucketing_cd_index: %s)",
|
|
1617
|
+
visitor_code, rule, bucketing_cd_index
|
|
1615
1618
|
)
|
|
1616
1619
|
eval_exp = None
|
|
1617
1620
|
var_id_group_by_scores = visitor.cbscores.values.get(rule.experiment.id_)
|
|
@@ -1629,16 +1632,14 @@ class KameleoonClient:
|
|
|
1629
1632
|
size = len(var_by_exp_in_cbs)
|
|
1630
1633
|
idx = 0
|
|
1631
1634
|
if size > 1:
|
|
1632
|
-
code_for_hash = self.__get_code_for_hash(visitor, visitor_code)
|
|
1635
|
+
code_for_hash = self.__get_code_for_hash(visitor, visitor_code, bucketing_cd_index)
|
|
1633
1636
|
variation_hash = Hasher.obtain(code_for_hash, rule.experiment.id_, rule.respool_time)
|
|
1634
1637
|
KameleoonLogger.debug("Calculated CBS hash %s for code %s", variation_hash, code_for_hash)
|
|
1635
1638
|
idx = min(int(variation_hash * size), size - 1)
|
|
1636
1639
|
eval_exp = EvaluatedExperiment.from_var_by_exp_rule(var_by_exp_in_cbs[idx], rule)
|
|
1637
1640
|
KameleoonLogger.debug(
|
|
1638
|
-
"RETURN: KameleoonClient.__evaluate_cbscores(visitor, visitor_code: %s, rule: %s
|
|
1639
|
-
visitor_code,
|
|
1640
|
-
rule,
|
|
1641
|
-
eval_exp,
|
|
1641
|
+
"RETURN: KameleoonClient.__evaluate_cbscores(visitor, visitor_code: %s, rule: %s, bucketing_cd_index: %s)"
|
|
1642
|
+
" -> (eval_exp: %s)", visitor_code, rule, bucketing_cd_index, eval_exp
|
|
1642
1643
|
)
|
|
1643
1644
|
return eval_exp
|
|
1644
1645
|
|
|
@@ -1652,7 +1653,7 @@ class KameleoonClient:
|
|
|
1652
1653
|
feature_flag,
|
|
1653
1654
|
)
|
|
1654
1655
|
visitor = self._visitor_manager.get_visitor(visitor_code)
|
|
1655
|
-
code_for_hash = self.__get_code_for_hash(visitor, visitor_code)
|
|
1656
|
+
code_for_hash = self.__get_code_for_hash(visitor, visitor_code, feature_flag.bucketing_custom_data_index)
|
|
1656
1657
|
eval_exp = None
|
|
1657
1658
|
for rule in feature_flag.rules:
|
|
1658
1659
|
forced_variation = visitor.get_forced_experiment_variation(rule.experiment.id_) if visitor else None
|
|
@@ -1672,7 +1673,9 @@ class KameleoonClient:
|
|
|
1672
1673
|
KameleoonLogger.debug("Calculated rule hash %s for code %s", hash_rule, code_for_hash)
|
|
1673
1674
|
# check main expostion for rule with hashRule
|
|
1674
1675
|
if hash_rule <= rule.exposition:
|
|
1675
|
-
eval_exp = self.__evaluate_cbscores(
|
|
1676
|
+
eval_exp = self.__evaluate_cbscores(
|
|
1677
|
+
visitor, visitor_code, rule, feature_flag.bucketing_custom_data_index
|
|
1678
|
+
)
|
|
1676
1679
|
if eval_exp:
|
|
1677
1680
|
break
|
|
1678
1681
|
if rule.is_targeted_delivery:
|
|
@@ -63,7 +63,7 @@ class RemoteDataManager:
|
|
|
63
63
|
if response.content is None:
|
|
64
64
|
return []
|
|
65
65
|
custom_data_info = self._data_manager.data_file.custom_data_info
|
|
66
|
-
remote_visitor_data = RemoteDataManager.__parse_remote_visitor_data(response.content)
|
|
66
|
+
remote_visitor_data = RemoteDataManager.__parse_remote_visitor_data(response.content, data_filter)
|
|
67
67
|
remote_visitor_data.mark_data_as_sent(custom_data_info)
|
|
68
68
|
data_to_add = remote_visitor_data.collect_data_to_add()
|
|
69
69
|
if add_data and data_to_add:
|
|
@@ -77,9 +77,9 @@ class RemoteDataManager:
|
|
|
77
77
|
return remote_visitor_data.collect_data_to_return()
|
|
78
78
|
|
|
79
79
|
@staticmethod
|
|
80
|
-
def __parse_remote_visitor_data(raw: Any) -> RemoteVisitorData:
|
|
80
|
+
def __parse_remote_visitor_data(raw: Any, data_filter: RemoteVisitorDataFilter) -> RemoteVisitorData:
|
|
81
81
|
try:
|
|
82
|
-
return RemoteVisitorData(raw)
|
|
82
|
+
return RemoteVisitorData(raw, data_filter)
|
|
83
83
|
except Exception as ex: # pylint: disable=W0703
|
|
84
84
|
KameleoonLogger.error("Parsing of remote visitor data failed: %s", ex)
|
|
85
|
-
return RemoteVisitorData({})
|
|
85
|
+
return RemoteVisitorData({}, data_filter)
|