haiway 0.24.3__tar.gz → 0.25.0__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 (79) hide show
  1. {haiway-0.24.3 → haiway-0.25.0}/Makefile +1 -1
  2. {haiway-0.24.3 → haiway-0.25.0}/PKG-INFO +1 -1
  3. haiway-0.25.0/junit/test-results.xml +1 -0
  4. {haiway-0.24.3 → haiway-0.25.0}/pyproject.toml +1 -1
  5. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/__init__.py +4 -1
  6. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/__init__.py +4 -0
  7. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/access.py +214 -63
  8. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/disposables.py +5 -119
  9. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/identifier.py +19 -44
  10. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/observability.py +22 -142
  11. haiway-0.25.0/src/haiway/context/presets.py +337 -0
  12. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/state.py +38 -84
  13. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/tasks.py +7 -10
  14. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/observability.py +4 -6
  15. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/opentelemetry/observability.py +5 -5
  16. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/__init__.py +2 -0
  17. haiway-0.25.0/src/haiway/state/immutable.py +127 -0
  18. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/structure.py +63 -117
  19. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/validation.py +95 -60
  20. haiway-0.25.0/tests/test_context_presets.py +464 -0
  21. {haiway-0.24.3 → haiway-0.25.0}/tests/test_disposables.py +0 -35
  22. haiway-0.25.0/tests/test_state_typing.py +66 -0
  23. {haiway-0.24.3 → haiway-0.25.0}/tests/test_state_validation.py +5 -47
  24. {haiway-0.24.3 → haiway-0.25.0}/uv.lock +10 -10
  25. haiway-0.24.3/junit/test-results.xml +0 -1
  26. {haiway-0.24.3 → haiway-0.25.0}/.claude/settings.json +0 -0
  27. {haiway-0.24.3 → haiway-0.25.0}/.github/workflows/ci.yml +0 -0
  28. {haiway-0.24.3 → haiway-0.25.0}/.github/workflows/publish.yml +0 -0
  29. {haiway-0.24.3 → haiway-0.25.0}/.gitignore +0 -0
  30. {haiway-0.24.3 → haiway-0.25.0}/CLAUDE.md +0 -0
  31. {haiway-0.24.3 → haiway-0.25.0}/LICENSE +0 -0
  32. {haiway-0.24.3 → haiway-0.25.0}/README.md +0 -0
  33. {haiway-0.24.3 → haiway-0.25.0}/config/pre-push +0 -0
  34. {haiway-0.24.3 → haiway-0.25.0}/guidelines/functionalities.md +0 -0
  35. {haiway-0.24.3 → haiway-0.25.0}/guidelines/llms.txt +0 -0
  36. {haiway-0.24.3 → haiway-0.25.0}/guidelines/packages.md +0 -0
  37. {haiway-0.24.3 → haiway-0.25.0}/guidelines/state.md +0 -0
  38. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/context/types.py +0 -0
  39. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/__init__.py +0 -0
  40. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/asynchrony.py +0 -0
  41. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/caching.py +0 -0
  42. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/concurrent.py +0 -0
  43. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/files.py +0 -0
  44. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/retries.py +0 -0
  45. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/throttling.py +0 -0
  46. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/timeouting.py +0 -0
  47. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/helpers/tracing.py +0 -0
  48. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/opentelemetry/__init__.py +0 -0
  49. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/py.typed +0 -0
  50. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/attributes.py +0 -0
  51. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/path.py +0 -0
  52. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/state/requirement.py +0 -0
  53. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/types/__init__.py +0 -0
  54. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/types/default.py +0 -0
  55. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/types/missing.py +0 -0
  56. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/__init__.py +0 -0
  57. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/always.py +0 -0
  58. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/collections.py +0 -0
  59. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/env.py +0 -0
  60. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/formatting.py +0 -0
  61. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/logs.py +0 -0
  62. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/mimic.py +0 -0
  63. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/noop.py +0 -0
  64. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/queue.py +0 -0
  65. {haiway-0.24.3 → haiway-0.25.0}/src/haiway/utils/stream.py +0 -0
  66. {haiway-0.24.3 → haiway-0.25.0}/tests/__init__.py +0 -0
  67. {haiway-0.24.3 → haiway-0.25.0}/tests/test_async_queue.py +0 -0
  68. {haiway-0.24.3 → haiway-0.25.0}/tests/test_async_stream.py +0 -0
  69. {haiway-0.24.3 → haiway-0.25.0}/tests/test_attribute_path.py +0 -0
  70. {haiway-0.24.3 → haiway-0.25.0}/tests/test_attribute_requirement.py +0 -0
  71. {haiway-0.24.3 → haiway-0.25.0}/tests/test_auto_retry.py +0 -0
  72. {haiway-0.24.3 → haiway-0.25.0}/tests/test_cache.py +0 -0
  73. {haiway-0.24.3 → haiway-0.25.0}/tests/test_context.py +0 -0
  74. {haiway-0.24.3 → haiway-0.25.0}/tests/test_execute_concurrently.py +0 -0
  75. {haiway-0.24.3 → haiway-0.25.0}/tests/test_process_concurrently.py +0 -0
  76. {haiway-0.24.3 → haiway-0.25.0}/tests/test_state.py +0 -0
  77. {haiway-0.24.3 → haiway-0.25.0}/tests/test_stream_concurrently.py +0 -0
  78. {haiway-0.24.3 → haiway-0.25.0}/tests/test_streaming.py +0 -0
  79. {haiway-0.24.3 → haiway-0.25.0}/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.6
13
+ UV_VERSION := 0.7.20
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.24.3
3
+ Version: 0.25.0
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
@@ -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="199" time="3.844" timestamp="2025-07-14T10:25:32.182364+00:00" hostname="pkrvmq0rgcvqdmg"><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.001" /><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.001" /><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.102" /><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.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.501" /><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_context_presets" name="test_preset_creation" time="0.000" /><testcase classname="tests.test_context_presets" name="test_preset_with_state" time="0.000" /><testcase classname="tests.test_context_presets" name="test_preset_with_disposable" time="0.000" /><testcase classname="tests.test_context_presets" name="test_preset_extended" time="0.000" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_with_static_state" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_with_state_factory" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_with_multiple_states_factory" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_with_disposables" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_with_multiple_disposables" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_prepare_mixed_state_and_disposables" time="0.001" /><testcase classname="tests.test_context_presets" name="test_registry_creation" time="0.000" /><testcase classname="tests.test_context_presets" name="test_registry_immutable" time="0.000" /><testcase classname="tests.test_context_presets" name="test_scope_with_preset" time="0.001" /><testcase classname="tests.test_context_presets" name="test_scope_preset_with_override" time="0.001" /><testcase classname="tests.test_context_presets" name="test_scope_preset_not_found" time="0.001" /><testcase classname="tests.test_context_presets" name="test_sync_scope_with_preset_fails" time="0.000" /><testcase classname="tests.test_context_presets" name="test_preset_with_async_state_factory" time="0.021" /><testcase classname="tests.test_context_presets" name="test_preset_with_disposable_lifecycle" time="0.001" /><testcase classname="tests.test_context_presets" name="test_nested_preset_registries" time="0.001" /><testcase classname="tests.test_context_presets" name="test_preset_with_mixed_state_sources" time="0.011" /><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.000" /><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_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_execute_concurrently" name="test_processes_all_elements" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_preserves_order" time="0.302" /><testcase classname="tests.test_execute_concurrently" name="test_handles_empty_collection" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_propagates_handler_exceptions" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_returns_exceptions_when_configured" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_cancels_running_tasks_on_cancellation" time="0.101" /><testcase classname="tests.test_execute_concurrently" name="test_respects_concurrency_limit" time="0.202" /><testcase classname="tests.test_execute_concurrently" name="test_works_with_different_types" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_handles_mixed_success_and_failure" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_works_with_sets_and_tuples" time="0.001" /><testcase classname="tests.test_execute_concurrently" name="test_exception_details_preserved" 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.001" /><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.000" /><testcase classname="tests.test_state" name="test_hash_consistency_with_missing_values" time="0.000" /><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.000" /><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.000" /><testcase classname="tests.test_state_typing" name="test_state_typing_subclass_and_instance_checks" 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.001" /><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.000" /><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.000" /><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_stream_concurrently" name="test_merges_two_streams" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_interleaves_based_on_timing" time="0.152" /><testcase classname="tests.test_stream_concurrently" name="test_handles_empty_iterators" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_handles_different_lengths" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_propagates_exceptions_from_source_a" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_propagates_exceptions_from_source_b" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_cancellation_cancels_both_sources" time="0.201" /><testcase classname="tests.test_stream_concurrently" name="test_works_with_different_types" time="0.001" /><testcase classname="tests.test_stream_concurrently" name="test_immediate_yield" time="0.601" /><testcase classname="tests.test_stream_concurrently" name="test_concurrent_execution" time="0.201" /><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.24.3"
8
+ version = "0.25.0"
9
9
  readme = "README.md"
10
10
  maintainers = [
11
11
  { name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
@@ -1,4 +1,5 @@
1
1
  from haiway.context import (
2
+ ContextPresets,
2
3
  Disposable,
3
4
  Disposables,
4
5
  MissingContext,
@@ -26,7 +27,7 @@ from haiway.helpers import (
26
27
  timeout,
27
28
  traced,
28
29
  )
29
- from haiway.state import AttributePath, AttributeRequirement, State
30
+ from haiway.state import AttributePath, AttributeRequirement, Immutable, State
30
31
  from haiway.types import (
31
32
  MISSING,
32
33
  Default,
@@ -64,12 +65,14 @@ __all__ = (
64
65
  "AsyncStream",
65
66
  "AttributePath",
66
67
  "AttributeRequirement",
68
+ "ContextPresets",
67
69
  "Default",
68
70
  "DefaultValue",
69
71
  "Disposable",
70
72
  "Disposables",
71
73
  "File",
72
74
  "FileAccess",
75
+ "Immutable",
73
76
  "LoggerObservability",
74
77
  "Missing",
75
78
  "MissingContext",
@@ -14,12 +14,16 @@ from haiway.context.observability import (
14
14
  ObservabilityScopeExiting,
15
15
  ObservabilityTraceIdentifying,
16
16
  )
17
+ from haiway.context.presets import ContextPresets
17
18
  from haiway.context.state import StateContext
18
19
  from haiway.context.types import MissingContext, MissingState
20
+ from haiway.state import Immutable
19
21
 
20
22
  __all__ = (
23
+ "ContextPresets",
21
24
  "Disposable",
22
25
  "Disposables",
26
+ "Immutable",
23
27
  "MissingContext",
24
28
  "MissingState",
25
29
  "Observability",
@@ -8,6 +8,7 @@ from collections.abc import (
8
8
  AsyncGenerator,
9
9
  AsyncIterator,
10
10
  Callable,
11
+ Collection,
11
12
  Coroutine,
12
13
  Iterable,
13
14
  Mapping,
@@ -24,16 +25,20 @@ from haiway.context.observability import (
24
25
  ObservabilityContext,
25
26
  ObservabilityLevel,
26
27
  )
28
+ from haiway.context.presets import (
29
+ ContextPresets,
30
+ ContextPresetsRegistry,
31
+ ContextPresetsRegistryContext,
32
+ )
27
33
  from haiway.context.state import ScopeState, StateContext
28
34
  from haiway.context.tasks import TaskGroupContext
29
- from haiway.state import State
35
+ from haiway.state import Immutable, State
30
36
  from haiway.utils.stream import AsyncStream
31
37
 
32
38
  __all__ = ("ctx",)
33
39
 
34
40
 
35
- @final
36
- class ScopeContext:
41
+ class ScopeContext(Immutable):
37
42
  """
38
43
  Context manager for executing code within a defined scope.
39
44
 
@@ -45,42 +50,62 @@ class ScopeContext:
45
50
  to create scope contexts.
46
51
  """
47
52
 
48
- __slots__ = (
49
- "_disposables",
50
- "_identifier",
51
- "_observability_context",
52
- "_state_context",
53
- "_task_group_context",
54
- )
53
+ _identifier: ScopeIdentifier
54
+ _state: Collection[State]
55
+ _captured_state: Collection[State]
56
+ _resolved_state_context: StateContext | None
57
+ _disposables: Disposables | None
58
+ _presets: ContextPresets | None
59
+ _presets_disposables: Disposables | None
60
+ _observability_context: ObservabilityContext
61
+ _task_group_context: TaskGroupContext | None
55
62
 
56
63
  def __init__(
57
64
  self,
58
- label: str,
65
+ name: str,
59
66
  task_group: TaskGroup | None,
60
67
  state: tuple[State, ...],
61
68
  disposables: Disposables | None,
62
69
  observability: Observability | Logger | None,
63
70
  ) -> None:
64
- self._identifier: ScopeIdentifier
65
71
  object.__setattr__(
66
72
  self,
67
73
  "_identifier",
68
- ScopeIdentifier.scope(label),
74
+ ScopeIdentifier.scope(name),
69
75
  )
70
- # prepare state context to capture current state
71
- self._state_context: StateContext
76
+ # store explicit state separately for priority control
72
77
  object.__setattr__(
73
78
  self,
74
- "_state_context",
75
- StateContext.updated(state),
79
+ "_state",
80
+ state,
81
+ )
82
+ # capture current contextual state (without new additions)
83
+ object.__setattr__(
84
+ self,
85
+ "_captured_state",
86
+ StateContext.current_state(),
87
+ )
88
+ # placeholder for temporary, resolved state context
89
+ object.__setattr__(
90
+ self,
91
+ "_resolved_state_context",
92
+ None,
76
93
  )
77
- self._disposables: Disposables | None
78
94
  object.__setattr__(
79
95
  self,
80
96
  "_disposables",
81
97
  disposables,
82
98
  )
83
- self._observability_context: ObservabilityContext
99
+ object.__setattr__(
100
+ self,
101
+ "_presets",
102
+ ContextPresetsRegistryContext.select(name),
103
+ )
104
+ object.__setattr__(
105
+ self,
106
+ "_presets_disposables",
107
+ None,
108
+ )
84
109
  object.__setattr__(
85
110
  self,
86
111
  "_observability_context",
@@ -90,7 +115,6 @@ class ScopeContext:
90
115
  observability=observability,
91
116
  ),
92
117
  )
93
- self._task_group_context: TaskGroupContext | None
94
118
  object.__setattr__(
95
119
  self,
96
120
  "_task_group_context",
@@ -99,35 +123,27 @@ class ScopeContext:
99
123
  else None,
100
124
  )
101
125
 
102
- def __setattr__(
103
- self,
104
- name: str,
105
- value: Any,
106
- ) -> Any:
107
- raise AttributeError(
108
- f"Can't modify immutable {self.__class__.__qualname__},"
109
- f" attribute - '{name}' cannot be modified"
110
- )
111
-
112
- def __delattr__(
113
- self,
114
- name: str,
115
- ) -> None:
116
- raise AttributeError(
117
- f"Can't modify immutable {self.__class__.__qualname__},"
118
- f" attribute - '{name}' cannot be deleted"
119
- )
120
-
121
126
  def __enter__(self) -> str:
122
127
  assert ( # nosec: B101
123
128
  self._task_group_context is None or self._identifier.is_root
124
129
  ), "Can't enter synchronous context with task group"
125
130
  assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
131
+ assert self._resolved_state_context is None # nosec: B101
132
+ assert self._presets is None, "Can't enter synchronous context with presets" # nosec: B101
126
133
  self._identifier.__enter__()
127
134
  self._observability_context.__enter__()
128
- self._state_context.__enter__()
135
+ # For sync context, only use explicit state (no presets or disposables allowed)
136
+ resolved_state_context: StateContext = StateContext(
137
+ _state=ScopeState((*self._captured_state, *self._state))
138
+ )
139
+ object.__setattr__(
140
+ self,
141
+ "_resolved_state_context",
142
+ resolved_state_context,
143
+ )
144
+ resolved_state_context.__enter__()
129
145
 
130
- return self._observability_context.observability.trace_identifying(self._identifier).hex
146
+ return str(self._observability_context.observability.trace_identifying(self._identifier))
131
147
 
132
148
  def __exit__(
133
149
  self,
@@ -135,11 +151,17 @@ class ScopeContext:
135
151
  exc_val: BaseException | None,
136
152
  exc_tb: TracebackType | None,
137
153
  ) -> None:
138
- self._state_context.__exit__(
154
+ assert self._resolved_state_context is not None # nosec: B101
155
+ self._resolved_state_context.__exit__(
139
156
  exc_type=exc_type,
140
157
  exc_val=exc_val,
141
158
  exc_tb=exc_tb,
142
159
  )
160
+ object.__setattr__(
161
+ self,
162
+ "_resolved_state_context",
163
+ None,
164
+ )
143
165
  self._observability_context.__exit__(
144
166
  exc_type=exc_type,
145
167
  exc_val=exc_val,
@@ -152,31 +174,49 @@ class ScopeContext:
152
174
  )
153
175
 
154
176
  async def __aenter__(self) -> str:
177
+ assert self._presets_disposables is None # nosec: B101
178
+ assert self._resolved_state_context is None # nosec: B101
155
179
  self._identifier.__enter__()
156
180
  self._observability_context.__enter__()
157
181
 
158
182
  if task_group := self._task_group_context:
159
183
  await task_group.__aenter__()
160
184
 
161
- # lazily initialize state to include disposables results
162
- if disposables := self._disposables:
163
- assert self._state_context._token is None # nosec: B101
185
+ # Collect all state sources in priority order (lowest to highest priority)
186
+ collected_state: list[State] = []
187
+
188
+ # 1. Add contextual state first (lowest priority)
189
+ collected_state.extend(self._captured_state)
190
+
191
+ # 2. Add preset state (low priority, overrides contextual)
192
+ if self._presets is not None:
193
+ presets_disposables: Disposables = await self._presets.prepare()
164
194
  object.__setattr__(
165
195
  self,
166
- "_state_context",
167
- StateContext(
168
- state=ScopeState(
169
- (
170
- *self._state_context._state._state.values(),
171
- *await disposables.prepare(),
172
- )
173
- ),
174
- ),
196
+ "_presets_disposables",
197
+ presets_disposables,
175
198
  )
199
+ collected_state.extend(await presets_disposables.prepare())
200
+
201
+ # 3. Add explicit disposables state (medium priority)
202
+ if self._disposables is not None:
203
+ collected_state.extend(await self._disposables.prepare())
204
+
205
+ # 4. Add explicit state last (highest priority)
206
+ collected_state.extend(self._state)
207
+ # Create resolved state context with all collected state
208
+ resolved_state_context: StateContext = StateContext(
209
+ _state=ScopeState(tuple(collected_state))
210
+ )
176
211
 
177
- self._state_context.__enter__()
212
+ resolved_state_context.__enter__()
213
+ object.__setattr__(
214
+ self,
215
+ "_resolved_state_context",
216
+ resolved_state_context,
217
+ )
178
218
 
179
- return self._observability_context.observability.trace_identifying(self._identifier).hex
219
+ return str(self._observability_context.observability.trace_identifying(self._identifier))
180
220
 
181
221
  async def __aexit__(
182
222
  self,
@@ -184,12 +224,25 @@ class ScopeContext:
184
224
  exc_val: BaseException | None,
185
225
  exc_tb: TracebackType | None,
186
226
  ) -> None:
187
- if disposables := self._disposables:
188
- await disposables.dispose(
227
+ assert self._resolved_state_context is not None # nosec: B101
228
+ if self._disposables is not None:
229
+ await self._disposables.dispose(
230
+ exc_type=exc_type,
231
+ exc_val=exc_val,
232
+ exc_tb=exc_tb,
233
+ )
234
+
235
+ if self._presets_disposables is not None:
236
+ await self._presets_disposables.dispose(
189
237
  exc_type=exc_type,
190
238
  exc_val=exc_val,
191
239
  exc_tb=exc_tb,
192
240
  )
241
+ object.__setattr__(
242
+ self,
243
+ "_presets_disposables",
244
+ None,
245
+ )
193
246
 
194
247
  if task_group := self._task_group_context:
195
248
  await task_group.__aexit__(
@@ -198,11 +251,16 @@ class ScopeContext:
198
251
  exc_tb=exc_tb,
199
252
  )
200
253
 
201
- self._state_context.__exit__(
254
+ self._resolved_state_context.__exit__(
202
255
  exc_type=exc_type,
203
256
  exc_val=exc_val,
204
257
  exc_tb=exc_tb,
205
258
  )
259
+ object.__setattr__(
260
+ self,
261
+ "_resolved_state_context",
262
+ None,
263
+ )
206
264
 
207
265
  self._observability_context.__exit__(
208
266
  exc_type=exc_type,
@@ -258,9 +316,90 @@ class ctx:
258
316
  """
259
317
  return ObservabilityContext.trace_id(scope_identifier)
260
318
 
319
+ @staticmethod
320
+ def presets(
321
+ *presets: ContextPresets,
322
+ ) -> ContextPresetsRegistryContext:
323
+ """
324
+ Create a context manager for a preset registry.
325
+
326
+ This method creates a registry of context presets that can be used within
327
+ nested scopes. Presets allow you to define reusable combinations of state
328
+ and disposables that can be referenced by name when creating scopes.
329
+
330
+ When entering this context manager, the provided presets become available
331
+ for use with ctx.scope(). The presets are looked up by their name when
332
+ creating scopes.
333
+
334
+ Parameters
335
+ ----------
336
+ *presets: ContextPresets
337
+ Variable number of preset configurations to register. Each preset
338
+ must have a unique name within the registry.
339
+
340
+ Returns
341
+ -------
342
+ ContextPresetsRegistryContext
343
+ A context manager that makes the presets available in nested scopes
344
+
345
+ Examples
346
+ --------
347
+ Basic preset usage:
348
+
349
+ >>> from haiway import ctx, State
350
+ >>> from haiway.context import ContextPresets
351
+ >>>
352
+ >>> class ApiConfig(State):
353
+ ... base_url: str
354
+ ... timeout: int = 30
355
+ >>>
356
+ >>> # Define presets
357
+ >>> dev_preset = ContextPresets(
358
+ ... name="development",
359
+ ... _state=[ApiConfig(base_url="https://dev-api.example.com")]
360
+ ... )
361
+ >>>
362
+ >>> prod_preset = ContextPresets(
363
+ ... name="production",
364
+ ... _state=[ApiConfig(base_url="https://api.example.com", timeout=60)]
365
+ ... )
366
+ >>>
367
+ >>> # Use presets
368
+ >>> with ctx.presets(dev_preset, prod_preset):
369
+ ... async with ctx.scope("development"):
370
+ ... config = ctx.state(ApiConfig)
371
+ ... assert config.base_url == "https://dev-api.example.com"
372
+
373
+ Nested preset registries:
374
+
375
+ >>> base_presets = [dev_preset, prod_preset]
376
+ >>> override_preset = ContextPresets(
377
+ ... name="development",
378
+ ... _state=[ApiConfig(base_url="https://staging.example.com")]
379
+ ... )
380
+ >>>
381
+ >>> with ctx.presets(*base_presets):
382
+ ... # Outer registry has dev and prod presets
383
+ ... with ctx.presets(override_preset):
384
+ ... # Inner registry overrides dev preset
385
+ ... async with ctx.scope("development"):
386
+ ... config = ctx.state(ApiConfig)
387
+ ... assert config.base_url == "https://staging.example.com"
388
+
389
+ See Also
390
+ --------
391
+ ContextPresets : For creating individual preset configurations
392
+ ctx.scope : For creating scopes that can use presets
393
+ """
394
+ return ContextPresetsRegistryContext(
395
+ registry=ContextPresetsRegistry(
396
+ presets=presets,
397
+ ),
398
+ )
399
+
261
400
  @staticmethod
262
401
  def scope(
263
- label: str,
402
+ name: str,
264
403
  /,
265
404
  *state: State | None,
266
405
  disposables: Disposables | Iterable[Disposable] | None = None,
@@ -271,10 +410,22 @@ class ctx:
271
410
  Prepare scope context with given parameters. When called within an existing context\
272
411
  it becomes nested with current context as its parent.
273
412
 
413
+ State Priority System
414
+ ---------------------
415
+ State resolution follows a 4-layer priority system (highest to lowest):
416
+
417
+ 1. **Explicit state** (passed to ctx.scope()) - HIGHEST priority
418
+ 2. **Explicit disposables** (passed to ctx.scope()) - medium priority
419
+ 3. **Preset state** (from presets) - low priority
420
+ 4. **Contextual state** (from parent contexts) - LOWEST priority
421
+
422
+ When state types conflict, higher priority sources override lower priority ones.
423
+ State objects are resolved by type, with the highest priority instance winning.
424
+
274
425
  Parameters
275
426
  ----------
276
- label: str
277
- name of the scope context
427
+ name: str
428
+ name of the scope context, can be associated with state presets
278
429
 
279
430
  *state: State | None
280
431
  state propagated within the scope context, will be merged with current state by\
@@ -313,7 +464,7 @@ class ctx:
313
464
  resolved_disposables = Disposables(*iterable)
314
465
 
315
466
  return ScopeContext(
316
- label=label,
467
+ name=name,
317
468
  task_group=task_group,
318
469
  state=tuple(element for element in state if element is not None),
319
470
  disposables=resolved_disposables,