haiway 0.21.4__tar.gz → 0.22.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.21.4 → haiway-0.22.1}/PKG-INFO +2 -2
- haiway-0.22.1/junit/test-results.xml +1 -0
- {haiway-0.21.4 → haiway-0.22.1}/pyproject.toml +2 -2
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/__init__.py +4 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/access.py +121 -6
- haiway-0.22.1/src/haiway/context/disposables.py +384 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/state.py +101 -12
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/__init__.py +4 -1
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/asynchrony.py +4 -6
- haiway-0.22.1/src/haiway/helpers/files.py +421 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/structure.py +18 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/types/missing.py +3 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/__init__.py +0 -2
- haiway-0.22.1/tests/test_disposables.py +362 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_state.py +178 -1
- haiway-0.22.1/uv.lock +491 -0
- haiway-0.21.4/examples/fastAPI/.dockerignore +0 -5
- haiway-0.21.4/examples/fastAPI/Dockerfile +0 -63
- haiway-0.21.4/examples/fastAPI/Makefile +0 -90
- haiway-0.21.4/examples/fastAPI/README.md +0 -3
- haiway-0.21.4/examples/fastAPI/config/.env.example +0 -11
- haiway-0.21.4/examples/fastAPI/config/unit.json +0 -50
- haiway-0.21.4/examples/fastAPI/docker-compose.yml +0 -68
- haiway-0.21.4/examples/fastAPI/pyproject.toml +0 -62
- haiway-0.21.4/examples/fastAPI/src/features/__int__.py +0 -0
- haiway-0.21.4/examples/fastAPI/src/features/todos/__init__.py +0 -5
- haiway-0.21.4/examples/fastAPI/src/features/todos/config.py +0 -1
- haiway-0.21.4/examples/fastAPI/src/features/todos/state.py +0 -23
- haiway-0.21.4/examples/fastAPI/src/features/todos/types.py +0 -15
- haiway-0.21.4/examples/fastAPI/src/features/todos/user_tasks.py +0 -19
- haiway-0.21.4/examples/fastAPI/src/integrations/postgres/__init__.py +0 -12
- haiway-0.21.4/examples/fastAPI/src/integrations/postgres/client.py +0 -171
- haiway-0.21.4/examples/fastAPI/src/integrations/postgres/config.py +0 -19
- haiway-0.21.4/examples/fastAPI/src/integrations/postgres/state.py +0 -77
- haiway-0.21.4/examples/fastAPI/src/integrations/postgres/types.py +0 -69
- haiway-0.21.4/examples/fastAPI/src/migrations/__init__.py +0 -3
- haiway-0.21.4/examples/fastAPI/src/migrations/__main__.py +0 -26
- haiway-0.21.4/examples/fastAPI/src/migrations/postgres/__init__.py +0 -5
- haiway-0.21.4/examples/fastAPI/src/migrations/postgres/execution.py +0 -80
- haiway-0.21.4/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -17
- haiway-0.21.4/examples/fastAPI/src/migrations/postgres/types.py +0 -9
- haiway-0.21.4/examples/fastAPI/src/server/__init__.py +0 -10
- haiway-0.21.4/examples/fastAPI/src/server/__main__.py +0 -10
- haiway-0.21.4/examples/fastAPI/src/server/application.py +0 -54
- haiway-0.21.4/examples/fastAPI/src/server/config.py +0 -11
- haiway-0.21.4/examples/fastAPI/src/server/middlewares/__init__.py +0 -5
- haiway-0.21.4/examples/fastAPI/src/server/middlewares/context.py +0 -133
- haiway-0.21.4/examples/fastAPI/src/server/routes/__init__.py +0 -7
- haiway-0.21.4/examples/fastAPI/src/server/routes/technical.py +0 -21
- haiway-0.21.4/examples/fastAPI/src/server/routes/todos.py +0 -32
- haiway-0.21.4/examples/fastAPI/src/solutions/__init__.py +0 -0
- haiway-0.21.4/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -7
- haiway-0.21.4/examples/fastAPI/src/solutions/user_tasks/config.py +0 -1
- haiway-0.21.4/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -218
- haiway-0.21.4/examples/fastAPI/src/solutions/user_tasks/state.py +0 -77
- haiway-0.21.4/examples/fastAPI/src/solutions/user_tasks/types.py +0 -71
- haiway-0.21.4/examples/fastAPI/uv.lock +0 -448
- haiway-0.21.4/junit/test-results.xml +0 -1
- haiway-0.21.4/src/haiway/context/disposables.py +0 -164
- haiway-0.21.4/src/haiway/utils/freezing.py +0 -46
- haiway-0.21.4/tests/__init__.py +0 -0
- haiway-0.21.4/uv.lock +0 -540
- {haiway-0.21.4 → haiway-0.22.1}/.github/workflows/ci.yml +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/.github/workflows/publish.yml +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/.gitignore +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/CLAUDE.md +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/LICENSE +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/Makefile +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/README.md +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/config/pre-push +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/guidelines/functionalities.md +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/guidelines/llms.txt +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/guidelines/packages.md +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/guidelines/state.md +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/__init__.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/identifier.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/observability.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/tasks.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/context/types.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/concurrent.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/observability.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/throttling.py +0 -0
- /haiway-0.21.4/src/haiway/helpers/timeouted.py → /haiway-0.22.1/src/haiway/helpers/timeouting.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/opentelemetry/__init__.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/opentelemetry/observability.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/py.typed +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/__init__.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/attributes.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/path.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/requirement.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/state/validation.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/types/__init__.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/types/default.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/always.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/collections.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/env.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/formatting.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/logs.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/noop.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/queue.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/src/haiway/utils/stream.py +0 -0
- {haiway-0.21.4/examples/fastAPI/src/integrations → haiway-0.22.1/tests}/__init__.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_async_queue.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_async_stream.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_attribute_path.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_attribute_requirement.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_auto_retry.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_cache.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_context.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_process_concurrently.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_state_validation.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_streaming.py +0 -0
- {haiway-0.21.4 → haiway-0.22.1}/tests/test_timeout.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.22.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
|
@@ -40,7 +40,7 @@ Requires-Dist: pyright~=1.1; extra == 'dev'
|
|
40
40
|
Requires-Dist: pytest-asyncio~=0.26; extra == 'dev'
|
41
41
|
Requires-Dist: pytest-cov~=6.1; extra == 'dev'
|
42
42
|
Requires-Dist: pytest~=8.3; extra == 'dev'
|
43
|
-
Requires-Dist: ruff~=0.
|
43
|
+
Requires-Dist: ruff~=0.12; extra == 'dev'
|
44
44
|
Provides-Extra: opentelemetry
|
45
45
|
Requires-Dist: opentelemetry-api~=1.33; extra == 'opentelemetry'
|
46
46
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc~=1.33; extra == 'opentelemetry'
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="159" time="1.948" timestamp="2025-06-18T12:25:24.482293+00:00" hostname="pkrvmxyh4eaekms"><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_attribute_requirement" name="test_equal_requirement" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_not_equal_requirement" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_contains_requirement" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_contains_any_requirement" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_contained_in_requirement" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_logical_and_or_requirements" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_filter" time="0.000" /><testcase classname="tests.test_attribute_requirement" name="test_immutability" 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_disposables" name="test_empty_initialization" time="0.000" /><testcase classname="tests.test_disposables" name="test_single_disposable_initialization" time="0.000" /><testcase classname="tests.test_disposables" name="test_multiple_disposables_initialization" time="0.000" /><testcase classname="tests.test_disposables" name="test_cannot_set_attributes" time="0.001" /><testcase classname="tests.test_disposables" name="test_cannot_delete_attributes" time="0.000" /><testcase classname="tests.test_disposables" name="test_empty_disposables_is_falsy" time="0.000" /><testcase classname="tests.test_disposables" name="test_non_empty_disposables_is_truthy" time="0.000" /><testcase classname="tests.test_disposables" name="test_setup_with_no_disposables" time="0.001" /><testcase classname="tests.test_disposables" name="test_setup_with_disposable_returning_none" time="0.001" /><testcase classname="tests.test_disposables" name="test_setup_with_disposable_returning_single_state" time="0.001" /><testcase classname="tests.test_disposables" name="test_setup_with_disposable_returning_multiple_states" time="0.001" /><testcase classname="tests.test_disposables" name="test_setup_with_multiple_disposables_mixed_returns" time="0.001" /><testcase classname="tests.test_disposables" name="test_setup_sets_loop_correctly" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_with_no_disposables" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_with_successful_cleanup" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_with_exception_context" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_with_multiple_exceptions_creates_group" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_with_single_exception_is_risen" time="0.001" /><testcase classname="tests.test_disposables" name="test_dispose_resets_loop_even_on_exception" time="0.001" /><testcase classname="tests.test_disposables" name="test_same_loop_cleanup" time="0.001" /><testcase classname="tests.test_disposables" name="test_with_real_async_context_managers" time="0.001" /><testcase classname="tests.test_disposables" name="test_nested_disposables_usage" time="0.001" /><testcase classname="tests.test_disposables" name="test_exception_during_setup_phase" time="0.001" /><testcase classname="tests.test_disposables" name="test_assertion_on_doubleprepare" time="0.001" /><testcase classname="tests.test_disposables" name="test_assertion_on_dispose_withoutprepare" 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.001" /><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.001" /><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.001" /><testcase classname="tests.test_state" name="test_hash_consistency_with_missing_values" time="0.001" /><testcase classname="tests.test_state" name="test_hash_with_unhashable_attributes" time="0.001" /><testcase classname="tests.test_state" name="test_hash_with_dict_key_order_independence" time="0.000" /><testcase classname="tests.test_state" name="test_hash_with_custom_objects" time="0.000" /><testcase classname="tests.test_state" name="test_hash_performance_with_many_attributes" time="0.001" /><testcase classname="tests.test_state" name="test_hash_with_nested_unhashable_collections" time="0.000" /><testcase classname="tests.test_state" name="test_hash_stability_across_instances" time="0.000" /><testcase classname="tests.test_state" name="test_hash_excludes_missing_values" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_basic_types" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_none_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_missing_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_literal_type" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_enum_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_sequence_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_set_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_mapping_type" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_tuple_type" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_union_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_callable_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_typed_dict" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_state_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_complex_types" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_recursive_state" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_generic_state" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validation_error_messages" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validation_any_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_with_defaults" time="0.000" /><testcase classname="tests.test_state_validation" name="test_attribute_validator_direct_usage" time="0.000" /><testcase classname="tests.test_state_validation" name="test_unsupported_type_annotation" 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.
|
8
|
+
version = "0.22.1"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -38,7 +38,7 @@ dev = [
|
|
38
38
|
"pytest~=8.3",
|
39
39
|
"pytest-asyncio~=0.26",
|
40
40
|
"pytest-cov~=6.1",
|
41
|
-
"ruff~=0.
|
41
|
+
"ruff~=0.12",
|
42
42
|
]
|
43
43
|
|
44
44
|
[tool.ruff]
|
@@ -13,6 +13,8 @@ from haiway.context import (
|
|
13
13
|
ctx,
|
14
14
|
)
|
15
15
|
from haiway.helpers import (
|
16
|
+
File,
|
17
|
+
FileAccess,
|
16
18
|
LoggerObservability,
|
17
19
|
asynchronous,
|
18
20
|
cache,
|
@@ -63,6 +65,8 @@ __all__ = (
|
|
63
65
|
"DefaultValue",
|
64
66
|
"Disposable",
|
65
67
|
"Disposables",
|
68
|
+
"File",
|
69
|
+
"FileAccess",
|
66
70
|
"LoggerObservability",
|
67
71
|
"Missing",
|
68
72
|
"MissingContext",
|
@@ -169,8 +169,8 @@ class ScopeContext:
|
|
169
169
|
StateContext(
|
170
170
|
state=ScopeState(
|
171
171
|
(
|
172
|
-
*await disposables.__aenter__(),
|
173
172
|
*self._state_context._state._state.values(),
|
173
|
+
*await disposables.prepare(),
|
174
174
|
)
|
175
175
|
),
|
176
176
|
),
|
@@ -187,7 +187,7 @@ class ScopeContext:
|
|
187
187
|
exc_tb: TracebackType | None,
|
188
188
|
) -> None:
|
189
189
|
if disposables := self._disposables:
|
190
|
-
await disposables.
|
190
|
+
await disposables.dispose(
|
191
191
|
exc_type=exc_type,
|
192
192
|
exc_val=exc_val,
|
193
193
|
exc_tb=exc_tb,
|
@@ -382,6 +382,48 @@ class ctx:
|
|
382
382
|
|
383
383
|
return StateContext.updated(element for element in state if element is not None)
|
384
384
|
|
385
|
+
@staticmethod
|
386
|
+
def disposables(
|
387
|
+
*disposables: Disposable | None,
|
388
|
+
) -> Disposables:
|
389
|
+
"""
|
390
|
+
Create a container for managing multiple disposable resources.
|
391
|
+
|
392
|
+
Disposables are async context managers that can provide state objects and
|
393
|
+
require proper cleanup. This method creates a Disposables container that
|
394
|
+
manages multiple disposable resources as a single unit, handling their
|
395
|
+
lifecycle and state propagation.
|
396
|
+
|
397
|
+
Parameters
|
398
|
+
----------
|
399
|
+
*disposables: Disposable | None
|
400
|
+
Variable number of disposable resources to be managed together.
|
401
|
+
None values are filtered out automatically.
|
402
|
+
|
403
|
+
Returns
|
404
|
+
-------
|
405
|
+
Disposables
|
406
|
+
A container that manages the lifecycle of all provided disposables
|
407
|
+
and propagates their state to the context when used with ctx.scope()
|
408
|
+
|
409
|
+
Examples
|
410
|
+
--------
|
411
|
+
Using disposables with database connections:
|
412
|
+
|
413
|
+
>>> from haiway import ctx
|
414
|
+
>>> async def main():
|
415
|
+
...
|
416
|
+
... async with ctx.scope(
|
417
|
+
... "database_work",
|
418
|
+
... disposables=(database_connection(),)
|
419
|
+
... ):
|
420
|
+
... # ConnectionState is now available in context
|
421
|
+
... conn_state = ctx.state(ConnectionState)
|
422
|
+
... await conn_state.connection.execute("SELECT 1")
|
423
|
+
"""
|
424
|
+
|
425
|
+
return Disposables(*(disposable for disposable in disposables if disposable is not None))
|
426
|
+
|
385
427
|
@staticmethod
|
386
428
|
def spawn[Result, **Arguments](
|
387
429
|
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
@@ -493,6 +535,37 @@ class ctx:
|
|
493
535
|
else:
|
494
536
|
raise RuntimeError("Attempting to cancel context out of asyncio task")
|
495
537
|
|
538
|
+
@staticmethod
|
539
|
+
def check_state[StateType: State](
|
540
|
+
state: type[StateType],
|
541
|
+
/,
|
542
|
+
*,
|
543
|
+
instantiate_defaults: bool = False,
|
544
|
+
) -> bool:
|
545
|
+
"""
|
546
|
+
Check if state object is available in the current context.
|
547
|
+
|
548
|
+
Verifies if state object of the specified type is available the current context.
|
549
|
+
Instantiates requested state if needed and possible.
|
550
|
+
|
551
|
+
Parameters
|
552
|
+
----------
|
553
|
+
state: type[StateType]
|
554
|
+
The type of state to check
|
555
|
+
|
556
|
+
instantiate_defaults: bool = False
|
557
|
+
Control if default value should be instantiated during check.
|
558
|
+
|
559
|
+
Returns
|
560
|
+
-------
|
561
|
+
bool
|
562
|
+
True if state is available, otherwise False.
|
563
|
+
"""
|
564
|
+
return StateContext.check_state(
|
565
|
+
state,
|
566
|
+
instantiate_defaults=instantiate_defaults,
|
567
|
+
)
|
568
|
+
|
496
569
|
@staticmethod
|
497
570
|
def state[StateType: State](
|
498
571
|
state: type[StateType],
|
@@ -500,18 +573,60 @@ class ctx:
|
|
500
573
|
default: StateType | None = None,
|
501
574
|
) -> StateType:
|
502
575
|
"""
|
503
|
-
Access current scope context
|
504
|
-
|
576
|
+
Access state from the current scope context by its type.
|
577
|
+
|
578
|
+
Retrieves state objects that have been propagated within the current execution context.
|
579
|
+
State objects are automatically made available through context scopes and disposables.
|
580
|
+
If no matching state is found, creates a default instance if possible.
|
505
581
|
|
506
582
|
Parameters
|
507
583
|
----------
|
508
584
|
state: type[StateType]
|
509
|
-
type
|
585
|
+
The State class type to retrieve from the current context
|
586
|
+
default: StateType | None, default=None
|
587
|
+
Optional default instance to return if state is not found in context.
|
588
|
+
If None and no state is found, a new instance will be created if possible.
|
510
589
|
|
511
590
|
Returns
|
512
591
|
-------
|
513
592
|
StateType
|
514
|
-
|
593
|
+
The state instance from the current context or a default/new instance
|
594
|
+
|
595
|
+
Raises
|
596
|
+
------
|
597
|
+
RuntimeError
|
598
|
+
If called outside of any scope context
|
599
|
+
TypeError
|
600
|
+
If no state is found and no default can be created
|
601
|
+
|
602
|
+
Examples
|
603
|
+
--------
|
604
|
+
Accessing configuration state:
|
605
|
+
|
606
|
+
>>> from haiway import ctx, State
|
607
|
+
>>>
|
608
|
+
>>> class ApiConfig(State):
|
609
|
+
... base_url: str = "https://api.example.com"
|
610
|
+
... timeout: int = 30
|
611
|
+
>>>
|
612
|
+
>>> async def fetch_data():
|
613
|
+
... config = ctx.state(ApiConfig)
|
614
|
+
... # Use config.base_url and config.timeout
|
615
|
+
>>>
|
616
|
+
>>> async with ctx.scope("api", ApiConfig(base_url="https://custom.api.com")):
|
617
|
+
... await fetch_data() # Uses custom config
|
618
|
+
|
619
|
+
Accessing state with default:
|
620
|
+
|
621
|
+
>>> cache_config = ctx.state(CacheConfig, default=CacheConfig(ttl=3600))
|
622
|
+
|
623
|
+
Within service classes:
|
624
|
+
|
625
|
+
>>> class UserService(State):
|
626
|
+
... @classmethod
|
627
|
+
... async def get_user(cls, user_id: str) -> User:
|
628
|
+
... config = ctx.state(DatabaseConfig)
|
629
|
+
... # Use config to connect to database
|
515
630
|
"""
|
516
631
|
return StateContext.state(
|
517
632
|
state,
|
@@ -0,0 +1,384 @@
|
|
1
|
+
from asyncio import (
|
2
|
+
AbstractEventLoop,
|
3
|
+
gather,
|
4
|
+
get_running_loop,
|
5
|
+
iscoroutinefunction,
|
6
|
+
run_coroutine_threadsafe,
|
7
|
+
wrap_future,
|
8
|
+
)
|
9
|
+
from collections.abc import Callable, Coroutine, Iterable
|
10
|
+
from contextlib import AbstractAsyncContextManager
|
11
|
+
from itertools import chain
|
12
|
+
from types import TracebackType
|
13
|
+
from typing import Any, final
|
14
|
+
|
15
|
+
from haiway.context.state import StateContext
|
16
|
+
from haiway.state import State
|
17
|
+
from haiway.utils.mimic import mimic_function
|
18
|
+
|
19
|
+
__all__ = (
|
20
|
+
"Disposable",
|
21
|
+
"Disposables",
|
22
|
+
)
|
23
|
+
|
24
|
+
type Disposable = AbstractAsyncContextManager[Iterable[State] | State | None]
|
25
|
+
"""
|
26
|
+
A type alias for asynchronous context managers that provide disposable resources.
|
27
|
+
|
28
|
+
Represents an asynchronous resource that needs proper cleanup when no longer needed.
|
29
|
+
When entered, it may return State instances that will be automatically propagated
|
30
|
+
to the current context. The resource is guaranteed to be properly disposed of
|
31
|
+
when the context exits, even if exceptions occur.
|
32
|
+
|
33
|
+
Type Details
|
34
|
+
------------
|
35
|
+
- Must be an async context manager (implements __aenter__ and __aexit__)
|
36
|
+
- Can return None, a single State instance, or multiple State instances
|
37
|
+
- State instances are automatically added to the context scope
|
38
|
+
- Cleanup is handled automatically when the context exits
|
39
|
+
|
40
|
+
Examples
|
41
|
+
--------
|
42
|
+
Creating a disposable database connection:
|
43
|
+
|
44
|
+
>>> import contextlib
|
45
|
+
>>> from haiway import State
|
46
|
+
>>>
|
47
|
+
>>> class DatabaseState(State):
|
48
|
+
... connection: DatabaseConnection
|
49
|
+
...
|
50
|
+
>>> @contextlib.asynccontextmanager
|
51
|
+
>>> async def database_disposable():
|
52
|
+
... connection = await create_database_connection()
|
53
|
+
... try:
|
54
|
+
... yield DatabaseState(connection=connection)
|
55
|
+
... finally:
|
56
|
+
... await connection.close()
|
57
|
+
"""
|
58
|
+
|
59
|
+
|
60
|
+
@final
|
61
|
+
class Disposables:
|
62
|
+
"""
|
63
|
+
A container for multiple Disposable resources that manages their lifecycle.
|
64
|
+
|
65
|
+
This class provides a way to handle multiple disposable resources as a single unit,
|
66
|
+
entering all of them in parallel when the container is entered and exiting all of
|
67
|
+
them when the container is exited. Any states returned by the disposables are
|
68
|
+
collected and automatically propagated to the context.
|
69
|
+
|
70
|
+
Key Features
|
71
|
+
------------
|
72
|
+
- Parallel setup and cleanup of all contained disposables
|
73
|
+
- Automatic state collection and context propagation
|
74
|
+
- Thread-safe cross-event-loop disposal
|
75
|
+
- Exception handling with BaseExceptionGroup for multiple failures
|
76
|
+
- Immutable after initialization
|
77
|
+
|
78
|
+
The class is designed to work seamlessly with ctx.scope() and ensures proper
|
79
|
+
resource cleanup even when exceptions occur during setup or teardown.
|
80
|
+
|
81
|
+
Examples
|
82
|
+
--------
|
83
|
+
Creating and using multiple disposables:
|
84
|
+
|
85
|
+
>>> from haiway import ctx
|
86
|
+
>>> async def main():
|
87
|
+
... disposables = Disposables(
|
88
|
+
... database_disposable(),
|
89
|
+
... cache_disposable()
|
90
|
+
... )
|
91
|
+
...
|
92
|
+
... async with ctx.scope("app", disposables=disposables):
|
93
|
+
... # Both DatabaseState and CacheState are available
|
94
|
+
... db = ctx.state(DatabaseState)
|
95
|
+
... cache = ctx.state(CacheState)
|
96
|
+
|
97
|
+
Direct context manager usage:
|
98
|
+
|
99
|
+
>>> async def process_data():
|
100
|
+
... disposables = Disposables(
|
101
|
+
... create_temp_file_disposable(),
|
102
|
+
... create_network_connection_disposable()
|
103
|
+
... )
|
104
|
+
...
|
105
|
+
... async with disposables:
|
106
|
+
... # Resources are set up in parallel
|
107
|
+
... temp_file = ctx.state(TempFileState)
|
108
|
+
... network = ctx.state(NetworkState)
|
109
|
+
...
|
110
|
+
... # Process data using both resources
|
111
|
+
...
|
112
|
+
... # All resources cleaned up automatically
|
113
|
+
"""
|
114
|
+
|
115
|
+
__slots__ = (
|
116
|
+
"_disposables",
|
117
|
+
"_loop",
|
118
|
+
"_state_context",
|
119
|
+
)
|
120
|
+
|
121
|
+
def __init__(
|
122
|
+
self,
|
123
|
+
*disposables: Disposable,
|
124
|
+
) -> None:
|
125
|
+
"""
|
126
|
+
Initialize a collection of disposable resources.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
*disposables: Disposable
|
131
|
+
Variable number of disposable resources to be managed together.
|
132
|
+
"""
|
133
|
+
self._disposables: tuple[Disposable, ...]
|
134
|
+
object.__setattr__(
|
135
|
+
self,
|
136
|
+
"_disposables",
|
137
|
+
disposables,
|
138
|
+
)
|
139
|
+
self._state_context: StateContext | None
|
140
|
+
object.__setattr__(
|
141
|
+
self,
|
142
|
+
"_state_context",
|
143
|
+
None,
|
144
|
+
)
|
145
|
+
self._loop: AbstractEventLoop | None
|
146
|
+
object.__setattr__(
|
147
|
+
self,
|
148
|
+
"_loop",
|
149
|
+
None,
|
150
|
+
)
|
151
|
+
|
152
|
+
def __setattr__(
|
153
|
+
self,
|
154
|
+
name: str,
|
155
|
+
value: Any,
|
156
|
+
) -> Any:
|
157
|
+
raise AttributeError(
|
158
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
159
|
+
f" attribute - '{name}' cannot be modified"
|
160
|
+
)
|
161
|
+
|
162
|
+
def __delattr__(
|
163
|
+
self,
|
164
|
+
name: str,
|
165
|
+
) -> None:
|
166
|
+
raise AttributeError(
|
167
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
168
|
+
f" attribute - '{name}' cannot be deleted"
|
169
|
+
)
|
170
|
+
|
171
|
+
def __bool__(self) -> bool:
|
172
|
+
"""
|
173
|
+
Check if this container has any disposables.
|
174
|
+
|
175
|
+
Returns
|
176
|
+
-------
|
177
|
+
bool
|
178
|
+
True if there are disposables, False otherwise.
|
179
|
+
"""
|
180
|
+
return len(self._disposables) > 0
|
181
|
+
|
182
|
+
async def _setup(
|
183
|
+
self,
|
184
|
+
disposable: Disposable,
|
185
|
+
/,
|
186
|
+
) -> Iterable[State]:
|
187
|
+
match await disposable.__aenter__():
|
188
|
+
case None:
|
189
|
+
return ()
|
190
|
+
|
191
|
+
case State() as single:
|
192
|
+
return (single,)
|
193
|
+
|
194
|
+
case multiple:
|
195
|
+
return multiple
|
196
|
+
|
197
|
+
async def prepare(self) -> Iterable[State]:
|
198
|
+
"""
|
199
|
+
Enter all contained disposables asynchronously.
|
200
|
+
|
201
|
+
Enters all disposables in parallel and collects any State objects they return.
|
202
|
+
"""
|
203
|
+
assert self._loop is None # nosec: B101
|
204
|
+
object.__setattr__(
|
205
|
+
self,
|
206
|
+
"_loop",
|
207
|
+
get_running_loop(),
|
208
|
+
)
|
209
|
+
|
210
|
+
return tuple(
|
211
|
+
chain.from_iterable(
|
212
|
+
await gather(
|
213
|
+
*[self._setup(disposable) for disposable in self._disposables],
|
214
|
+
)
|
215
|
+
)
|
216
|
+
)
|
217
|
+
|
218
|
+
async def __aenter__(self) -> None:
|
219
|
+
"""
|
220
|
+
Enter all contained disposables asynchronously.
|
221
|
+
|
222
|
+
Enters all disposables in parallel and collects any State objects they return updating
|
223
|
+
current state context.
|
224
|
+
"""
|
225
|
+
|
226
|
+
assert self._state_context is None, "Context reentrance is not allowed" # nosec: B101
|
227
|
+
state_context = StateContext.updated(await self.prepare())
|
228
|
+
state_context.__enter__()
|
229
|
+
object.__setattr__(
|
230
|
+
self,
|
231
|
+
"_state_context",
|
232
|
+
state_context,
|
233
|
+
)
|
234
|
+
|
235
|
+
async def _cleanup(
|
236
|
+
self,
|
237
|
+
/,
|
238
|
+
exc_type: type[BaseException] | None,
|
239
|
+
exc_val: BaseException | None,
|
240
|
+
exc_tb: TracebackType | None,
|
241
|
+
) -> list[bool | BaseException | None]:
|
242
|
+
return await gather(
|
243
|
+
*[
|
244
|
+
disposable.__aexit__(
|
245
|
+
exc_type,
|
246
|
+
exc_val,
|
247
|
+
exc_tb,
|
248
|
+
)
|
249
|
+
for disposable in self._disposables
|
250
|
+
],
|
251
|
+
return_exceptions=True,
|
252
|
+
)
|
253
|
+
|
254
|
+
async def dispose(
|
255
|
+
self,
|
256
|
+
/,
|
257
|
+
exc_type: type[BaseException] | None = None,
|
258
|
+
exc_val: BaseException | None = None,
|
259
|
+
exc_tb: TracebackType | None = None,
|
260
|
+
) -> None:
|
261
|
+
"""
|
262
|
+
Exit all contained disposables asynchronously.
|
263
|
+
|
264
|
+
Properly disposes of all resources by calling their __aexit__ methods in parallel.
|
265
|
+
If multiple disposables raise exceptions, they are collected into a BaseExceptionGroup.
|
266
|
+
|
267
|
+
Parameters
|
268
|
+
----------
|
269
|
+
exc_type: type[BaseException] | None
|
270
|
+
The type of exception that caused the context to be exited
|
271
|
+
exc_val: BaseException | None
|
272
|
+
The exception that caused the context to be exited
|
273
|
+
exc_tb: TracebackType | None
|
274
|
+
The traceback for the exception that caused the context to be exited
|
275
|
+
|
276
|
+
Raises
|
277
|
+
------
|
278
|
+
BaseExceptionGroup
|
279
|
+
If multiple disposables raise exceptions during exit
|
280
|
+
"""
|
281
|
+
assert self._loop is not None # nosec: B101
|
282
|
+
results: list[bool | BaseException | None]
|
283
|
+
|
284
|
+
try:
|
285
|
+
current_loop: AbstractEventLoop = get_running_loop()
|
286
|
+
if self._loop != current_loop:
|
287
|
+
results = await wrap_future(
|
288
|
+
run_coroutine_threadsafe(
|
289
|
+
self._cleanup(
|
290
|
+
exc_type,
|
291
|
+
exc_val,
|
292
|
+
exc_tb,
|
293
|
+
),
|
294
|
+
loop=self._loop,
|
295
|
+
)
|
296
|
+
)
|
297
|
+
|
298
|
+
else:
|
299
|
+
results = await self._cleanup(
|
300
|
+
exc_type,
|
301
|
+
exc_val,
|
302
|
+
exc_tb,
|
303
|
+
)
|
304
|
+
|
305
|
+
finally:
|
306
|
+
object.__setattr__(
|
307
|
+
self,
|
308
|
+
"_loop",
|
309
|
+
None,
|
310
|
+
)
|
311
|
+
|
312
|
+
exceptions: list[BaseException] = [exc for exc in results if isinstance(exc, BaseException)]
|
313
|
+
|
314
|
+
match len(exceptions):
|
315
|
+
case 0:
|
316
|
+
return
|
317
|
+
|
318
|
+
case 1:
|
319
|
+
raise exceptions[0]
|
320
|
+
|
321
|
+
case _:
|
322
|
+
raise BaseExceptionGroup("Disposables cleanup errors", exceptions)
|
323
|
+
|
324
|
+
async def __aexit__(
|
325
|
+
self,
|
326
|
+
exc_type: type[BaseException] | None,
|
327
|
+
exc_val: BaseException | None,
|
328
|
+
exc_tb: TracebackType | None,
|
329
|
+
) -> None:
|
330
|
+
"""
|
331
|
+
Exit all contained disposables asynchronously.
|
332
|
+
|
333
|
+
Properly disposes of all resources by calling their __aexit__ methods in parallel.
|
334
|
+
If multiple disposables raise exceptions, they are collected into a BaseExceptionGroup.
|
335
|
+
Additionally, produced state context will be also exited resetting state to previous.
|
336
|
+
|
337
|
+
Parameters
|
338
|
+
----------
|
339
|
+
exc_type: type[BaseException] | None
|
340
|
+
The type of exception that caused the context to be exited
|
341
|
+
exc_val: BaseException | None
|
342
|
+
The exception that caused the context to be exited
|
343
|
+
exc_tb: TracebackType | None
|
344
|
+
The traceback for the exception that caused the context to be exited
|
345
|
+
|
346
|
+
Raises
|
347
|
+
------
|
348
|
+
BaseExceptionGroup
|
349
|
+
If multiple disposables raise exceptions during exit
|
350
|
+
"""
|
351
|
+
assert self._state_context is not None, "Unbalanced context enter/exit" # nosec: B101
|
352
|
+
try:
|
353
|
+
self._state_context.__exit__(
|
354
|
+
exc_type,
|
355
|
+
exc_val,
|
356
|
+
exc_tb,
|
357
|
+
)
|
358
|
+
object.__setattr__(
|
359
|
+
self,
|
360
|
+
"_state_context",
|
361
|
+
None,
|
362
|
+
)
|
363
|
+
|
364
|
+
finally:
|
365
|
+
await self.dispose(
|
366
|
+
exc_type,
|
367
|
+
exc_val,
|
368
|
+
exc_tb,
|
369
|
+
)
|
370
|
+
|
371
|
+
def __call__[Result, **Arguments](
|
372
|
+
self,
|
373
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
374
|
+
) -> Callable[Arguments, Coroutine[Any, Any, Result]]:
|
375
|
+
assert iscoroutinefunction(function) # nosec: B101
|
376
|
+
|
377
|
+
async def async_context(
|
378
|
+
*args: Arguments.args,
|
379
|
+
**kwargs: Arguments.kwargs,
|
380
|
+
) -> Result:
|
381
|
+
async with self:
|
382
|
+
return await function(*args, **kwargs)
|
383
|
+
|
384
|
+
return mimic_function(function, within=async_context)
|