haiway 0.22.0__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.
Files changed (114) hide show
  1. {haiway-0.22.0 → haiway-0.22.1}/PKG-INFO +1 -1
  2. {haiway-0.22.0 → haiway-0.22.1}/junit/test-results.xml +1 -1
  3. {haiway-0.22.0 → haiway-0.22.1}/pyproject.toml +1 -1
  4. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/access.py +9 -1
  5. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/disposables.py +32 -5
  6. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/state.py +17 -2
  7. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/__init__.py +1 -1
  8. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/structure.py +16 -6
  9. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/__init__.py +0 -2
  10. {haiway-0.22.0 → haiway-0.22.1}/tests/test_disposables.py +2 -2
  11. {haiway-0.22.0 → haiway-0.22.1}/tests/test_state.py +178 -1
  12. {haiway-0.22.0 → haiway-0.22.1}/uv.lock +1 -1
  13. haiway-0.22.0/examples/fastAPI/.dockerignore +0 -5
  14. haiway-0.22.0/examples/fastAPI/Dockerfile +0 -63
  15. haiway-0.22.0/examples/fastAPI/Makefile +0 -90
  16. haiway-0.22.0/examples/fastAPI/README.md +0 -3
  17. haiway-0.22.0/examples/fastAPI/config/.env.example +0 -11
  18. haiway-0.22.0/examples/fastAPI/config/unit.json +0 -50
  19. haiway-0.22.0/examples/fastAPI/docker-compose.yml +0 -68
  20. haiway-0.22.0/examples/fastAPI/pyproject.toml +0 -62
  21. haiway-0.22.0/examples/fastAPI/src/features/__int__.py +0 -0
  22. haiway-0.22.0/examples/fastAPI/src/features/todos/__init__.py +0 -5
  23. haiway-0.22.0/examples/fastAPI/src/features/todos/config.py +0 -1
  24. haiway-0.22.0/examples/fastAPI/src/features/todos/state.py +0 -23
  25. haiway-0.22.0/examples/fastAPI/src/features/todos/types.py +0 -15
  26. haiway-0.22.0/examples/fastAPI/src/features/todos/user_tasks.py +0 -19
  27. haiway-0.22.0/examples/fastAPI/src/integrations/postgres/__init__.py +0 -12
  28. haiway-0.22.0/examples/fastAPI/src/integrations/postgres/client.py +0 -171
  29. haiway-0.22.0/examples/fastAPI/src/integrations/postgres/config.py +0 -19
  30. haiway-0.22.0/examples/fastAPI/src/integrations/postgres/state.py +0 -77
  31. haiway-0.22.0/examples/fastAPI/src/integrations/postgres/types.py +0 -69
  32. haiway-0.22.0/examples/fastAPI/src/migrations/__init__.py +0 -3
  33. haiway-0.22.0/examples/fastAPI/src/migrations/__main__.py +0 -26
  34. haiway-0.22.0/examples/fastAPI/src/migrations/postgres/__init__.py +0 -5
  35. haiway-0.22.0/examples/fastAPI/src/migrations/postgres/execution.py +0 -80
  36. haiway-0.22.0/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -17
  37. haiway-0.22.0/examples/fastAPI/src/migrations/postgres/types.py +0 -9
  38. haiway-0.22.0/examples/fastAPI/src/server/__init__.py +0 -10
  39. haiway-0.22.0/examples/fastAPI/src/server/__main__.py +0 -10
  40. haiway-0.22.0/examples/fastAPI/src/server/application.py +0 -54
  41. haiway-0.22.0/examples/fastAPI/src/server/config.py +0 -11
  42. haiway-0.22.0/examples/fastAPI/src/server/middlewares/__init__.py +0 -5
  43. haiway-0.22.0/examples/fastAPI/src/server/middlewares/context.py +0 -133
  44. haiway-0.22.0/examples/fastAPI/src/server/routes/__init__.py +0 -7
  45. haiway-0.22.0/examples/fastAPI/src/server/routes/technical.py +0 -21
  46. haiway-0.22.0/examples/fastAPI/src/server/routes/todos.py +0 -32
  47. haiway-0.22.0/examples/fastAPI/src/solutions/__init__.py +0 -0
  48. haiway-0.22.0/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -7
  49. haiway-0.22.0/examples/fastAPI/src/solutions/user_tasks/config.py +0 -1
  50. haiway-0.22.0/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -218
  51. haiway-0.22.0/examples/fastAPI/src/solutions/user_tasks/state.py +0 -77
  52. haiway-0.22.0/examples/fastAPI/src/solutions/user_tasks/types.py +0 -71
  53. haiway-0.22.0/examples/fastAPI/uv.lock +0 -448
  54. haiway-0.22.0/src/haiway/utils/freezing.py +0 -46
  55. haiway-0.22.0/tests/__init__.py +0 -0
  56. {haiway-0.22.0 → haiway-0.22.1}/.github/workflows/ci.yml +0 -0
  57. {haiway-0.22.0 → haiway-0.22.1}/.github/workflows/publish.yml +0 -0
  58. {haiway-0.22.0 → haiway-0.22.1}/.gitignore +0 -0
  59. {haiway-0.22.0 → haiway-0.22.1}/CLAUDE.md +0 -0
  60. {haiway-0.22.0 → haiway-0.22.1}/LICENSE +0 -0
  61. {haiway-0.22.0 → haiway-0.22.1}/Makefile +0 -0
  62. {haiway-0.22.0 → haiway-0.22.1}/README.md +0 -0
  63. {haiway-0.22.0 → haiway-0.22.1}/config/pre-push +0 -0
  64. {haiway-0.22.0 → haiway-0.22.1}/guidelines/functionalities.md +0 -0
  65. {haiway-0.22.0 → haiway-0.22.1}/guidelines/llms.txt +0 -0
  66. {haiway-0.22.0 → haiway-0.22.1}/guidelines/packages.md +0 -0
  67. {haiway-0.22.0 → haiway-0.22.1}/guidelines/state.md +0 -0
  68. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/__init__.py +0 -0
  69. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/__init__.py +0 -0
  70. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/identifier.py +0 -0
  71. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/observability.py +0 -0
  72. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/tasks.py +0 -0
  73. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/context/types.py +0 -0
  74. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/asynchrony.py +0 -0
  75. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/caching.py +0 -0
  76. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/concurrent.py +0 -0
  77. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/files.py +0 -0
  78. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/observability.py +0 -0
  79. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/retries.py +0 -0
  80. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/throttling.py +0 -0
  81. /haiway-0.22.0/src/haiway/helpers/timeouted.py → /haiway-0.22.1/src/haiway/helpers/timeouting.py +0 -0
  82. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/helpers/tracing.py +0 -0
  83. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/opentelemetry/__init__.py +0 -0
  84. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/opentelemetry/observability.py +0 -0
  85. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/py.typed +0 -0
  86. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/__init__.py +0 -0
  87. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/attributes.py +0 -0
  88. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/path.py +0 -0
  89. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/requirement.py +0 -0
  90. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/state/validation.py +0 -0
  91. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/types/__init__.py +0 -0
  92. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/types/default.py +0 -0
  93. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/types/missing.py +0 -0
  94. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/always.py +0 -0
  95. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/collections.py +0 -0
  96. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/env.py +0 -0
  97. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/formatting.py +0 -0
  98. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/logs.py +0 -0
  99. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/mimic.py +0 -0
  100. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/noop.py +0 -0
  101. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/queue.py +0 -0
  102. {haiway-0.22.0 → haiway-0.22.1}/src/haiway/utils/stream.py +0 -0
  103. {haiway-0.22.0/examples/fastAPI/src/integrations → haiway-0.22.1/tests}/__init__.py +0 -0
  104. {haiway-0.22.0 → haiway-0.22.1}/tests/test_async_queue.py +0 -0
  105. {haiway-0.22.0 → haiway-0.22.1}/tests/test_async_stream.py +0 -0
  106. {haiway-0.22.0 → haiway-0.22.1}/tests/test_attribute_path.py +0 -0
  107. {haiway-0.22.0 → haiway-0.22.1}/tests/test_attribute_requirement.py +0 -0
  108. {haiway-0.22.0 → haiway-0.22.1}/tests/test_auto_retry.py +0 -0
  109. {haiway-0.22.0 → haiway-0.22.1}/tests/test_cache.py +0 -0
  110. {haiway-0.22.0 → haiway-0.22.1}/tests/test_context.py +0 -0
  111. {haiway-0.22.0 → haiway-0.22.1}/tests/test_process_concurrently.py +0 -0
  112. {haiway-0.22.0 → haiway-0.22.1}/tests/test_state_validation.py +0 -0
  113. {haiway-0.22.0 → haiway-0.22.1}/tests/test_streaming.py +0 -0
  114. {haiway-0.22.0 → 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.22.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
@@ -1 +1 @@
1
- <?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="151" time="1.947" timestamp="2025-06-18T09:04:02.496497+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.002" /><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.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.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_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.000" /><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.001" /><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.000" /><testcase classname="tests.test_state_validation" name="test_validator_tuple_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_union_type" time="0.001" /><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.001" /><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.012" /><testcase classname="tests.test_timeout" name="test_raises_with_timeout" time="0.011" /></testsuite></testsuites>
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.22.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" },
@@ -539,6 +539,8 @@ class ctx:
539
539
  def check_state[StateType: State](
540
540
  state: type[StateType],
541
541
  /,
542
+ *,
543
+ instantiate_defaults: bool = False,
542
544
  ) -> bool:
543
545
  """
544
546
  Check if state object is available in the current context.
@@ -551,12 +553,18 @@ class ctx:
551
553
  state: type[StateType]
552
554
  The type of state to check
553
555
 
556
+ instantiate_defaults: bool = False
557
+ Control if default value should be instantiated during check.
558
+
554
559
  Returns
555
560
  -------
556
561
  bool
557
562
  True if state is available, otherwise False.
558
563
  """
559
- return StateContext.check_state(state)
564
+ return StateContext.check_state(
565
+ state,
566
+ instantiate_defaults=instantiate_defaults,
567
+ )
560
568
 
561
569
  @staticmethod
562
570
  def state[StateType: State](
@@ -12,7 +12,7 @@ from itertools import chain
12
12
  from types import TracebackType
13
13
  from typing import Any, final
14
14
 
15
- from haiway.context.state import ScopeState, StateContext
15
+ from haiway.context.state import StateContext
16
16
  from haiway.state import State
17
17
  from haiway.utils.mimic import mimic_function
18
18
 
@@ -195,19 +195,25 @@ class Disposables:
195
195
  return multiple
196
196
 
197
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
+ """
198
203
  assert self._loop is None # nosec: B101
199
204
  object.__setattr__(
200
205
  self,
201
206
  "_loop",
202
207
  get_running_loop(),
203
208
  )
204
- return [
205
- *chain.from_iterable(
209
+
210
+ return tuple(
211
+ chain.from_iterable(
206
212
  await gather(
207
213
  *[self._setup(disposable) for disposable in self._disposables],
208
214
  )
209
215
  )
210
- ]
216
+ )
211
217
 
212
218
  async def __aenter__(self) -> None:
213
219
  """
@@ -218,7 +224,7 @@ class Disposables:
218
224
  """
219
225
 
220
226
  assert self._state_context is None, "Context reentrance is not allowed" # nosec: B101
221
- state_context = StateContext(state=ScopeState(await self.prepare()))
227
+ state_context = StateContext.updated(await self.prepare())
222
228
  state_context.__enter__()
223
229
  object.__setattr__(
224
230
  self,
@@ -252,6 +258,26 @@ class Disposables:
252
258
  exc_val: BaseException | None = None,
253
259
  exc_tb: TracebackType | None = None,
254
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
+ """
255
281
  assert self._loop is not None # nosec: B101
256
282
  results: list[bool | BaseException | None]
257
283
 
@@ -306,6 +332,7 @@ class Disposables:
306
332
 
307
333
  Properly disposes of all resources by calling their __aexit__ methods in parallel.
308
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.
309
336
 
310
337
  Parameters
311
338
  ----------
@@ -67,6 +67,8 @@ class ScopeState:
67
67
  self,
68
68
  state: type[StateType],
69
69
  /,
70
+ *,
71
+ instantiate_defaults: bool = False,
70
72
  ) -> bool:
71
73
  """
72
74
  Check state object availability by its type.
@@ -79,6 +81,9 @@ class ScopeState:
79
81
  state: type[StateType]
80
82
  The type of state to check
81
83
 
84
+ instantiate_defaults: bool = False
85
+ Control if default value should be instantiated during check.
86
+
82
87
  Returns
83
88
  -------
84
89
  bool
@@ -87,7 +92,7 @@ class ScopeState:
87
92
  if state in self._state:
88
93
  return True
89
94
 
90
- else:
95
+ elif instantiate_defaults:
91
96
  with self._lock:
92
97
  if state in self._state:
93
98
  return True
@@ -100,6 +105,9 @@ class ScopeState:
100
105
  except BaseException:
101
106
  return False # unavailable, we don't care the exception
102
107
 
108
+ else:
109
+ return False
110
+
103
111
  def state[StateType: State](
104
112
  self,
105
113
  state: type[StateType],
@@ -201,6 +209,7 @@ class StateContext:
201
209
  cls,
202
210
  state: type[StateType],
203
211
  /,
212
+ instantiate_defaults: bool = False,
204
213
  ) -> bool:
205
214
  """
206
215
  Check if state object is available in the current context.
@@ -212,13 +221,19 @@ class StateContext:
212
221
  state: type[StateType]
213
222
  The type of state to check
214
223
 
224
+ instantiate_defaults: bool = False
225
+ Control if default value should be instantiated during check.
226
+
215
227
  Returns
216
228
  -------
217
229
  bool
218
230
  True if state is available, otherwise False.
219
231
  """
220
232
  try:
221
- return cls._context.get().check_state(state)
233
+ return cls._context.get().check_state(
234
+ state,
235
+ instantiate_defaults=instantiate_defaults,
236
+ )
222
237
 
223
238
  except LookupError:
224
239
  return False # no context no state
@@ -5,7 +5,7 @@ from haiway.helpers.files import File, FileAccess
5
5
  from haiway.helpers.observability import LoggerObservability
6
6
  from haiway.helpers.retries import retry
7
7
  from haiway.helpers.throttling import throttle
8
- from haiway.helpers.timeouted import timeout
8
+ from haiway.helpers.timeouting import timeout
9
9
  from haiway.helpers.tracing import traced
10
10
 
11
11
  __all__ = (
@@ -729,12 +729,22 @@ class State(metaclass=StateMeta):
729
729
  )
730
730
 
731
731
  def __hash__(self) -> int:
732
- return hash(
733
- (
734
- self.__class__,
735
- *tuple(getattr(self, key, MISSING) for key in sorted(self.__ATTRIBUTES__.keys())),
736
- )
737
- )
732
+ hash_values: list[int] = []
733
+ for key in self.__ATTRIBUTES__.keys():
734
+ value: Any = getattr(self, key, MISSING)
735
+
736
+ # Skip MISSING values to ensure consistent hashing
737
+ if value is MISSING:
738
+ continue
739
+
740
+ # Convert to hashable representation
741
+ try:
742
+ hash_values.append(hash(value))
743
+
744
+ except TypeError:
745
+ continue # skip unhashable
746
+
747
+ return hash((self.__class__, tuple(hash_values)))
738
748
 
739
749
  def __setattr__(
740
750
  self,
@@ -9,7 +9,6 @@ from haiway.utils.env import (
9
9
  load_env,
10
10
  )
11
11
  from haiway.utils.formatting import format_str
12
- from haiway.utils.freezing import freeze
13
12
  from haiway.utils.logs import setup_logging
14
13
  from haiway.utils.mimic import mimic_function
15
14
  from haiway.utils.noop import async_noop, noop
@@ -27,7 +26,6 @@ __all__ = (
27
26
  "async_always",
28
27
  "async_noop",
29
28
  "format_str",
30
- "freeze",
31
29
  "getenv_base64",
32
30
  "getenv_bool",
33
31
  "getenv_float",
@@ -128,7 +128,7 @@ def test_non_empty_disposables_is_truthy():
128
128
  async def test_setup_with_no_disposables():
129
129
  disposables = Disposables()
130
130
  result = await disposables.prepare()
131
- assert result == []
131
+ assert result == ()
132
132
  assert disposables._loop is not None
133
133
 
134
134
 
@@ -137,7 +137,7 @@ async def test_setup_with_disposable_returning_none():
137
137
  mock = MockDisposable(enter_return=None)
138
138
  disposables = Disposables(mock)
139
139
  result = await disposables.prepare()
140
- assert result == []
140
+ assert result == ()
141
141
  assert mock.enter_called
142
142
  assert disposables._loop is not None
143
143
 
@@ -1,4 +1,4 @@
1
- from collections.abc import Callable, Sequence, Set
1
+ from collections.abc import Callable, Mapping, Sequence, Set
2
2
  from copy import copy, deepcopy
3
3
  from datetime import date, datetime
4
4
  from enum import StrEnum
@@ -240,3 +240,180 @@ def test_copying_leaves_same_object() -> None:
240
240
  origin = Copied(string="42", nested=Nested(string="answer"))
241
241
  assert copy(origin) is origin
242
242
  assert deepcopy(origin) is origin
243
+
244
+
245
+ def test_hash_consistency_with_missing_values() -> None:
246
+ class HashTest(State):
247
+ required: str
248
+ optional: int | Missing
249
+ nullable: str | None
250
+
251
+ # Test that hash is consistent regardless of how MISSING values are specified
252
+ obj1 = HashTest(required="test", optional=MISSING, nullable=None)
253
+ obj2 = HashTest(required="test", nullable=None) # optional omitted
254
+ obj3 = HashTest(**{"required": "test", "nullable": None}) # optional missing from dict
255
+
256
+ assert hash(obj1) == hash(obj2) == hash(obj3)
257
+ assert obj1 == obj2 == obj3
258
+
259
+
260
+ def test_hash_with_unhashable_attributes() -> None:
261
+ class UnhashableTest(State):
262
+ name: str
263
+ data_list: Sequence[int]
264
+ data_dict: Mapping[str, int]
265
+ data_set: Set[str]
266
+ function: Callable[..., Any] | None
267
+
268
+ obj1 = UnhashableTest(
269
+ name="test",
270
+ data_list=[1, 2, 3],
271
+ data_dict={"a": 1, "b": 2},
272
+ data_set={"x", "y"},
273
+ function=None,
274
+ )
275
+
276
+ # Should not raise TypeError
277
+ hash_value = hash(obj1)
278
+ assert isinstance(hash_value, int)
279
+
280
+ # Objects with same data should have same hash
281
+ obj2 = UnhashableTest(
282
+ name="test",
283
+ data_list=[1, 2, 3],
284
+ data_dict={"a": 1, "b": 2},
285
+ data_set={"x", "y"},
286
+ function=None,
287
+ )
288
+ assert hash(obj1) == hash(obj2)
289
+
290
+ # Objects with different data should have different hashes (usually)
291
+ obj3 = UnhashableTest(
292
+ name="test",
293
+ data_list=[1, 2, 3],
294
+ data_dict={"a": 1, "b": 2},
295
+ data_set={"x", "y"},
296
+ function=lambda: ..., # Different function
297
+ )
298
+ obj4 = UnhashableTest(
299
+ name="test",
300
+ data_list=[1, 2, 3],
301
+ data_dict={"a": 1, "b": 2},
302
+ data_set={"x", "y"},
303
+ function=lambda: ..., # Different function
304
+ )
305
+ # Note: Hash collisions are possible but unlikely
306
+ assert hash(obj3) != hash(obj4)
307
+
308
+
309
+ def test_hash_with_dict_key_order_independence() -> None:
310
+ class DictTest(State):
311
+ data: Mapping[str, int]
312
+
313
+ # Dictionaries with same content but different creation order should hash the same
314
+ obj1 = DictTest(data={"a": 1, "b": 2, "c": 3})
315
+ obj2 = DictTest(data={"c": 3, "a": 1, "b": 2})
316
+
317
+ assert hash(obj1) == hash(obj2)
318
+ assert obj1 == obj2
319
+
320
+
321
+ def test_hash_with_custom_objects() -> None:
322
+ # Test with a simple custom object that can go through Any validation
323
+ class CustomTest(State):
324
+ name: str
325
+ custom: Any # Use Any to allow custom objects
326
+
327
+ custom_obj1 = {"value": "test"} # Use dict as custom object
328
+ custom_obj2 = {"value": "test"} # Same content
329
+
330
+ obj1 = CustomTest(name="test", custom=custom_obj1)
331
+ obj2 = CustomTest(name="test", custom=custom_obj2)
332
+
333
+ # Should not raise TypeError
334
+ hash_value1 = hash(obj1)
335
+ hash_value2 = hash(obj2)
336
+
337
+ assert isinstance(hash_value1, int)
338
+ assert isinstance(hash_value2, int)
339
+ # Objects with same content should have same hash
340
+ assert hash_value1 == hash_value2
341
+
342
+
343
+ def test_hash_performance_with_many_attributes() -> None:
344
+ # Test that the optimized hash function performs well with many attributes
345
+ class ManyAttrs(State):
346
+ attr_0: int = 0
347
+ attr_1: int = 1
348
+ attr_2: int = 2
349
+ attr_3: int = 3
350
+ attr_4: int = 4
351
+ attr_5: int = 5
352
+ attr_6: int = 6
353
+ attr_7: int = 7
354
+ attr_8: int = 8
355
+ attr_9: int = 9
356
+
357
+ obj = ManyAttrs()
358
+
359
+ # Should compute hash without issues
360
+ hash_value = hash(obj)
361
+ assert isinstance(hash_value, int)
362
+
363
+
364
+ def test_hash_with_nested_unhashable_collections() -> None:
365
+ class NestedTest(State):
366
+ nested_list: Sequence[Sequence[int]]
367
+ nested_dict: Mapping[str, Mapping[str, int]]
368
+
369
+ obj = NestedTest(nested_list=[[1, 2], [3, 4]], nested_dict={"outer": {"inner": 42}})
370
+
371
+ # Should handle nested unhashable collections
372
+ hash_value = hash(obj)
373
+ assert isinstance(hash_value, int)
374
+
375
+
376
+ def test_hash_stability_across_instances() -> None:
377
+ class StabilityTest(State):
378
+ value: str
379
+ number: int
380
+
381
+ # Same data should always produce same hash
382
+ obj1 = StabilityTest(value="test", number=42)
383
+ obj2 = StabilityTest(value="test", number=42)
384
+
385
+ assert hash(obj1) == hash(obj2)
386
+
387
+ # Hash should be stable across multiple calls
388
+ hash1 = hash(obj1)
389
+ hash2 = hash(obj1)
390
+ hash3 = hash(obj1)
391
+
392
+ assert hash1 == hash2 == hash3
393
+
394
+
395
+ def test_hash_excludes_missing_values() -> None:
396
+ class MissingTest(State):
397
+ always_present: str
398
+ sometimes_missing: int | Missing
399
+ sometimes_none: str | None
400
+
401
+ # Test various combinations of missing values
402
+ obj1 = MissingTest(always_present="test", sometimes_missing=42, sometimes_none="value")
403
+ obj2 = MissingTest(always_present="test", sometimes_missing=MISSING, sometimes_none="value")
404
+ obj3 = MissingTest(always_present="test", sometimes_missing=MISSING, sometimes_none=None)
405
+
406
+ # All should be hashable
407
+ hash1 = hash(obj1)
408
+ hash2 = hash(obj2)
409
+ hash3 = hash(obj3)
410
+
411
+ assert isinstance(hash1, int)
412
+ assert isinstance(hash2, int)
413
+ assert isinstance(hash3, int)
414
+
415
+ # obj2 should have different hash from obj1 since it's missing a value
416
+ assert hash1 != hash2
417
+
418
+ # obj3 should have different hash from obj2 since None != MISSING
419
+ assert hash2 != hash3
@@ -114,7 +114,7 @@ wheels = [
114
114
 
115
115
  [[package]]
116
116
  name = "haiway"
117
- version = "0.22.0"
117
+ version = "0.22.1"
118
118
  source = { editable = "." }
119
119
 
120
120
  [package.optional-dependencies]
@@ -1,5 +0,0 @@
1
- *
2
- !./pyproject.toml
3
- !./config/unit.json
4
- !./src
5
- !./uv.lock
@@ -1,63 +0,0 @@
1
- ARG PYTHON_TAG=3.12
2
- ARG UNIT_TAG=1.32.1-python${PYTHON_TAG}
3
-
4
- # SERVER #
5
-
6
- FROM unit:${UNIT_TAG} AS server_builder
7
-
8
- # copy only the parts needed for production
9
- COPY ./src/integrations ./src/integrations
10
- COPY ./src/solutions ./src/solutions
11
- COPY ./src/features ./src/features
12
- COPY ./src/server ./src/server
13
-
14
- # install dependencies and packages
15
- COPY --from=ghcr.io/astral-sh/uv:0.6.8 /uv /uvx /bin/
16
-
17
- ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
18
-
19
- RUN --mount=type=bind,source=./uv.lock,target=./uv.lock --mount=type=bind,source=./pyproject.toml,target=./pyproject.toml uv sync --python python${PYTHON_TAG} --locked --no-editable --no-python-downloads --link-mode copy --compile-bytecode --only-group server
20
-
21
- FROM server_builder AS server
22
-
23
- # allow access to home directory for asyncpg library
24
- RUN chgrp -R unit ${HOME} && chmod -R 050 ${HOME}
25
-
26
- RUN apt-get update \
27
- && apt-get upgrade -y \
28
- && apt-get -y autoremove \
29
- && apt-get clean \
30
- && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
31
-
32
- # copy configuration
33
- COPY ./config/unit.json /docker-entrypoint.d/config.json
34
-
35
- CMD ["unitd", "--no-daemon", "--log", "/dev/stdout"]
36
-
37
- # port 80 is already exposed by nginx unit image, can't change it...
38
-
39
- # MIGRATIONS #
40
-
41
- FROM python:${PYTHON_TAG} AS migrations_builder
42
-
43
- # copy only the parts needed for production
44
- COPY ./src/integrations ./src/integrations
45
- COPY ./src/solutions ./src/solutions
46
- COPY ./src/features ./src/features
47
- COPY ./src/migrations ./src/migrations
48
- # install dependencies and packages
49
- COPY --from=ghcr.io/astral-sh/uv:0.6.8 /uv /uvx /bin/
50
-
51
- ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
52
-
53
- RUN --mount=type=bind,source=./uv.lock,target=./uv.lock --mount=type=bind,source=./pyproject.toml,target=./pyproject.toml uv sync --python python${PYTHON_TAG} --locked --no-editable --no-python-downloads --link-mode copy --compile-bytecode --only-group server
54
-
55
- FROM migrations_builder AS migrations
56
-
57
- RUN apt-get update \
58
- && apt-get upgrade -y \
59
- && apt-get -y autoremove \
60
- && apt-get clean \
61
- && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
62
-
63
- CMD ["python", "-m", "migrations"]