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.
- {haiway-0.20.0 → haiway-0.20.1}/Makefile +1 -1
- {haiway-0.20.0 → haiway-0.20.1}/PKG-INFO +1 -1
- {haiway-0.20.0 → haiway-0.20.1}/junit/test-results.xml +1 -1
- {haiway-0.20.0 → haiway-0.20.1}/pyproject.toml +1 -1
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/concurrent.py +25 -14
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/structure.py +8 -0
- haiway-0.20.1/tests/test_process_concurrently.py +191 -0
- {haiway-0.20.0 → haiway-0.20.1}/uv.lock +205 -205
- {haiway-0.20.0 → haiway-0.20.1}/.github/workflows/ci.yml +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/.github/workflows/publish.yml +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/.gitignore +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/LICENSE +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/README.md +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/config/pre-push +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/.dockerignore +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/Dockerfile +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/Makefile +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/README.md +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/config/.env.example +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/config/unit.json +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/docker-compose.yml +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/pyproject.toml +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/__int__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/config.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/state.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/types.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/__main__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/__main__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/application.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/config.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/middlewares/context.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/technical.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/server/routes/todos.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/examples/fastAPI/uv.lock +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/guidelines/functionalities.md +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/guidelines/packages.md +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/access.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/disposables.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/identifier.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/observability.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/state.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/tasks.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/context/types.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/asynchrony.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/observability.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/opentelemetry/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/opentelemetry/observability.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/py.typed +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/attributes.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/path.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/requirement.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/state/validation.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/default.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/frozen.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/types/missing.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/always.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/collections.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/env.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/formatting.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/freezing.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/logs.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/noop.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/queue.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/src/haiway/utils/stream.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/__init__.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_async_queue.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_async_stream.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_attribute_path.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_auto_retry.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_cache.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_context.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_state.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_streaming.py +0 -0
- {haiway-0.20.0 → haiway-0.20.1}/tests/test_timeout.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.20.
|
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.
|
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
|
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(
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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))
|