haiway 0.20.0__tar.gz → 0.20.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {haiway-0.20.0 → haiway-0.20.1}/Makefile +1 -1
  2. {haiway-0.20.0 → haiway-0.20.1}/PKG-INFO +1 -1
  3. {haiway-0.20.0 → haiway-0.20.1}/junit/test-results.xml +1 -1
  4. {haiway-0.20.0 → haiway-0.20.1}/pyproject.toml +1 -1
  5. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/concurrent.py +25 -14
  6. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/structure.py +8 -0
  7. haiway-0.20.1/tests/test_process_concurrently.py +191 -0
  8. {haiway-0.20.0 → haiway-0.20.1}/uv.lock +205 -205
  9. {haiway-0.20.0 → haiway-0.20.1}/.github/workflows/ci.yml +0 -0
  10. {haiway-0.20.0 → haiway-0.20.1}/.github/workflows/publish.yml +0 -0
  11. {haiway-0.20.0 → haiway-0.20.1}/.gitignore +0 -0
  12. {haiway-0.20.0 → haiway-0.20.1}/LICENSE +0 -0
  13. {haiway-0.20.0 → haiway-0.20.1}/README.md +0 -0
  14. {haiway-0.20.0 → haiway-0.20.1}/config/pre-push +0 -0
  15. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/.dockerignore +0 -0
  16. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/Dockerfile +0 -0
  17. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/Makefile +0 -0
  18. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/README.md +0 -0
  19. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/config/.env.example +0 -0
  20. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/config/unit.json +0 -0
  21. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/docker-compose.yml +0 -0
  22. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/pyproject.toml +0 -0
  23. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/__int__.py +0 -0
  24. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/__init__.py +0 -0
  25. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/config.py +0 -0
  26. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/state.py +0 -0
  27. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/types.py +0 -0
  28. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
  29. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/__init__.py +0 -0
  30. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
  31. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
  32. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
  33. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
  34. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
  35. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/__init__.py +0 -0
  36. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/__main__.py +0 -0
  37. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
  38. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
  39. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
  40. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
  41. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/__init__.py +0 -0
  42. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/__main__.py +0 -0
  43. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/application.py +0 -0
  44. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/config.py +0 -0
  45. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
  46. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/middlewares/context.py +0 -0
  47. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/__init__.py +0 -0
  48. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/technical.py +0 -0
  49. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/todos.py +0 -0
  50. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/__init__.py +0 -0
  51. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
  52. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
  53. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
  54. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
  55. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
  56. {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/uv.lock +0 -0
  57. {haiway-0.20.0 → haiway-0.20.1}/guidelines/functionalities.md +0 -0
  58. {haiway-0.20.0 → haiway-0.20.1}/guidelines/packages.md +0 -0
  59. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/__init__.py +0 -0
  60. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/__init__.py +0 -0
  61. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/access.py +0 -0
  62. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/disposables.py +0 -0
  63. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/identifier.py +0 -0
  64. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/observability.py +0 -0
  65. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/state.py +0 -0
  66. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/tasks.py +0 -0
  67. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/types.py +0 -0
  68. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/__init__.py +0 -0
  69. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/asynchrony.py +0 -0
  70. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/caching.py +0 -0
  71. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/observability.py +0 -0
  72. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/retries.py +0 -0
  73. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/throttling.py +0 -0
  74. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/timeouted.py +0 -0
  75. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/tracing.py +0 -0
  76. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/opentelemetry/__init__.py +0 -0
  77. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/opentelemetry/observability.py +0 -0
  78. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/py.typed +0 -0
  79. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/__init__.py +0 -0
  80. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/attributes.py +0 -0
  81. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/path.py +0 -0
  82. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/requirement.py +0 -0
  83. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/validation.py +0 -0
  84. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/__init__.py +0 -0
  85. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/default.py +0 -0
  86. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/frozen.py +0 -0
  87. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/missing.py +0 -0
  88. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/__init__.py +0 -0
  89. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/always.py +0 -0
  90. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/collections.py +0 -0
  91. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/env.py +0 -0
  92. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/formatting.py +0 -0
  93. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/freezing.py +0 -0
  94. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/logs.py +0 -0
  95. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/mimic.py +0 -0
  96. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/noop.py +0 -0
  97. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/queue.py +0 -0
  98. {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/stream.py +0 -0
  99. {haiway-0.20.0 → haiway-0.20.1}/tests/__init__.py +0 -0
  100. {haiway-0.20.0 → haiway-0.20.1}/tests/test_async_queue.py +0 -0
  101. {haiway-0.20.0 → haiway-0.20.1}/tests/test_async_stream.py +0 -0
  102. {haiway-0.20.0 → haiway-0.20.1}/tests/test_attribute_path.py +0 -0
  103. {haiway-0.20.0 → haiway-0.20.1}/tests/test_auto_retry.py +0 -0
  104. {haiway-0.20.0 → haiway-0.20.1}/tests/test_cache.py +0 -0
  105. {haiway-0.20.0 → haiway-0.20.1}/tests/test_context.py +0 -0
  106. {haiway-0.20.0 → haiway-0.20.1}/tests/test_state.py +0 -0
  107. {haiway-0.20.0 → haiway-0.20.1}/tests/test_streaming.py +0 -0
  108. {haiway-0.20.0 → haiway-0.20.1}/tests/test_timeout.py +0 -0
@@ -10,7 +10,7 @@ TESTS_PATH := tests
10
10
  -include .env
11
11
 
12
12
  ifndef UV_VERSION
13
- UV_VERSION := 0.7.2
13
+ UV_VERSION := 0.7.5
14
14
  endif
15
15
 
16
16
  .PHONY: uv_check venv sync update format lint test release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haiway
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: Framework for dependency injection and state management within structured concurrency model.
5
5
  Project-URL: Homepage, https://miquido.com
6
6
  Project-URL: Repository, https://github.com/miquido/haiway.git
@@ -1 +1 @@
1
- <?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="88" time="1.104" timestamp="2025-05-16T14:32:19.527609+00:00" hostname="pkrvmf6wy0o8zjz"><testcase classname="tests.test_async_queue" name="test_fails_when_stream_fails" time="0.001" /><testcase classname="tests.test_async_queue" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_async_queue" name="test_ends_when_stream_ends" time="0.001" /><testcase classname="tests.test_async_queue" name="test_buffers_values_when_not_reading" time="0.001" /><testcase classname="tests.test_async_queue" name="test_delivers_buffer_when_streaming_fails" time="0.001" /><testcase classname="tests.test_async_queue" name="test_delivers_updates_when_sending" time="0.001" /><testcase classname="tests.test_async_queue" name="test_fails_when_sending_to_finished" time="0.001" /><testcase classname="tests.test_async_queue" name="test_ignores_when_finishing_when_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_fails_when_stream_fails" time="0.001" /><testcase classname="tests.test_async_stream" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ends_when_stream_ends" time="0.001" /><testcase classname="tests.test_async_stream" name="test_finishes_without_buffer" time="0.001" /><testcase classname="tests.test_async_stream" name="test_fails_without_buffer" time="0.001" /><testcase classname="tests.test_async_stream" name="test_delivers_updates_when_sending" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_sending_to_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_sending_to_failed" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_finishing_when_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_delivers_all_when_sending_async" time="0.001" /><testcase classname="tests.test_attribute_path" name="test_id_path_points_to_self" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_attribute_path_points_to_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_nested_attribute_path_points_to_nested_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_recursive_attribute_path_points_to_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_list_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_tuple_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_mixed_tuple_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_dict_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_id_path_set_updates_self" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_attribute_path_set_updates_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_nested_attribute_path_set_updates_nested_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_recursive_attribute_set_updates_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_list_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_tuple_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_mixed_tuple_item_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_dict_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_auto_retry" name="test_returns_value_without_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_retries_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_logs_issue_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_exceeding_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_cancellation" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_retries_with_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_not_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_returns_value_without_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_retries_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_exceeding_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_cancellation" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_when_cancelled" time="0.021" /><testcase classname="tests.test_auto_retry" name="test_async_uses_delay_with_errors" time="0.101" /><testcase classname="tests.test_auto_retry" name="test_async_uses_computed_delay_with_errors" time="0.107" /><testcase classname="tests.test_auto_retry" name="test_async_logs_issue_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_retries_with_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_not_selected_errors" time="0.001" /><testcase classname="tests.test_cache" name="test_returns_cache_value_with_same_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_different_argument" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_limit_exceed" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_same_value_with_repeating_argument" time="0.000" /><testcase classname="tests.test_cache" name="test_fails_with_error" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_expiration_time_exceed" time="0.020" /><testcase classname="tests.test_cache" name="test_async_returns_cache_value_with_same_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_different_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_limit_exceed" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_same_value_with_repeating_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_expiration_time_exceed" time="0.021" /><testcase classname="tests.test_cache" name="test_async_cancel_waiting_does_not_cancel_task" time="0.502" /><testcase classname="tests.test_cache" name="test_async_expiration_does_not_cancel_task" time="0.021" /><testcase classname="tests.test_cache" name="test_async_fails_with_error" time="0.001" /><testcase classname="tests.test_context" name="test_state_is_available_according_to_context" time="0.001" /><testcase classname="tests.test_context" name="test_state_update_updates_local_context" time="0.001" /><testcase classname="tests.test_context" name="test_exceptions_are_propagated" time="0.001" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments" time="0.002" /><testcase classname="tests.test_state" name="test_basic_initializes_with_defaults" time="0.001" /><testcase classname="tests.test_state" name="test_basic_equals_checks_properties" time="0.000" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments_and_defaults" time="0.000" /><testcase classname="tests.test_state" name="test_parametrized_initializes_with_proper_parameters" time="0.000" /><testcase classname="tests.test_state" name="test_nested_initializes_with_proper_arguments" time="0.001" /><testcase classname="tests.test_state" name="test_dict_skips_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_initialization_allows_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_generic_subtypes_validation" time="0.002" /><testcase classname="tests.test_state" name="test_copying_leaves_same_object" time="0.001" /><testcase classname="tests.test_streaming" name="test_fails_when_generator_fails" time="0.001" /><testcase classname="tests.test_streaming" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_streaming" name="test_ends_when_generator_ends" time="0.001" /><testcase classname="tests.test_streaming" name="test_delivers_updates_when_generating" time="0.001" /><testcase classname="tests.test_streaming" name="test_streaming_context_variables_access_is_preserved" time="0.001" /><testcase classname="tests.test_streaming" name="test_nested_streaming_streams_correctly" time="0.001" /><testcase classname="tests.test_timeout" name="test_returns_result_when_returning_value" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_error" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_cancel" time="0.011" /><testcase classname="tests.test_timeout" name="test_raises_with_timeout" time="0.011" /></testsuite></testsuites>
1
+ <?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="97" time="1.784" timestamp="2025-05-19T14:57:38.045838+00:00" hostname="pkrvmf6wy0o8zjz"><testcase classname="tests.test_async_queue" name="test_fails_when_stream_fails" time="0.001" /><testcase classname="tests.test_async_queue" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_async_queue" name="test_ends_when_stream_ends" time="0.001" /><testcase classname="tests.test_async_queue" name="test_buffers_values_when_not_reading" time="0.001" /><testcase classname="tests.test_async_queue" name="test_delivers_buffer_when_streaming_fails" time="0.001" /><testcase classname="tests.test_async_queue" name="test_delivers_updates_when_sending" time="0.001" /><testcase classname="tests.test_async_queue" name="test_fails_when_sending_to_finished" time="0.001" /><testcase classname="tests.test_async_queue" name="test_ignores_when_finishing_when_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_fails_when_stream_fails" time="0.001" /><testcase classname="tests.test_async_stream" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ends_when_stream_ends" time="0.001" /><testcase classname="tests.test_async_stream" name="test_finishes_without_buffer" time="0.001" /><testcase classname="tests.test_async_stream" name="test_fails_without_buffer" time="0.001" /><testcase classname="tests.test_async_stream" name="test_delivers_updates_when_sending" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_sending_to_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_sending_to_failed" time="0.001" /><testcase classname="tests.test_async_stream" name="test_ignores_when_finishing_when_finished" time="0.001" /><testcase classname="tests.test_async_stream" name="test_delivers_all_when_sending_async" time="0.001" /><testcase classname="tests.test_attribute_path" name="test_id_path_points_to_self" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_attribute_path_points_to_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_nested_attribute_path_points_to_nested_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_recursive_attribute_path_points_to_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_list_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_tuple_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_mixed_tuple_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_dict_item_path_points_to_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_id_path_set_updates_self" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_attribute_path_set_updates_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_nested_attribute_path_set_updates_nested_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_recursive_attribute_set_updates_attribute" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_list_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_tuple_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_mixed_tuple_item_set_updates_item" time="0.000" /><testcase classname="tests.test_attribute_path" name="test_dict_item_path_set_updates_item" time="0.000" /><testcase classname="tests.test_auto_retry" name="test_returns_value_without_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_retries_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_logs_issue_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_exceeding_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_cancellation" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_retries_with_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_fails_with_not_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_returns_value_without_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_retries_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_exceeding_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_cancellation" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_when_cancelled" time="0.021" /><testcase classname="tests.test_auto_retry" name="test_async_uses_delay_with_errors" time="0.101" /><testcase classname="tests.test_auto_retry" name="test_async_uses_computed_delay_with_errors" time="0.106" /><testcase classname="tests.test_auto_retry" name="test_async_logs_issue_with_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_retries_with_selected_errors" time="0.001" /><testcase classname="tests.test_auto_retry" name="test_async_fails_with_not_selected_errors" time="0.001" /><testcase classname="tests.test_cache" name="test_returns_cache_value_with_same_argument" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_different_argument" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_limit_exceed" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_same_value_with_repeating_argument" time="0.000" /><testcase classname="tests.test_cache" name="test_fails_with_error" time="0.000" /><testcase classname="tests.test_cache" name="test_returns_fresh_value_with_expiration_time_exceed" time="0.021" /><testcase classname="tests.test_cache" name="test_async_returns_cache_value_with_same_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_different_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_limit_exceed" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_same_value_with_repeating_argument" time="0.001" /><testcase classname="tests.test_cache" name="test_async_returns_fresh_value_with_expiration_time_exceed" time="0.021" /><testcase classname="tests.test_cache" name="test_async_cancel_waiting_does_not_cancel_task" time="0.502" /><testcase classname="tests.test_cache" name="test_async_expiration_does_not_cancel_task" time="0.021" /><testcase classname="tests.test_cache" name="test_async_fails_with_error" time="0.001" /><testcase classname="tests.test_context" name="test_state_is_available_according_to_context" time="0.001" /><testcase classname="tests.test_context" name="test_state_update_updates_local_context" time="0.001" /><testcase classname="tests.test_context" name="test_exceptions_are_propagated" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_processes_all_elements" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_processes_elements_concurrently" time="0.302" /><testcase classname="tests.test_process_concurrently" name="test_handles_empty_source" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_propagates_handler_exceptions" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_ignores_handler_exceptions_when_configured" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_handles_source_exception" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_cancels_running_tasks_on_cancellation" time="0.101" /><testcase classname="tests.test_process_concurrently" name="test_respects_concurrency_limit" time="0.202" /><testcase classname="tests.test_process_concurrently" name="test_processes_elements_from_queue" time="0.052" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments" time="0.002" /><testcase classname="tests.test_state" name="test_basic_initializes_with_defaults" time="0.001" /><testcase classname="tests.test_state" name="test_basic_equals_checks_properties" time="0.000" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments_and_defaults" time="0.000" /><testcase classname="tests.test_state" name="test_parametrized_initializes_with_proper_parameters" time="0.000" /><testcase classname="tests.test_state" name="test_nested_initializes_with_proper_arguments" time="0.001" /><testcase classname="tests.test_state" name="test_dict_skips_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_initialization_allows_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_generic_subtypes_validation" time="0.001" /><testcase classname="tests.test_state" name="test_copying_leaves_same_object" time="0.000" /><testcase classname="tests.test_streaming" name="test_fails_when_generator_fails" time="0.001" /><testcase classname="tests.test_streaming" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_streaming" name="test_ends_when_generator_ends" time="0.001" /><testcase classname="tests.test_streaming" name="test_delivers_updates_when_generating" time="0.001" /><testcase classname="tests.test_streaming" name="test_streaming_context_variables_access_is_preserved" time="0.001" /><testcase classname="tests.test_streaming" name="test_nested_streaming_streams_correctly" time="0.001" /><testcase classname="tests.test_timeout" name="test_returns_result_when_returning_value" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_error" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_cancel" time="0.011" /><testcase classname="tests.test_timeout" name="test_raises_with_timeout" time="0.011" /></testsuite></testsuites>
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
  [project]
6
6
  name = "haiway"
7
7
  description = "Framework for dependency injection and state management within structured concurrency model."
8
- version = "0.20.0"
8
+ version = "0.20.1"
9
9
  readme = "README.md"
10
10
  maintainers = [
11
11
  { name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
@@ -8,7 +8,7 @@ from haiway.context import ctx
8
8
  __all__ = ("process_concurrently",)
9
9
 
10
10
 
11
- async def process_concurrently[Element]( # noqa: C901
11
+ async def process_concurrently[Element]( # noqa: C901, PLR0912
12
12
  source: AsyncIterator[Element],
13
13
  /,
14
14
  handler: Callable[[Element], Coroutine[Any, Any, None]],
@@ -37,12 +37,16 @@ async def process_concurrently[Element]( # noqa: C901
37
37
  assert concurrent_tasks > 0 # nosec: B101
38
38
  running: set[Task[None]] = set()
39
39
  try:
40
- while element := await anext(source, None):
40
+ while True:
41
+ element: Element = await anext(source)
42
+ running.add(ctx.spawn(handler, element))
41
43
  if len(running) < concurrent_tasks:
42
- running.add(ctx.spawn(handler, element))
43
44
  continue # keep spawning tasks
44
45
 
45
- completed, running = await wait(running, return_when=FIRST_COMPLETED)
46
+ completed, running = await wait(
47
+ running,
48
+ return_when=FIRST_COMPLETED,
49
+ )
46
50
 
47
51
  for task in completed:
48
52
  if exc := task.exception():
@@ -61,14 +65,21 @@ async def process_concurrently[Element]( # noqa: C901
61
65
 
62
66
  raise exc
63
67
 
68
+ except StopAsyncIteration:
69
+ pass # just stop and proceed to finally
70
+
64
71
  finally:
65
- completed, _ = await wait(running, return_when=ALL_COMPLETED)
66
- for task in completed:
67
- if exc := task.exception():
68
- if not ignore_exceptions:
69
- raise exc
70
-
71
- ctx.log_error(
72
- f"Concurrent processing error - {type(exc)}: {exc}",
73
- exception=exc,
74
- )
72
+ if running:
73
+ completed, _ = await wait(
74
+ running,
75
+ return_when=ALL_COMPLETED,
76
+ )
77
+ for task in completed:
78
+ if exc := task.exception():
79
+ if not ignore_exceptions:
80
+ raise exc
81
+
82
+ ctx.log_error(
83
+ f"Concurrent processing error - {type(exc)}: {exc}",
84
+ exception=exc,
85
+ )
@@ -551,6 +551,14 @@ class State(metaclass=StateMeta):
551
551
  case _:
552
552
  raise TypeError(f"Expected '{cls.__name__}', received '{type(value).__name__}'")
553
553
 
554
+ @classmethod
555
+ def from_mapping(
556
+ cls,
557
+ value: Mapping[str, Any],
558
+ /,
559
+ ) -> Self:
560
+ return cls(**value)
561
+
554
562
  def __init__(
555
563
  self,
556
564
  **kwargs: Any,
@@ -0,0 +1,191 @@
1
+ from asyncio import CancelledError, sleep
2
+ from collections import deque
3
+ from collections.abc import AsyncIterator, Iterable
4
+
5
+ from pytest import mark, raises
6
+
7
+ from haiway import AsyncQueue, ctx
8
+ from haiway.helpers.concurrent import process_concurrently
9
+
10
+
11
+ class FakeException(Exception):
12
+ pass
13
+
14
+
15
+ class Source:
16
+ """A simple async iterator for testing."""
17
+
18
+ def __init__(
19
+ self,
20
+ elements: Iterable[int] | None = None,
21
+ exception: Exception | None = None,
22
+ ) -> None:
23
+ self.elements = deque(elements or [])
24
+ self.exception = exception
25
+
26
+ def __aiter__(self) -> AsyncIterator[int]:
27
+ return self
28
+
29
+ async def __anext__(self) -> int:
30
+ if not self.elements:
31
+ if self.exception:
32
+ raise self.exception
33
+
34
+ raise StopAsyncIteration
35
+
36
+ return self.elements.popleft()
37
+
38
+
39
+ @mark.asyncio
40
+ async def test_processes_all_elements():
41
+ processed: list[int] = []
42
+
43
+ async def handler(element: int) -> None:
44
+ processed.append(element)
45
+
46
+ source = Source(range(10))
47
+ await process_concurrently(source, handler)
48
+ assert sorted(processed) == list(range(10))
49
+
50
+
51
+ @mark.asyncio
52
+ async def test_processes_elements_concurrently():
53
+ processed: list[int] = []
54
+ completion_order: list[int] = []
55
+
56
+ async def handler(element: int) -> None:
57
+ # Simulate varying processing times
58
+ await sleep(0.1 if element % 2 == 0 else 0.05)
59
+ processed.append(element)
60
+ completion_order.append(element)
61
+
62
+ source = Source(range(10))
63
+ await process_concurrently(source, handler, concurrent_tasks=3)
64
+ assert sorted(processed) == list(range(10))
65
+ # Odd numbers should complete before even numbers due to sleep times
66
+ assert completion_order != sorted(completion_order)
67
+
68
+
69
+ @mark.asyncio
70
+ async def test_handles_empty_source():
71
+ processed: list[int] = []
72
+
73
+ async def handler(element: int) -> None:
74
+ processed.append(element)
75
+
76
+ source = Source([])
77
+ await process_concurrently(source, handler)
78
+ assert processed == []
79
+
80
+
81
+ @mark.asyncio
82
+ async def test_propagates_handler_exceptions():
83
+ async def handler(element: int) -> None:
84
+ if element == 3:
85
+ raise FakeException("Test exception")
86
+
87
+ source = Source(range(10))
88
+ with raises(FakeException):
89
+ await process_concurrently(source, handler)
90
+
91
+
92
+ @mark.asyncio
93
+ async def test_ignores_handler_exceptions_when_configured():
94
+ processed: list[int] = []
95
+
96
+ async def handler(element: int) -> None:
97
+ if element == 3:
98
+ raise FakeException("Test exception")
99
+ processed.append(element)
100
+
101
+ source = Source([0, 1, 2, 3, 4, 5])
102
+ await process_concurrently(source, handler, ignore_exceptions=True)
103
+ assert sorted(processed) == [0, 1, 2, 4, 5]
104
+
105
+
106
+ @mark.asyncio
107
+ async def test_handles_source_exception():
108
+ processed: list[int] = []
109
+
110
+ async def handler(element: int) -> None:
111
+ processed.append(element)
112
+
113
+ source = Source([1, 2], FakeException("Source exception"))
114
+
115
+ with raises(FakeException):
116
+ await process_concurrently(source, handler)
117
+ assert sorted(processed) == [1, 2]
118
+
119
+
120
+ @mark.asyncio
121
+ async def test_cancels_running_tasks_on_cancellation():
122
+ processed: list[int] = []
123
+ started: list[int] = []
124
+
125
+ async def slow_handler(element: int) -> None:
126
+ started.append(element)
127
+ try:
128
+ await sleep(10) # Long sleep that should be cancelled
129
+ processed.append(element)
130
+
131
+ except CancelledError:
132
+ # Just to track cancellation, not needed in real code
133
+ pass
134
+
135
+ # Run the process with cancellation
136
+ with raises(CancelledError):
137
+ task = ctx.spawn(
138
+ process_concurrently,
139
+ Source(range(10)),
140
+ slow_handler,
141
+ )
142
+ # Give some time for tasks to start
143
+ await sleep(0.1)
144
+ # Cancel the main task
145
+ task.cancel()
146
+ await task
147
+ # Some tasks should have started but none should have completed
148
+ assert len(started) > 0
149
+ assert processed == []
150
+
151
+
152
+ @mark.asyncio
153
+ async def test_respects_concurrency_limit():
154
+ # Test that only the specified number of tasks run concurrently
155
+ currently_running: set[int] = set()
156
+ max_concurrent: int = 0
157
+ processed: list[int] = []
158
+
159
+ async def tracking_handler(element: int) -> None:
160
+ nonlocal max_concurrent
161
+ currently_running.add(element)
162
+ max_concurrent = max(max_concurrent, len(currently_running))
163
+ await sleep(0.05) # Short sleep to allow concurrency
164
+ currently_running.remove(element)
165
+ processed.append(element)
166
+
167
+ source = Source(range(10))
168
+ await process_concurrently(source, tracking_handler, concurrent_tasks=3)
169
+ assert max_concurrent <= 3
170
+ assert sorted(processed) == list(range(10))
171
+ assert currently_running == set()
172
+
173
+
174
+ @mark.asyncio
175
+ async def test_processes_elements_from_queue():
176
+ queue = AsyncQueue[int]()
177
+ processed: list[int] = []
178
+
179
+ async def handler(element: int) -> None:
180
+ processed.append(element + 1)
181
+
182
+ # Start processing in the background
183
+ task = ctx.spawn(process_concurrently, queue, handler)
184
+ # Add elements to the queue
185
+ for i in range(5):
186
+ queue.enqueue(i)
187
+ await sleep(0.01) # Small delay to ensure processing happens
188
+
189
+ queue.finish()
190
+ await task
191
+ assert sorted(processed) == list(range(1, 6))