leaf-common 1.2.39__tar.gz → 1.2.41__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.
- leaf_common-1.2.41/.github/workflows/publish.yml +46 -0
- leaf_common-1.2.41/.github/workflows/tests.yml +31 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/PKG-INFO +1 -1
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/asyncio/asyncio_executor.py +14 -0
- leaf_common-1.2.41/leaf_common/asyncio/asyncio_executor_pool.py +134 -0
- leaf_common-1.2.41/leaf_common/asyncio/asyncio_threadpool_executor.py +71 -0
- leaf_common-1.2.41/leaf_common/serialization/util/text_file_reader.py +53 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common.egg-info/PKG-INFO +1 -1
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common.egg-info/SOURCES.txt +6 -1
- {leaf_common-1.2.39 → leaf_common-1.2.41}/requirements-build.txt +1 -1
- leaf_common-1.2.41/tests/asyncio/asyncio_executor_metrics_test.py +121 -0
- leaf_common-1.2.41/tests/asyncio/asyncio_executor_pool_metrics_test.py +212 -0
- leaf_common-1.2.41/tests/asyncio/asyncio_threadpool_executor_metrics_test.py +186 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/sync_test_helpers.py +11 -0
- leaf_common-1.2.41/tests/serialization/encoding/text_file_reader_test.py +139 -0
- leaf_common-1.2.39/.github/workflows/codeql.yml +0 -98
- leaf_common-1.2.39/.github/workflows/publish.yml +0 -55
- leaf_common-1.2.39/.github/workflows/tests.yml +0 -29
- leaf_common-1.2.39/leaf_common/asyncio/asyncio_executor_pool.py +0 -78
- {leaf_common-1.2.39 → leaf_common-1.2.41}/.coveragerc +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/.flake8 +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/.gitignore +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/.pylintrc +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/CODE_OF_CONDUCT.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/CONTRIBUTING.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/LICENSE.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/Makefile +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/NOTICE.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/README.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/SECURITY.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/SUPPORT.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/build_scripts/copyright_config.json +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/build_scripts/run_flake8.sh +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/build_scripts/run_pylint.sh +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/build_scripts/source_available_copyright.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/asyncio/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/asyncio/async_to_sync_generator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/asyncio/task_executor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/candidates/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/candidates/constants.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/candidates/representation_types.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/config_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/config_filter_chain.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/config_handler.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/dictionary_overlay.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/environment_defaults.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/file_of_class.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/resolver.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/config/resolver_util.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/evaluation/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/evaluation/component_evaluator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/evaluation/metrics_merger.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/composite_string_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/inclusionary_replacement_string_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/replacement_string_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/sanitizer_util.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/string_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/strip_whitespace_string_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/filters/tensorflow_field_name_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/candidate_metrics_provider.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/cmp_to_key.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/comparator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objective.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives_builder.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives_from_config.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/metrics_based_individual_comparator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/metrics_provider.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/none_comparator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/number_comparator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/fitness/reversed_comparator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/log_utils/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/log_utils/message_type.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/log_utils/structured_message.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/logging/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/logging/example_logging_config.hocon +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/logging/logging_setup.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/logging/stream_to_logger.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/boolean_list_parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/boolean_parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/canonical_multi_config_parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/dictionary_extractor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/field_extractor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/list_parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/parsers/string_parser.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/abstract_easy_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/composite_persistor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_hocon_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_json_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_txt_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_yaml_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/README.md +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/abstract_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/hocon_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/json_gzip_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/json_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/null_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/override_file_extension_provider.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/persistence_factory.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/raw_bytes_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/reference_helper.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/simple_file_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/text_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/factory/yaml_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/interface/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistence_mechanism.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/interface/restorer.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/abstract_persistence_mechanism.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/local_file_persistence_mechanism.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/persistence_mechanism_factory.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/persistence_mechanisms.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/s3_file_persistence_mechanism.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/composite_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/iterating_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/json_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/status_dict_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/progress/worker_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/registry/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_file_extension_provider_registry.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_persistence_registry.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_serialization_format_registry.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/registry/self_identifying_restorer.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/config/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/config/rule_set_config_helper.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/condition.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/features.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule_set.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule_set_binding.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rules_constants.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/condition_evaluator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_evaluator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_metrics_merger.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_binding_evaluator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_evaluator.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_metrics_merger.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/rule_set_binding_file_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/rule_set_file_persistence.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/condition_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_binding_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/auth0_direct_service_accessor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/leaf_service_access.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/service_accessor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/service_accessor_factory.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/static_token_service_accessor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/service/vault_dynamic_token_service_accessor.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/github_vault_login.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/kubernetes_vault_login.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/token_vault_login.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/vault_login.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/security/vault/vault_login_factory.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/buffered_gzip_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/chained_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/conversion_policy.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/gzip_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/hocon_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/json_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/raw_bytes_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/serialization_formats.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/text_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/format/yaml_serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/deserializer.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/file_extension_provider.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/reference_pruner.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/self_identifying_representation_error.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/serialization_format.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/interface/serializer.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/empty_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/metrics_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/pass_through_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/pass_through_reference_pruner.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/prep/redactor_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/util/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/serialization/util/bytes_decoder.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/abstract_service_session.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/async_abstract_service_session.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/async_grpc_client_retry.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/extension_packaging.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/grpc_channel_security.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/grpc_client_retry.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/grpc_metadata_util.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/population_response_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/population_session.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/population_session_factory.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/session/response_candidate_dictionary_converter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/time/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/time/time_util.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/time/timeout.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/time/timeout_reached_exception.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/utils/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/utils/async_atomic_counter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common/utils/atomic_counter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common.egg-info/dependency_links.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common.egg-info/requires.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/leaf_common.egg-info/top_level.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/pyproject.toml +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/requirements.txt +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/setup.cfg +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/async_test_helpers.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/asyncio_executor_not_started_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/asyncio_executor_started_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/asyncio/callable_class.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/candidates/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/candidates/test_representation_types.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/config/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/config/dictionary_overlay_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/config/resolver_util_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/filters/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/filters/test_sanitizer_util.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/filters/test_tensorflow_field_name_filter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/fitness/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/fitness/fitness_objectives_from_config_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/fixtures/saved_rule_set.rules +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/fixtures/saved_rules_model.json +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/parsers/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/parsers/canonical_multi_config_parser_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/progress/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/progress/test_status_dict_progress_reporter.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/representation/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/representation/rule_based/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/representation/rule_based/test_condition.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/representation/rule_based/test_rule.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/representation/rule_based/test_rule_set.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/serialization/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/serialization/encoding/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/serialization/encoding/decode_text_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/serialization/prep/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/serialization/prep/redactor_dictionary_converter_test.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/session/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/session/test_extension_packaging.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/time/__init__.py +0 -0
- {leaf_common-1.2.39 → leaf_common-1.2.41}/tests/time/timeout_test.py +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
name: Build and Publish
|
|
13
|
+
if: github.repository == 'cognizant-ai-lab/leaf-common'
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write # Mandatory for PyPI Trusted Publishing
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
# v6.0.2
|
|
21
|
+
- name: Checkout code
|
|
22
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
|
23
|
+
|
|
24
|
+
- name: Setup Python environment
|
|
25
|
+
# 1.0.6
|
|
26
|
+
uses: cognizant-ai-lab/build-common/actions/setup-python-env@e5f749a7ba5de8bb8106cb172e89f21ca581a697
|
|
27
|
+
with:
|
|
28
|
+
python-version: '3.10'
|
|
29
|
+
requirements-file: ''
|
|
30
|
+
requirements-build-file: ''
|
|
31
|
+
install-extras: 'build'
|
|
32
|
+
|
|
33
|
+
- name: Build wheel and sdist
|
|
34
|
+
run: python -m build
|
|
35
|
+
|
|
36
|
+
# v1.14.0
|
|
37
|
+
- name: Publish to PyPI
|
|
38
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
|
39
|
+
|
|
40
|
+
- name: Notify Slack
|
|
41
|
+
if: ${{ !cancelled() }}
|
|
42
|
+
# 1.0.6
|
|
43
|
+
uses: cognizant-ai-lab/build-common/actions/slack-notify@e5f749a7ba5de8bb8106cb172e89f21ca581a697
|
|
44
|
+
with:
|
|
45
|
+
status: ${{ job.status }}
|
|
46
|
+
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Test
|
|
3
|
+
|
|
4
|
+
permissions:
|
|
5
|
+
contents: read
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches:
|
|
10
|
+
- "**"
|
|
11
|
+
pull_request:
|
|
12
|
+
types: [opened, reopened]
|
|
13
|
+
branches:
|
|
14
|
+
- main
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
if: >
|
|
18
|
+
(github.event_name == 'push' && github.ref != 'refs/heads/main') ||
|
|
19
|
+
(github.event_name == 'pull_request' && github.base_ref == 'main')
|
|
20
|
+
# 1.0.6
|
|
21
|
+
uses: cognizant-ai-lab/build-common/.github/workflows/_python-quality-gate.yml@e5f749a7ba5de8bb8106cb172e89f21ca581a697
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
lint-toolchain: 'legacy'
|
|
25
|
+
pylint-command: 'build_scripts/run_pylint.sh'
|
|
26
|
+
run-shellcheck: true
|
|
27
|
+
pytest-markers: 'not integration'
|
|
28
|
+
pytest-extra-args: '--timer-top-n 10 --cov-config=.coveragerc --cov'
|
|
29
|
+
check-readme-pypi: true
|
|
30
|
+
secrets:
|
|
31
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
@@ -22,6 +22,7 @@ from typing import Awaitable
|
|
|
22
22
|
from typing import Callable
|
|
23
23
|
from typing import Dict
|
|
24
24
|
from typing import List
|
|
25
|
+
from typing import Tuple
|
|
25
26
|
|
|
26
27
|
import asyncio
|
|
27
28
|
import functools
|
|
@@ -36,6 +37,7 @@ from asyncio import Task
|
|
|
36
37
|
from concurrent import futures
|
|
37
38
|
|
|
38
39
|
from leaf_common.asyncio.task_executor import TaskExecutor
|
|
40
|
+
from leaf_common.asyncio.asyncio_threadpool_executor import AsyncioThreadPoolExecutor
|
|
39
41
|
|
|
40
42
|
EXECUTOR_START_TIMEOUT_SECONDS: int = 5
|
|
41
43
|
|
|
@@ -57,8 +59,10 @@ class AsyncioExecutor(TaskExecutor):
|
|
|
57
59
|
self._thread: threading.Thread = None
|
|
58
60
|
# We are going to start new thread for this Executor,
|
|
59
61
|
# so we need a new event loop bound to this particular thread:
|
|
62
|
+
self._threadpool_executor: AsyncioThreadPoolExecutor = AsyncioThreadPoolExecutor()
|
|
60
63
|
self._loop: AbstractEventLoop = asyncio.new_event_loop()
|
|
61
64
|
self._loop.set_exception_handler(AsyncioExecutor.loop_exception_handler)
|
|
65
|
+
self._loop.set_default_executor(self._threadpool_executor)
|
|
62
66
|
self._loop_ready = threading.Event()
|
|
63
67
|
self._init_done = threading.Event()
|
|
64
68
|
self._background_tasks: Dict[int, Dict[str, Any]] = {}
|
|
@@ -446,6 +450,16 @@ class AsyncioExecutor(TaskExecutor):
|
|
|
446
450
|
with self._background_tasks_lock:
|
|
447
451
|
self._background_tasks.pop(task_id, None)
|
|
448
452
|
|
|
453
|
+
def get_threads_metrics(self) -> Tuple[int, int]:
|
|
454
|
+
"""
|
|
455
|
+
For ThreadExecutorPool used by our event loop,
|
|
456
|
+
get number of created threads and number of currently running threads.
|
|
457
|
+
:return: Tuple of (number of threads in the pool, number of currently running threads)
|
|
458
|
+
"""
|
|
459
|
+
if self._threadpool_executor:
|
|
460
|
+
return self._threadpool_executor.get_threads_metrics()
|
|
461
|
+
return 0, 0
|
|
462
|
+
|
|
449
463
|
def shutdown(self, wait: bool = True, *, cancel_futures: bool = False):
|
|
450
464
|
"""
|
|
451
465
|
Shuts down the event loop.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright © 2019-2026 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# END COPYRIGHT
|
|
17
|
+
"""
|
|
18
|
+
See class comments
|
|
19
|
+
"""
|
|
20
|
+
from typing import Any
|
|
21
|
+
from typing import Dict
|
|
22
|
+
|
|
23
|
+
import copy
|
|
24
|
+
import logging
|
|
25
|
+
import threading
|
|
26
|
+
from leaf_common.asyncio.asyncio_executor import AsyncioExecutor
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AsyncioExecutorPool:
|
|
30
|
+
"""
|
|
31
|
+
Class maintaining a dynamic set of reusable AsyncioExecutor instances.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, reuse_mode: bool = True):
|
|
35
|
+
"""
|
|
36
|
+
Constructor.
|
|
37
|
+
:param reuse_mode: True, if requested executor instances
|
|
38
|
+
are taken from pool of available ones (pool mode);
|
|
39
|
+
False, if requested executor instances are created new
|
|
40
|
+
and shutdown on return (backward compatible mode)
|
|
41
|
+
"""
|
|
42
|
+
self.reuse_mode: bool = reuse_mode
|
|
43
|
+
# List of available (not currently used) AsyncioExecutor instances in the pool.
|
|
44
|
+
self.pool_available = []
|
|
45
|
+
|
|
46
|
+
# List of currently used AsyncioExecutor instances in the pool.
|
|
47
|
+
self.pool_used = []
|
|
48
|
+
|
|
49
|
+
self.lock = threading.Lock()
|
|
50
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
51
|
+
self.logger.debug("AsyncioExecutorPool created: %s reuse: %s",
|
|
52
|
+
id(self), str(self.reuse_mode))
|
|
53
|
+
|
|
54
|
+
def get_executor(self) -> AsyncioExecutor:
|
|
55
|
+
"""
|
|
56
|
+
Get active (running) executor from the pool
|
|
57
|
+
:return: AsyncioExecutor instance
|
|
58
|
+
"""
|
|
59
|
+
if self.reuse_mode:
|
|
60
|
+
with self.lock:
|
|
61
|
+
if len(self.pool_available) > 0:
|
|
62
|
+
result = self.pool_available.pop(0)
|
|
63
|
+
self.logger.debug("Reusing AsyncioExecutor %s", id(result))
|
|
64
|
+
self.pool_used.append(result)
|
|
65
|
+
return result
|
|
66
|
+
# Create AsyncioExecutor outside of lock
|
|
67
|
+
# to avoid potentially longer locked periods
|
|
68
|
+
result = AsyncioExecutor()
|
|
69
|
+
result.start()
|
|
70
|
+
self.logger.debug("Creating AsyncioExecutor %s", id(result))
|
|
71
|
+
with self.lock:
|
|
72
|
+
self.pool_used.append(result)
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
def return_executor(self, executor: AsyncioExecutor):
|
|
76
|
+
"""
|
|
77
|
+
Return AsyncioExecutor instance back to the pool of available instances.
|
|
78
|
+
:param executor: AsyncioExecutor to return.
|
|
79
|
+
"""
|
|
80
|
+
with self.lock:
|
|
81
|
+
if executor not in self.pool_used:
|
|
82
|
+
raise ValueError(f"Returned executor {id(executor)} is not in the pool of used executors")
|
|
83
|
+
self.pool_used.remove(executor)
|
|
84
|
+
|
|
85
|
+
# Executor clean up: cancel current tasks and shutdown if not in reuse mode.
|
|
86
|
+
if self.reuse_mode:
|
|
87
|
+
executor.cancel_current_tasks()
|
|
88
|
+
else:
|
|
89
|
+
self.logger.debug("Shutting down: AsyncioExecutor %s", id(executor))
|
|
90
|
+
executor.shutdown()
|
|
91
|
+
|
|
92
|
+
if self.reuse_mode:
|
|
93
|
+
with self.lock:
|
|
94
|
+
self.pool_available.append(executor)
|
|
95
|
+
self.logger.debug("Returned to pool: AsyncioExecutor %s pool size: %d",
|
|
96
|
+
id(executor), len(self.pool_available))
|
|
97
|
+
|
|
98
|
+
def get_threads_metrics(self) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Get metrics related to threads in the pool of executors:
|
|
101
|
+
for both collections of used and available executors,
|
|
102
|
+
get the total number of executors in this collection "executors",
|
|
103
|
+
the total number of work threads across all executors in this collection "work_threads",
|
|
104
|
+
and the total number of currently running work threads
|
|
105
|
+
across all executors in this collection "threads_running".
|
|
106
|
+
"""
|
|
107
|
+
with self.lock:
|
|
108
|
+
available_copy = copy.copy(self.pool_available)
|
|
109
|
+
used_copy = copy.copy(self.pool_used)
|
|
110
|
+
used_threads: int = 0
|
|
111
|
+
used_running: int = 0
|
|
112
|
+
available_threads: int = 0
|
|
113
|
+
available_running: int = 0
|
|
114
|
+
for executor in used_copy:
|
|
115
|
+
threads, running = executor.get_threads_metrics()
|
|
116
|
+
used_threads += threads
|
|
117
|
+
used_running += running
|
|
118
|
+
for executor in available_copy:
|
|
119
|
+
threads, running = executor.get_threads_metrics()
|
|
120
|
+
available_threads += threads
|
|
121
|
+
available_running += running
|
|
122
|
+
result_dict = {
|
|
123
|
+
"used": {
|
|
124
|
+
"executors": len(used_copy),
|
|
125
|
+
"work_threads": used_threads,
|
|
126
|
+
"threads_running": used_running
|
|
127
|
+
},
|
|
128
|
+
"available": {
|
|
129
|
+
"executors": len(available_copy),
|
|
130
|
+
"work_threads": available_threads,
|
|
131
|
+
"threads_running": available_running
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result_dict
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright © 2019-2026 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# END COPYRIGHT
|
|
17
|
+
"""
|
|
18
|
+
See class comment for details.
|
|
19
|
+
"""
|
|
20
|
+
from typing import Tuple
|
|
21
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
22
|
+
import logging
|
|
23
|
+
import threading
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AsyncioThreadPoolExecutor(ThreadPoolExecutor):
|
|
27
|
+
"""
|
|
28
|
+
Class instrumenting a ThreadPoolExecutor with some additional run-time metrics
|
|
29
|
+
such as number of created and active threads.
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, *args, **kwargs):
|
|
32
|
+
"""Constructor."""
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
self.running: int = 0
|
|
35
|
+
self.lock = threading.Lock()
|
|
36
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
37
|
+
self.no_threads_warning_logged: bool = False
|
|
38
|
+
|
|
39
|
+
def submit(self, fn, /, *args, **kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Override of submit method to wrap the submitted function with additional logic
|
|
42
|
+
for counting the number of active (running) threads.
|
|
43
|
+
"""
|
|
44
|
+
def wrapped(*a, **kw):
|
|
45
|
+
with self.lock:
|
|
46
|
+
self.running += 1
|
|
47
|
+
try:
|
|
48
|
+
return fn(*a, **kw)
|
|
49
|
+
finally:
|
|
50
|
+
with self.lock:
|
|
51
|
+
self.running -= 1
|
|
52
|
+
|
|
53
|
+
return super().submit(wrapped, *args, **kwargs)
|
|
54
|
+
|
|
55
|
+
def get_threads_metrics(self) -> Tuple[int, int]:
|
|
56
|
+
"""
|
|
57
|
+
Get number of threads in the pool and number of currently running threads.
|
|
58
|
+
:return: Tuple of (number of threads in the pool, number of currently running threads)
|
|
59
|
+
Note: number of threads in the pool is an approximation, as ThreadPoolExecutor does not provide
|
|
60
|
+
a direct way to get this information. The method tries to access the protected member _threads,
|
|
61
|
+
which may not be available in all implementations of ThreadPoolExecutor.
|
|
62
|
+
"""
|
|
63
|
+
num_threads: int = 0
|
|
64
|
+
if hasattr(self, "_threads"):
|
|
65
|
+
num_threads = len(self._threads)
|
|
66
|
+
elif not self.no_threads_warning_logged:
|
|
67
|
+
self.logger.warning("ThreadPoolExecutor does not have _threads attribute, "
|
|
68
|
+
"number of threads in the pool will be reported as 0")
|
|
69
|
+
self.no_threads_warning_logged = True
|
|
70
|
+
with self.lock:
|
|
71
|
+
return num_threads, self.running
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright © 2019-2026 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# END COPYRIGHT
|
|
17
|
+
"""
|
|
18
|
+
See class comment for details.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
|
|
23
|
+
from leaf_common.serialization.util.bytes_decoder import BytesDecoder
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TextFileReader:
|
|
27
|
+
"""
|
|
28
|
+
Class providing utility methods for reading text files in several possible encodings.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def read_text_file(filepath: str) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Read the content of a text file in some encoding and return it as a string.
|
|
35
|
+
:param filepath: The path to the text file to read
|
|
36
|
+
:return: The content of the text file as a string
|
|
37
|
+
"""
|
|
38
|
+
with open(filepath, "rb") as binary_file:
|
|
39
|
+
contents: bytes = binary_file.read()
|
|
40
|
+
text, _ = BytesDecoder.decode_bytes(contents, source_name=filepath)
|
|
41
|
+
return text
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
async def async_read_text_file(filepath: str) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Asynchronously read the content of a text file in some encoding and return it as a string.
|
|
47
|
+
|
|
48
|
+
Implemented by offloading the blocking read to a default thread executor via
|
|
49
|
+
``asyncio.to_thread`` to avoid an extra third-party dependency on aiofiles.
|
|
50
|
+
:param filepath: The path to the text file to read
|
|
51
|
+
:return: The content of the text file as a string
|
|
52
|
+
"""
|
|
53
|
+
return await asyncio.to_thread(TextFileReader.read_text_file, filepath)
|
|
@@ -13,7 +13,6 @@ SUPPORT.md
|
|
|
13
13
|
pyproject.toml
|
|
14
14
|
requirements-build.txt
|
|
15
15
|
requirements.txt
|
|
16
|
-
.github/workflows/codeql.yml
|
|
17
16
|
.github/workflows/publish.yml
|
|
18
17
|
.github/workflows/tests.yml
|
|
19
18
|
build_scripts/copyright_config.json
|
|
@@ -30,6 +29,7 @@ leaf_common/asyncio/__init__.py
|
|
|
30
29
|
leaf_common/asyncio/async_to_sync_generator.py
|
|
31
30
|
leaf_common/asyncio/asyncio_executor.py
|
|
32
31
|
leaf_common/asyncio/asyncio_executor_pool.py
|
|
32
|
+
leaf_common/asyncio/asyncio_threadpool_executor.py
|
|
33
33
|
leaf_common/asyncio/task_executor.py
|
|
34
34
|
leaf_common/candidates/__init__.py
|
|
35
35
|
leaf_common/candidates/constants.py
|
|
@@ -197,6 +197,7 @@ leaf_common/serialization/prep/pass_through_reference_pruner.py
|
|
|
197
197
|
leaf_common/serialization/prep/redactor_dictionary_converter.py
|
|
198
198
|
leaf_common/serialization/util/__init__.py
|
|
199
199
|
leaf_common/serialization/util/bytes_decoder.py
|
|
200
|
+
leaf_common/serialization/util/text_file_reader.py
|
|
200
201
|
leaf_common/session/__init__.py
|
|
201
202
|
leaf_common/session/abstract_service_session.py
|
|
202
203
|
leaf_common/session/async_abstract_service_session.py
|
|
@@ -219,8 +220,11 @@ leaf_common/utils/atomic_counter.py
|
|
|
219
220
|
tests/__init__.py
|
|
220
221
|
tests/asyncio/__init__.py
|
|
221
222
|
tests/asyncio/async_test_helpers.py
|
|
223
|
+
tests/asyncio/asyncio_executor_metrics_test.py
|
|
222
224
|
tests/asyncio/asyncio_executor_not_started_test.py
|
|
225
|
+
tests/asyncio/asyncio_executor_pool_metrics_test.py
|
|
223
226
|
tests/asyncio/asyncio_executor_started_test.py
|
|
227
|
+
tests/asyncio/asyncio_threadpool_executor_metrics_test.py
|
|
224
228
|
tests/asyncio/callable_class.py
|
|
225
229
|
tests/asyncio/sync_test_helpers.py
|
|
226
230
|
tests/candidates/__init__.py
|
|
@@ -247,6 +251,7 @@ tests/representation/rule_based/test_rule_set.py
|
|
|
247
251
|
tests/serialization/__init__.py
|
|
248
252
|
tests/serialization/encoding/__init__.py
|
|
249
253
|
tests/serialization/encoding/decode_text_test.py
|
|
254
|
+
tests/serialization/encoding/text_file_reader_test.py
|
|
250
255
|
tests/serialization/prep/__init__.py
|
|
251
256
|
tests/serialization/prep/redactor_dictionary_converter_test.py
|
|
252
257
|
tests/session/__init__.py
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright © 2019-2026 Cognizant Technology Solutions Corp, www.cognizant.com.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# END COPYRIGHT
|
|
17
|
+
"""
|
|
18
|
+
Unit tests for AsyncioExecutor.get_threads_metrics().
|
|
19
|
+
|
|
20
|
+
The method delegates to the executor's internal AsyncioThreadPoolExecutor
|
|
21
|
+
when one is present, or returns (0, 0) otherwise. The behavior of the
|
|
22
|
+
threadpool itself is covered in asyncio_threadpool_executor_metrics_test;
|
|
23
|
+
this module isolates AsyncioExecutor's delegation logic.
|
|
24
|
+
"""
|
|
25
|
+
import threading
|
|
26
|
+
|
|
27
|
+
from unittest import TestCase
|
|
28
|
+
|
|
29
|
+
from leaf_common.asyncio.asyncio_executor import AsyncioExecutor
|
|
30
|
+
from tests.asyncio.sync_test_helpers import SyncTestHelpers
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AsyncioExecutorMetricsTest(TestCase):
|
|
34
|
+
"""
|
|
35
|
+
Verifies that AsyncioExecutor.get_threads_metrics() returns the same
|
|
36
|
+
(threads, running) tuple as the underlying AsyncioThreadPoolExecutor,
|
|
37
|
+
and that the fallback path returns (0, 0) when no threadpool is set.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def setUp(self):
|
|
41
|
+
"""Create and start a fresh executor."""
|
|
42
|
+
self.executor = AsyncioExecutor()
|
|
43
|
+
self.executor.start()
|
|
44
|
+
|
|
45
|
+
def tearDown(self):
|
|
46
|
+
"""Always shutdown so the event-loop thread terminates cleanly."""
|
|
47
|
+
if self.executor is not None:
|
|
48
|
+
self.executor.shutdown(wait=True)
|
|
49
|
+
|
|
50
|
+
def test_metrics_on_idle_executor_report_zero(self):
|
|
51
|
+
"""
|
|
52
|
+
Immediately after start(), no work has been dispatched to the
|
|
53
|
+
threadpool, so both counts are zero.
|
|
54
|
+
"""
|
|
55
|
+
num_threads, num_running = self.executor.get_threads_metrics()
|
|
56
|
+
self.assertEqual(
|
|
57
|
+
0, num_threads,
|
|
58
|
+
f"Expected 0 worker threads on an idle executor, got {num_threads}.",
|
|
59
|
+
)
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
0, num_running,
|
|
62
|
+
f"Expected 0 running tasks on an idle executor, got {num_running}.",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def test_metrics_reflect_work_dispatched_to_internal_threadpool(self):
|
|
66
|
+
"""
|
|
67
|
+
AsyncioExecutor.get_threads_metrics() delegates to its internal
|
|
68
|
+
AsyncioThreadPoolExecutor. Submit work directly to that threadpool
|
|
69
|
+
(the same one the executor's event loop dispatches sync work to via
|
|
70
|
+
run_in_executor) and verify the delegated metrics reflect it.
|
|
71
|
+
"""
|
|
72
|
+
start_event = threading.Event()
|
|
73
|
+
release_event = threading.Event()
|
|
74
|
+
|
|
75
|
+
# pylint: disable=protected-access
|
|
76
|
+
threadpool = self.executor._threadpool_executor
|
|
77
|
+
future = threadpool.submit(SyncTestHelpers.block_on_event, start_event, release_event)
|
|
78
|
+
self.assertTrue(
|
|
79
|
+
start_event.wait(timeout=5.0),
|
|
80
|
+
"Helper task never entered its body within 5s.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
num_threads, num_running = self.executor.get_threads_metrics()
|
|
84
|
+
|
|
85
|
+
# Release and drain before asserting, so failures do not leak
|
|
86
|
+
# a blocked worker.
|
|
87
|
+
release_event.set()
|
|
88
|
+
future.result(timeout=5.0)
|
|
89
|
+
|
|
90
|
+
self.assertGreaterEqual(
|
|
91
|
+
num_threads, 1,
|
|
92
|
+
f"Expected at least 1 worker thread mid-task, got {num_threads}.",
|
|
93
|
+
)
|
|
94
|
+
self.assertEqual(
|
|
95
|
+
1, num_running,
|
|
96
|
+
f"Expected exactly 1 running task while the helper was blocked, "
|
|
97
|
+
f"got {num_running}.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def test_metrics_return_zero_tuple_when_threadpool_is_absent(self):
|
|
101
|
+
"""
|
|
102
|
+
AsyncioExecutor.get_threads_metrics() short-circuits to (0, 0) if
|
|
103
|
+
the internal _threadpool_executor reference is falsy. Simulate
|
|
104
|
+
that branch by clearing the attribute. Avoids needing a real
|
|
105
|
+
environment where the threadpool failed to construct.
|
|
106
|
+
"""
|
|
107
|
+
# pylint: disable=protected-access
|
|
108
|
+
self.executor._threadpool_executor = None
|
|
109
|
+
|
|
110
|
+
num_threads, num_running = self.executor.get_threads_metrics()
|
|
111
|
+
|
|
112
|
+
self.assertEqual(
|
|
113
|
+
0, num_threads,
|
|
114
|
+
f"Expected fallback of 0 worker threads when threadpool is absent, "
|
|
115
|
+
f"got {num_threads}.",
|
|
116
|
+
)
|
|
117
|
+
self.assertEqual(
|
|
118
|
+
0, num_running,
|
|
119
|
+
f"Expected 0 running tasks when threadpool is absent, "
|
|
120
|
+
f"got {num_running}.",
|
|
121
|
+
)
|