leaf-common 1.2.40__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.
Files changed (266) hide show
  1. leaf_common-1.2.41/.github/workflows/publish.yml +46 -0
  2. leaf_common-1.2.41/.github/workflows/tests.yml +31 -0
  3. {leaf_common-1.2.40 → leaf_common-1.2.41}/PKG-INFO +1 -1
  4. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/asyncio/asyncio_executor.py +14 -0
  5. leaf_common-1.2.41/leaf_common/asyncio/asyncio_executor_pool.py +134 -0
  6. leaf_common-1.2.41/leaf_common/asyncio/asyncio_threadpool_executor.py +71 -0
  7. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common.egg-info/PKG-INFO +1 -1
  8. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common.egg-info/SOURCES.txt +4 -1
  9. leaf_common-1.2.41/tests/asyncio/asyncio_executor_metrics_test.py +121 -0
  10. leaf_common-1.2.41/tests/asyncio/asyncio_executor_pool_metrics_test.py +212 -0
  11. leaf_common-1.2.41/tests/asyncio/asyncio_threadpool_executor_metrics_test.py +186 -0
  12. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/sync_test_helpers.py +11 -0
  13. leaf_common-1.2.40/.github/workflows/codeql.yml +0 -98
  14. leaf_common-1.2.40/.github/workflows/publish.yml +0 -55
  15. leaf_common-1.2.40/.github/workflows/tests.yml +0 -29
  16. leaf_common-1.2.40/leaf_common/asyncio/asyncio_executor_pool.py +0 -78
  17. {leaf_common-1.2.40 → leaf_common-1.2.41}/.coveragerc +0 -0
  18. {leaf_common-1.2.40 → leaf_common-1.2.41}/.flake8 +0 -0
  19. {leaf_common-1.2.40 → leaf_common-1.2.41}/.gitignore +0 -0
  20. {leaf_common-1.2.40 → leaf_common-1.2.41}/.pylintrc +0 -0
  21. {leaf_common-1.2.40 → leaf_common-1.2.41}/CODE_OF_CONDUCT.md +0 -0
  22. {leaf_common-1.2.40 → leaf_common-1.2.41}/CONTRIBUTING.md +0 -0
  23. {leaf_common-1.2.40 → leaf_common-1.2.41}/LICENSE.txt +0 -0
  24. {leaf_common-1.2.40 → leaf_common-1.2.41}/Makefile +0 -0
  25. {leaf_common-1.2.40 → leaf_common-1.2.41}/NOTICE.txt +0 -0
  26. {leaf_common-1.2.40 → leaf_common-1.2.41}/README.md +0 -0
  27. {leaf_common-1.2.40 → leaf_common-1.2.41}/SECURITY.md +0 -0
  28. {leaf_common-1.2.40 → leaf_common-1.2.41}/SUPPORT.md +0 -0
  29. {leaf_common-1.2.40 → leaf_common-1.2.41}/build_scripts/copyright_config.json +0 -0
  30. {leaf_common-1.2.40 → leaf_common-1.2.41}/build_scripts/run_flake8.sh +0 -0
  31. {leaf_common-1.2.40 → leaf_common-1.2.41}/build_scripts/run_pylint.sh +0 -0
  32. {leaf_common-1.2.40 → leaf_common-1.2.41}/build_scripts/source_available_copyright.txt +0 -0
  33. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/__init__.py +0 -0
  34. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/asyncio/__init__.py +0 -0
  35. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/asyncio/async_to_sync_generator.py +0 -0
  36. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/asyncio/task_executor.py +0 -0
  37. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/candidates/__init__.py +0 -0
  38. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/candidates/constants.py +0 -0
  39. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/candidates/representation_types.py +0 -0
  40. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/__init__.py +0 -0
  41. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/config_filter.py +0 -0
  42. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/config_filter_chain.py +0 -0
  43. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/config_handler.py +0 -0
  44. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/dictionary_overlay.py +0 -0
  45. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/environment_defaults.py +0 -0
  46. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/file_of_class.py +0 -0
  47. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/resolver.py +0 -0
  48. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/config/resolver_util.py +0 -0
  49. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/evaluation/__init__.py +0 -0
  50. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/evaluation/component_evaluator.py +0 -0
  51. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/evaluation/metrics_merger.py +0 -0
  52. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/__init__.py +0 -0
  53. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/composite_string_filter.py +0 -0
  54. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/inclusionary_replacement_string_filter.py +0 -0
  55. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/replacement_string_filter.py +0 -0
  56. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/sanitizer_util.py +0 -0
  57. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/string_filter.py +0 -0
  58. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/strip_whitespace_string_filter.py +0 -0
  59. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/filters/tensorflow_field_name_filter.py +0 -0
  60. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/__init__.py +0 -0
  61. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/candidate_metrics_provider.py +0 -0
  62. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/cmp_to_key.py +0 -0
  63. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/comparator.py +0 -0
  64. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objective.py +0 -0
  65. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives.py +0 -0
  66. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives_builder.py +0 -0
  67. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/fitness_objectives_from_config.py +0 -0
  68. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/metrics_based_individual_comparator.py +0 -0
  69. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/metrics_provider.py +0 -0
  70. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/none_comparator.py +0 -0
  71. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/number_comparator.py +0 -0
  72. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/fitness/reversed_comparator.py +0 -0
  73. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/log_utils/__init__.py +0 -0
  74. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/log_utils/message_type.py +0 -0
  75. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/log_utils/structured_message.py +0 -0
  76. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/logging/__init__.py +0 -0
  77. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/logging/example_logging_config.hocon +0 -0
  78. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/logging/logging_setup.py +0 -0
  79. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/logging/stream_to_logger.py +0 -0
  80. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/__init__.py +0 -0
  81. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/boolean_list_parser.py +0 -0
  82. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/boolean_parser.py +0 -0
  83. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/canonical_multi_config_parser.py +0 -0
  84. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/dictionary_extractor.py +0 -0
  85. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/field_extractor.py +0 -0
  86. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/list_parser.py +0 -0
  87. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/parser.py +0 -0
  88. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/parsers/string_parser.py +0 -0
  89. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/__init__.py +0 -0
  90. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/__init__.py +0 -0
  91. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/abstract_easy_persistence.py +0 -0
  92. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/composite_persistor.py +0 -0
  93. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_hocon_persistence.py +0 -0
  94. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_json_persistence.py +0 -0
  95. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_txt_persistence.py +0 -0
  96. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/easy/easy_yaml_persistence.py +0 -0
  97. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/README.md +0 -0
  98. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/__init__.py +0 -0
  99. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/abstract_persistence.py +0 -0
  100. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/hocon_persistence.py +0 -0
  101. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/json_gzip_persistence.py +0 -0
  102. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/json_persistence.py +0 -0
  103. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/null_persistence.py +0 -0
  104. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/override_file_extension_provider.py +0 -0
  105. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/persistence_factory.py +0 -0
  106. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/raw_bytes_persistence.py +0 -0
  107. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/reference_helper.py +0 -0
  108. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/simple_file_persistence.py +0 -0
  109. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/text_persistence.py +0 -0
  110. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/factory/yaml_persistence.py +0 -0
  111. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/interface/__init__.py +0 -0
  112. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistence.py +0 -0
  113. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistence_mechanism.py +0 -0
  114. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/interface/persistor.py +0 -0
  115. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/interface/restorer.py +0 -0
  116. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/__init__.py +0 -0
  117. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/abstract_persistence_mechanism.py +0 -0
  118. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/local_file_persistence_mechanism.py +0 -0
  119. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/persistence_mechanism_factory.py +0 -0
  120. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/persistence_mechanisms.py +0 -0
  121. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/persistence/mechanism/s3_file_persistence_mechanism.py +0 -0
  122. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/__init__.py +0 -0
  123. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/composite_progress_reporter.py +0 -0
  124. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/iterating_progress_reporter.py +0 -0
  125. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/json_progress_reporter.py +0 -0
  126. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/progress_reporter.py +0 -0
  127. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/status_dict_progress_reporter.py +0 -0
  128. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/progress/worker_progress_reporter.py +0 -0
  129. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/__init__.py +0 -0
  130. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/registry/__init__.py +0 -0
  131. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_file_extension_provider_registry.py +0 -0
  132. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_persistence_registry.py +0 -0
  133. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/registry/representation_serialization_format_registry.py +0 -0
  134. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/registry/self_identifying_restorer.py +0 -0
  135. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/__init__.py +0 -0
  136. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/config/__init__.py +0 -0
  137. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/config/rule_set_config_helper.py +0 -0
  138. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/__init__.py +0 -0
  139. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/condition.py +0 -0
  140. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/features.py +0 -0
  141. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule.py +0 -0
  142. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule_set.py +0 -0
  143. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rule_set_binding.py +0 -0
  144. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/data/rules_constants.py +0 -0
  145. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/__init__.py +0 -0
  146. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/condition_evaluator.py +0 -0
  147. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_evaluator.py +0 -0
  148. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_metrics_merger.py +0 -0
  149. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_binding_evaluator.py +0 -0
  150. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_evaluator.py +0 -0
  151. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/evaluation/rule_set_metrics_merger.py +0 -0
  152. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/__init__.py +0 -0
  153. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/rule_set_binding_file_persistence.py +0 -0
  154. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/persistence/rule_set_file_persistence.py +0 -0
  155. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/__init__.py +0 -0
  156. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/condition_dictionary_converter.py +0 -0
  157. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_dictionary_converter.py +0 -0
  158. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_binding_dictionary_converter.py +0 -0
  159. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_dictionary_converter.py +0 -0
  160. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/representation/rule_based/serialization/rule_set_serialization_format.py +0 -0
  161. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/__init__.py +0 -0
  162. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/__init__.py +0 -0
  163. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/auth0_direct_service_accessor.py +0 -0
  164. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/leaf_service_access.py +0 -0
  165. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/service_accessor.py +0 -0
  166. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/service_accessor_factory.py +0 -0
  167. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/static_token_service_accessor.py +0 -0
  168. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/service/vault_dynamic_token_service_accessor.py +0 -0
  169. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/__init__.py +0 -0
  170. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/github_vault_login.py +0 -0
  171. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/kubernetes_vault_login.py +0 -0
  172. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/token_vault_login.py +0 -0
  173. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/vault_login.py +0 -0
  174. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/security/vault/vault_login_factory.py +0 -0
  175. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/__init__.py +0 -0
  176. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/__init__.py +0 -0
  177. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/buffered_gzip_serialization_format.py +0 -0
  178. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/chained_serialization_format.py +0 -0
  179. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/conversion_policy.py +0 -0
  180. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/gzip_serialization_format.py +0 -0
  181. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/hocon_serialization_format.py +0 -0
  182. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/json_serialization_format.py +0 -0
  183. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/raw_bytes_serialization_format.py +0 -0
  184. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/serialization_formats.py +0 -0
  185. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/text_serialization_format.py +0 -0
  186. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/format/yaml_serialization_format.py +0 -0
  187. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/__init__.py +0 -0
  188. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/deserializer.py +0 -0
  189. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/dictionary_converter.py +0 -0
  190. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/file_extension_provider.py +0 -0
  191. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/reference_pruner.py +0 -0
  192. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/self_identifying_representation_error.py +0 -0
  193. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/serialization_format.py +0 -0
  194. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/interface/serializer.py +0 -0
  195. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/__init__.py +0 -0
  196. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/empty_dictionary_converter.py +0 -0
  197. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/metrics_dictionary_converter.py +0 -0
  198. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/pass_through_dictionary_converter.py +0 -0
  199. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/pass_through_reference_pruner.py +0 -0
  200. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/prep/redactor_dictionary_converter.py +0 -0
  201. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/util/__init__.py +0 -0
  202. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/util/bytes_decoder.py +0 -0
  203. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/serialization/util/text_file_reader.py +0 -0
  204. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/__init__.py +0 -0
  205. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/abstract_service_session.py +0 -0
  206. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/async_abstract_service_session.py +0 -0
  207. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/async_grpc_client_retry.py +0 -0
  208. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/extension_packaging.py +0 -0
  209. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/grpc_channel_security.py +0 -0
  210. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/grpc_client_retry.py +0 -0
  211. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/grpc_metadata_util.py +0 -0
  212. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/population_response_dictionary_converter.py +0 -0
  213. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/population_session.py +0 -0
  214. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/population_session_factory.py +0 -0
  215. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/session/response_candidate_dictionary_converter.py +0 -0
  216. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/time/__init__.py +0 -0
  217. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/time/time_util.py +0 -0
  218. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/time/timeout.py +0 -0
  219. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/time/timeout_reached_exception.py +0 -0
  220. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/utils/__init__.py +0 -0
  221. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/utils/async_atomic_counter.py +0 -0
  222. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common/utils/atomic_counter.py +0 -0
  223. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common.egg-info/dependency_links.txt +0 -0
  224. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common.egg-info/requires.txt +0 -0
  225. {leaf_common-1.2.40 → leaf_common-1.2.41}/leaf_common.egg-info/top_level.txt +0 -0
  226. {leaf_common-1.2.40 → leaf_common-1.2.41}/pyproject.toml +0 -0
  227. {leaf_common-1.2.40 → leaf_common-1.2.41}/requirements-build.txt +0 -0
  228. {leaf_common-1.2.40 → leaf_common-1.2.41}/requirements.txt +0 -0
  229. {leaf_common-1.2.40 → leaf_common-1.2.41}/setup.cfg +0 -0
  230. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/__init__.py +0 -0
  231. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/__init__.py +0 -0
  232. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/async_test_helpers.py +0 -0
  233. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/asyncio_executor_not_started_test.py +0 -0
  234. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/asyncio_executor_started_test.py +0 -0
  235. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/asyncio/callable_class.py +0 -0
  236. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/candidates/__init__.py +0 -0
  237. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/candidates/test_representation_types.py +0 -0
  238. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/config/__init__.py +0 -0
  239. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/config/dictionary_overlay_test.py +0 -0
  240. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/config/resolver_util_test.py +0 -0
  241. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/filters/__init__.py +0 -0
  242. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/filters/test_sanitizer_util.py +0 -0
  243. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/filters/test_tensorflow_field_name_filter.py +0 -0
  244. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/fitness/__init__.py +0 -0
  245. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/fitness/fitness_objectives_from_config_test.py +0 -0
  246. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/fixtures/saved_rule_set.rules +0 -0
  247. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/fixtures/saved_rules_model.json +0 -0
  248. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/parsers/__init__.py +0 -0
  249. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/parsers/canonical_multi_config_parser_test.py +0 -0
  250. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/progress/__init__.py +0 -0
  251. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/progress/test_status_dict_progress_reporter.py +0 -0
  252. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/representation/__init__.py +0 -0
  253. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/representation/rule_based/__init__.py +0 -0
  254. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/representation/rule_based/test_condition.py +0 -0
  255. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/representation/rule_based/test_rule.py +0 -0
  256. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/representation/rule_based/test_rule_set.py +0 -0
  257. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/__init__.py +0 -0
  258. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/encoding/__init__.py +0 -0
  259. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/encoding/decode_text_test.py +0 -0
  260. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/encoding/text_file_reader_test.py +0 -0
  261. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/prep/__init__.py +0 -0
  262. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/serialization/prep/redactor_dictionary_converter_test.py +0 -0
  263. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/session/__init__.py +0 -0
  264. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/session/test_extension_packaging.py +0 -0
  265. {leaf_common-1.2.40 → leaf_common-1.2.41}/tests/time/__init__.py +0 -0
  266. {leaf_common-1.2.40 → 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 }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: leaf-common
3
- Version: 1.2.40
3
+ Version: 1.2.41
4
4
  Summary: LEAF team common code library
5
5
  Author-email: Dan Fink <Daniel.Fink@cognizant.com>
6
6
  License-Expression: Apache-2.0
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: leaf-common
3
- Version: 1.2.40
3
+ Version: 1.2.41
4
4
  Summary: LEAF team common code library
5
5
  Author-email: Dan Fink <Daniel.Fink@cognizant.com>
6
6
  License-Expression: Apache-2.0
@@ -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
@@ -220,8 +220,11 @@ leaf_common/utils/atomic_counter.py
220
220
  tests/__init__.py
221
221
  tests/asyncio/__init__.py
222
222
  tests/asyncio/async_test_helpers.py
223
+ tests/asyncio/asyncio_executor_metrics_test.py
223
224
  tests/asyncio/asyncio_executor_not_started_test.py
225
+ tests/asyncio/asyncio_executor_pool_metrics_test.py
224
226
  tests/asyncio/asyncio_executor_started_test.py
227
+ tests/asyncio/asyncio_threadpool_executor_metrics_test.py
225
228
  tests/asyncio/callable_class.py
226
229
  tests/asyncio/sync_test_helpers.py
227
230
  tests/candidates/__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
+ )