haiway 0.13.1__tar.gz → 0.15.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.
- {haiway-0.13.1 → haiway-0.15.0}/PKG-INFO +1 -1
- haiway-0.15.0/junit/test-results.xml +1 -0
- {haiway-0.13.1 → haiway-0.15.0}/pyproject.toml +4 -1
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/__init__.py +2 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/access.py +59 -61
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/state.py +43 -3
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/tasks.py +2 -1
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/asynchrony.py +4 -6
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/caching.py +1 -1
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/metrics.py +46 -24
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/__init__.py +2 -0
- haiway-0.15.0/src/haiway/utils/stream.py +97 -0
- haiway-0.15.0/tests/test_async_stream.py +138 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_streaming.py +5 -11
- {haiway-0.13.1 → haiway-0.15.0}/uv.lock +46 -46
- haiway-0.13.1/junit/test-results.xml +0 -1
- {haiway-0.13.1 → haiway-0.15.0}/.github/workflows/ci.yml +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/.github/workflows/publish.yml +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/.gitignore +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/LICENSE +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/Makefile +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/README.md +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/config/pre-push +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/.dockerignore +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/Dockerfile +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/Makefile +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/README.md +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/config/.env.example +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/config/unit.json +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/docker-compose.yml +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/pyproject.toml +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/__int__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/todos/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/todos/config.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/todos/state.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/todos/types.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/__main__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/__main__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/application.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/config.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/middlewares/context.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/routes/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/routes/technical.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/server/routes/todos.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/examples/fastAPI/uv.lock +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/guidelines/functionalities.md +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/guidelines/packages.md +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/disposables.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/identifier.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/logging.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/metrics.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/context/types.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/py.typed +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/attributes.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/path.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/requirement.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/structure.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/state/validation.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/types/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/types/default.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/types/frozen.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/types/missing.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/always.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/collections.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/env.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/freezing.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/logs.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/noop.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/src/haiway/utils/queue.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_async_queue.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_attribute_path.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_auto_retry.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_cache.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_context.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_state.py +0 -0
- {haiway-0.13.1 → haiway-0.15.0}/tests/test_timeout.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.15.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><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="88" time="1.118" timestamp="2025-04-02T07:36:48.794161" hostname="fv-az1784-745"><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_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.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.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_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.001" /><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.002" /><testcase classname="tests.test_state" name="test_copying_leaves_same_object" time="0.001" /><testcase classname="tests.test_streaming" name="test_fails_when_generator_fails" time="0.001" /><testcase classname="tests.test_streaming" name="test_cancels_when_iteration_cancels" time="0.001" /><testcase classname="tests.test_streaming" name="test_ends_when_generator_ends" time="0.001" /><testcase classname="tests.test_streaming" name="test_delivers_updates_when_generating" time="0.001" /><testcase classname="tests.test_streaming" name="test_streaming_context_variables_access_is_preserved" time="0.001" /><testcase classname="tests.test_streaming" name="test_nested_streaming_streams_correctly" time="0.001" /><testcase classname="tests.test_timeout" name="test_returns_result_when_returning_value" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_error" time="0.001" /><testcase classname="tests.test_timeout" name="test_raises_with_cancel" time="0.011" /><testcase classname="tests.test_timeout" name="test_raises_with_timeout" time="0.012" /></testsuite></testsuites>
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
5
5
|
[project]
|
6
6
|
name = "haiway"
|
7
7
|
description = "Framework for dependency injection and state management within structured concurrency model."
|
8
|
-
version = "0.
|
8
|
+
version = "0.15.0"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -44,6 +44,9 @@ extend-exclude = [".venv", ".git", ".cache"]
|
|
44
44
|
lint.select = ["E", "F", "A", "I", "B", "PL", "W", "C", "RUF", "UP"]
|
45
45
|
lint.ignore = ["A005"]
|
46
46
|
|
47
|
+
[tool.ruff.lint.pylint]
|
48
|
+
max-args = 12
|
49
|
+
|
47
50
|
[tool.ruff.lint.per-file-ignores]
|
48
51
|
"__init__.py" = ["F401", "E402"]
|
49
52
|
"./tests/*.py" = ["PLR2004"]
|
@@ -40,6 +40,7 @@ from haiway.types import (
|
|
40
40
|
)
|
41
41
|
from haiway.utils import (
|
42
42
|
AsyncQueue,
|
43
|
+
AsyncStream,
|
43
44
|
always,
|
44
45
|
as_dict,
|
45
46
|
as_list,
|
@@ -63,6 +64,7 @@ __all__ = [
|
|
63
64
|
"MISSING",
|
64
65
|
"ArgumentsTrace",
|
65
66
|
"AsyncQueue",
|
67
|
+
"AsyncStream",
|
66
68
|
"AttributePath",
|
67
69
|
"AttributeRequirement",
|
68
70
|
"Default",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from asyncio import (
|
2
2
|
CancelledError,
|
3
3
|
Task,
|
4
|
+
TaskGroup,
|
4
5
|
current_task,
|
5
6
|
iscoroutinefunction,
|
6
7
|
)
|
@@ -11,7 +12,6 @@ from collections.abc import (
|
|
11
12
|
Coroutine,
|
12
13
|
Iterable,
|
13
14
|
)
|
14
|
-
from contextvars import Context, copy_context
|
15
15
|
from logging import Logger
|
16
16
|
from types import TracebackType
|
17
17
|
from typing import Any, final, overload
|
@@ -24,6 +24,7 @@ from haiway.context.state import StateContext
|
|
24
24
|
from haiway.context.tasks import TaskGroupContext
|
25
25
|
from haiway.state import State
|
26
26
|
from haiway.utils import mimic_function
|
27
|
+
from haiway.utils.stream import AsyncStream
|
27
28
|
|
28
29
|
__all__ = [
|
29
30
|
"ctx",
|
@@ -37,7 +38,6 @@ class ScopeContext:
|
|
37
38
|
"_identifier",
|
38
39
|
"_logger_context",
|
39
40
|
"_metrics_context",
|
40
|
-
"_state",
|
41
41
|
"_state_context",
|
42
42
|
"_task_group_context",
|
43
43
|
)
|
@@ -46,6 +46,7 @@ class ScopeContext:
|
|
46
46
|
self,
|
47
47
|
label: str,
|
48
48
|
logger: Logger | None,
|
49
|
+
task_group: TaskGroup | None,
|
49
50
|
state: tuple[State, ...],
|
50
51
|
disposables: Disposables | None,
|
51
52
|
metrics: MetricsHandler | None,
|
@@ -65,15 +66,22 @@ class ScopeContext:
|
|
65
66
|
logger=logger,
|
66
67
|
),
|
67
68
|
)
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
self._task_group_context: TaskGroupContext | None
|
70
|
+
object.__setattr__(
|
71
|
+
self,
|
72
|
+
"_task_group_context",
|
73
|
+
TaskGroupContext(
|
74
|
+
task_group=task_group,
|
75
|
+
)
|
76
|
+
if task_group is not None or self._identifier.is_root
|
77
|
+
else None,
|
78
|
+
)
|
79
|
+
# prepare state context to capture current state
|
71
80
|
self._state_context: StateContext
|
72
|
-
self._state: tuple[State, ...]
|
73
81
|
object.__setattr__(
|
74
82
|
self,
|
75
|
-
"
|
76
|
-
state,
|
83
|
+
"_state_context",
|
84
|
+
StateContext.updated(state),
|
77
85
|
)
|
78
86
|
self._disposables: Disposables | None
|
79
87
|
object.__setattr__(
|
@@ -112,15 +120,12 @@ class ScopeContext:
|
|
112
120
|
)
|
113
121
|
|
114
122
|
def __enter__(self) -> str:
|
123
|
+
assert ( # nosec: B101
|
124
|
+
self._task_group_context is None or self._identifier.is_root
|
125
|
+
), "Can't enter synchronous context with task group"
|
115
126
|
assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
|
116
127
|
self._identifier.__enter__()
|
117
128
|
self._logger_context.__enter__()
|
118
|
-
# lazily initialize state
|
119
|
-
object.__setattr__(
|
120
|
-
self,
|
121
|
-
"_state_context",
|
122
|
-
StateContext.updated(self._state),
|
123
|
-
)
|
124
129
|
self._state_context.__enter__()
|
125
130
|
self._metrics_context.__enter__()
|
126
131
|
|
@@ -159,34 +164,23 @@ class ScopeContext:
|
|
159
164
|
async def __aenter__(self) -> str:
|
160
165
|
self._identifier.__enter__()
|
161
166
|
self._logger_context.__enter__()
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
"_task_group_context",
|
166
|
-
TaskGroupContext(),
|
167
|
-
)
|
168
|
-
await self._task_group_context.__aenter__()
|
167
|
+
|
168
|
+
if task_group := self._task_group_context:
|
169
|
+
await task_group.__aenter__()
|
169
170
|
|
170
171
|
# lazily initialize state to include disposables results
|
171
|
-
if self._disposables
|
172
|
+
if disposables := self._disposables:
|
173
|
+
assert self._state_context._token is None # nosec: B101
|
172
174
|
object.__setattr__(
|
173
175
|
self,
|
174
176
|
"_state_context",
|
175
|
-
StateContext
|
176
|
-
(
|
177
|
-
|
178
|
-
|
179
|
-
)
|
177
|
+
StateContext(
|
178
|
+
state=self._state_context._state.updated(
|
179
|
+
await disposables.__aenter__(),
|
180
|
+
),
|
180
181
|
),
|
181
182
|
)
|
182
183
|
|
183
|
-
else:
|
184
|
-
object.__setattr__(
|
185
|
-
self,
|
186
|
-
"_state_context",
|
187
|
-
StateContext.updated(self._state),
|
188
|
-
)
|
189
|
-
|
190
184
|
self._state_context.__enter__()
|
191
185
|
self._metrics_context.__enter__()
|
192
186
|
|
@@ -198,18 +192,19 @@ class ScopeContext:
|
|
198
192
|
exc_val: BaseException | None,
|
199
193
|
exc_tb: TracebackType | None,
|
200
194
|
) -> None:
|
201
|
-
if self._disposables
|
202
|
-
await
|
195
|
+
if disposables := self._disposables:
|
196
|
+
await disposables.__aexit__(
|
203
197
|
exc_type=exc_type,
|
204
198
|
exc_val=exc_val,
|
205
199
|
exc_tb=exc_tb,
|
206
200
|
)
|
207
201
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
202
|
+
if task_group := self._task_group_context:
|
203
|
+
await task_group.__aexit__(
|
204
|
+
exc_type=exc_type,
|
205
|
+
exc_val=exc_val,
|
206
|
+
exc_tb=exc_tb,
|
207
|
+
)
|
213
208
|
|
214
209
|
self._metrics_context.__exit__(
|
215
210
|
exc_type=exc_type,
|
@@ -293,6 +288,7 @@ class ctx:
|
|
293
288
|
*state: State,
|
294
289
|
disposables: Disposables | Iterable[Disposable] | None = None,
|
295
290
|
logger: Logger | None = None,
|
291
|
+
task_group: TaskGroup | None = None,
|
296
292
|
metrics: MetricsHandler | None = None,
|
297
293
|
) -> ScopeContext:
|
298
294
|
"""
|
@@ -317,6 +313,10 @@ class ctx:
|
|
317
313
|
logger used within the scope context, when not provided current logger will be used\
|
318
314
|
if any, otherwise the logger with the scope name will be requested.
|
319
315
|
|
316
|
+
task_group: TaskGroup | None
|
317
|
+
task group used for spawning and joining tasks within the context. Root scope will
|
318
|
+
always have task group created even when not set.
|
319
|
+
|
320
320
|
metrics_store: MetricsStore | None = None
|
321
321
|
metrics storage solution responsible for recording and storing metrics.\
|
322
322
|
Metrics recroding will be ignored if storage is not provided.
|
@@ -343,6 +343,7 @@ class ctx:
|
|
343
343
|
return ScopeContext(
|
344
344
|
label=label,
|
345
345
|
logger=logger,
|
346
|
+
task_group=task_group,
|
346
347
|
state=state,
|
347
348
|
disposables=resolved_disposables,
|
348
349
|
metrics=metrics,
|
@@ -401,12 +402,12 @@ class ctx:
|
|
401
402
|
return TaskGroupContext.run(function, *args, **kwargs)
|
402
403
|
|
403
404
|
@staticmethod
|
404
|
-
def stream[
|
405
|
-
source: Callable[Arguments, AsyncGenerator[
|
405
|
+
def stream[Element, **Arguments](
|
406
|
+
source: Callable[Arguments, AsyncGenerator[Element, None]],
|
406
407
|
/,
|
407
408
|
*args: Arguments.args,
|
408
409
|
**kwargs: Arguments.kwargs,
|
409
|
-
) -> AsyncIterator[
|
410
|
+
) -> AsyncIterator[Element]:
|
410
411
|
"""
|
411
412
|
Stream results produced by a generator within the proper context state.
|
412
413
|
|
@@ -427,25 +428,22 @@ class ctx:
|
|
427
428
|
iterator for accessing generated results
|
428
429
|
"""
|
429
430
|
|
430
|
-
|
431
|
-
context_snapshot: Context = copy_context()
|
431
|
+
output_stream = AsyncStream[Element]()
|
432
432
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
source,
|
437
|
-
"__name__",
|
438
|
-
"streaming",
|
439
|
-
)
|
440
|
-
)
|
441
|
-
|
442
|
-
async def generator() -> AsyncGenerator[Result, None]:
|
443
|
-
async with streaming_context:
|
433
|
+
@ctx.scope("stream")
|
434
|
+
async def stream() -> None:
|
435
|
+
try:
|
444
436
|
async for result in source(*args, **kwargs):
|
445
|
-
|
437
|
+
await output_stream.send(result)
|
438
|
+
|
439
|
+
except BaseException as exc:
|
440
|
+
output_stream.finish(exception=exc)
|
441
|
+
|
442
|
+
else:
|
443
|
+
output_stream.finish()
|
446
444
|
|
447
|
-
|
448
|
-
return
|
445
|
+
TaskGroupContext.run(stream)
|
446
|
+
return output_stream
|
449
447
|
|
450
448
|
@staticmethod
|
451
449
|
def check_cancellation() -> None:
|
@@ -488,7 +486,7 @@ class ctx:
|
|
488
486
|
StateType
|
489
487
|
resolved state instance
|
490
488
|
"""
|
491
|
-
return StateContext.
|
489
|
+
return StateContext.state(
|
492
490
|
state,
|
493
491
|
default=default,
|
494
492
|
)
|
@@ -1,10 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from asyncio import iscoroutinefunction
|
2
|
+
from collections.abc import Callable, Coroutine, Iterable, MutableMapping
|
2
3
|
from contextvars import ContextVar, Token
|
3
4
|
from types import TracebackType
|
4
|
-
from typing import Any, Self, cast, final
|
5
|
+
from typing import Any, Self, cast, final, overload
|
5
6
|
|
6
7
|
from haiway.context.types import MissingContext, MissingState
|
7
8
|
from haiway.state import State
|
9
|
+
from haiway.utils.mimic import mimic_function
|
8
10
|
|
9
11
|
__all__ = [
|
10
12
|
"ScopeState",
|
@@ -92,7 +94,7 @@ class StateContext:
|
|
92
94
|
_context = ContextVar[ScopeState]("StateContext")
|
93
95
|
|
94
96
|
@classmethod
|
95
|
-
def
|
97
|
+
def state[StateType: State](
|
96
98
|
cls,
|
97
99
|
state: type[StateType],
|
98
100
|
/,
|
@@ -179,3 +181,41 @@ class StateContext:
|
|
179
181
|
"_token",
|
180
182
|
None,
|
181
183
|
)
|
184
|
+
|
185
|
+
@overload
|
186
|
+
def __call__[Result, **Arguments](
|
187
|
+
self,
|
188
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]],
|
189
|
+
) -> Callable[Arguments, Coroutine[Any, Any, Result]]: ...
|
190
|
+
|
191
|
+
@overload
|
192
|
+
def __call__[Result, **Arguments](
|
193
|
+
self,
|
194
|
+
function: Callable[Arguments, Result],
|
195
|
+
) -> Callable[Arguments, Result]: ...
|
196
|
+
|
197
|
+
def __call__[Result, **Arguments](
|
198
|
+
self,
|
199
|
+
function: Callable[Arguments, Coroutine[Any, Any, Result]] | Callable[Arguments, Result],
|
200
|
+
) -> Callable[Arguments, Coroutine[Any, Any, Result]] | Callable[Arguments, Result]:
|
201
|
+
if iscoroutinefunction(function):
|
202
|
+
|
203
|
+
async def async_context(
|
204
|
+
*args: Arguments.args,
|
205
|
+
**kwargs: Arguments.kwargs,
|
206
|
+
) -> Result:
|
207
|
+
with self:
|
208
|
+
return await function(*args, **kwargs)
|
209
|
+
|
210
|
+
return mimic_function(function, within=async_context)
|
211
|
+
|
212
|
+
else:
|
213
|
+
|
214
|
+
def sync_context(
|
215
|
+
*args: Arguments.args,
|
216
|
+
**kwargs: Arguments.kwargs,
|
217
|
+
) -> Result:
|
218
|
+
with self:
|
219
|
+
return function(*args, **kwargs) # pyright: ignore[reportReturnType]
|
220
|
+
|
221
|
+
return mimic_function(function, within=sync_context) # pyright: ignore[reportReturnType]
|
@@ -40,12 +40,13 @@ class TaskGroupContext:
|
|
40
40
|
|
41
41
|
def __init__(
|
42
42
|
self,
|
43
|
+
task_group: TaskGroup | None = None,
|
43
44
|
) -> None:
|
44
45
|
self._group: TaskGroup
|
45
46
|
object.__setattr__(
|
46
47
|
self,
|
47
48
|
"_group",
|
48
|
-
TaskGroup(),
|
49
|
+
task_group if task_group is not None else TaskGroup(),
|
49
50
|
)
|
50
51
|
self._token: Token[TaskGroup] | None
|
51
52
|
object.__setattr__(
|
@@ -30,12 +30,10 @@ def wrap_async[**Args, Result](
|
|
30
30
|
|
31
31
|
|
32
32
|
@overload
|
33
|
-
def asynchronous[**Args, Result]() ->
|
34
|
-
Callable[
|
35
|
-
|
36
|
-
|
37
|
-
]
|
38
|
-
): ...
|
33
|
+
def asynchronous[**Args, Result]() -> Callable[
|
34
|
+
[Callable[Args, Result]],
|
35
|
+
Callable[Args, Coroutine[Any, Any, Result]],
|
36
|
+
]: ...
|
39
37
|
|
40
38
|
|
41
39
|
@overload
|
@@ -15,6 +15,7 @@ __all_ = [
|
|
15
15
|
|
16
16
|
class MetricsScopeStore:
|
17
17
|
__slots__ = (
|
18
|
+
"allow_exit",
|
18
19
|
"entered",
|
19
20
|
"exited",
|
20
21
|
"identifier",
|
@@ -31,6 +32,7 @@ class MetricsScopeStore:
|
|
31
32
|
self.entered: float = monotonic()
|
32
33
|
self.metrics: dict[type[State], State] = {}
|
33
34
|
self.exited: float | None = None
|
35
|
+
self.allow_exit: bool = False
|
34
36
|
self.nested: list[MetricsScopeStore] = []
|
35
37
|
|
36
38
|
@property
|
@@ -115,7 +117,7 @@ class MetricsHolder:
|
|
115
117
|
|
116
118
|
def __init__(self) -> None:
|
117
119
|
self.root_scope: ScopeIdentifier | None = None
|
118
|
-
self.scopes: dict[
|
120
|
+
self.scopes: dict[str, MetricsScopeStore] = {}
|
119
121
|
|
120
122
|
def record(
|
121
123
|
self,
|
@@ -124,10 +126,10 @@ class MetricsHolder:
|
|
124
126
|
metric: State,
|
125
127
|
) -> None:
|
126
128
|
assert self.root_scope is not None # nosec: B101
|
127
|
-
assert scope in self.scopes # nosec: B101
|
129
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
128
130
|
|
129
131
|
metric_type: type[State] = type(metric)
|
130
|
-
metrics: dict[type[State], State] = self.scopes[scope].metrics
|
132
|
+
metrics: dict[type[State], State] = self.scopes[scope.scope_id].metrics
|
131
133
|
if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
|
132
134
|
metrics[type(metric)] = current.__add__(metric) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
133
135
|
|
@@ -142,29 +144,29 @@ class MetricsHolder:
|
|
142
144
|
merged: bool,
|
143
145
|
) -> Metric | None:
|
144
146
|
assert self.root_scope is not None # nosec: B101
|
145
|
-
assert scope in self.scopes # nosec: B101
|
147
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
146
148
|
|
147
149
|
if merged:
|
148
|
-
return self.scopes[scope].merged(metric)
|
150
|
+
return self.scopes[scope.scope_id].merged(metric)
|
149
151
|
|
150
152
|
else:
|
151
|
-
return cast(Metric | None, self.scopes[scope].metrics.get(metric))
|
153
|
+
return cast(Metric | None, self.scopes[scope.scope_id].metrics.get(metric))
|
152
154
|
|
153
155
|
def enter_scope[Metric: State](
|
154
156
|
self,
|
155
157
|
scope: ScopeIdentifier,
|
156
158
|
/,
|
157
159
|
) -> None:
|
158
|
-
assert scope not in self.scopes # nosec: B101
|
160
|
+
assert scope.scope_id not in self.scopes # nosec: B101
|
159
161
|
scope_metrics = MetricsScopeStore(scope)
|
160
|
-
self.scopes[scope] = scope_metrics
|
162
|
+
self.scopes[scope.scope_id] = scope_metrics
|
161
163
|
|
162
164
|
if self.root_scope is None:
|
163
165
|
self.root_scope = scope
|
164
166
|
|
165
167
|
else:
|
166
168
|
for key in self.scopes.keys():
|
167
|
-
if key
|
169
|
+
if key == scope.parent_id:
|
168
170
|
self.scopes[key].nested.append(scope_metrics)
|
169
171
|
return
|
170
172
|
|
@@ -177,8 +179,18 @@ class MetricsHolder:
|
|
177
179
|
scope: ScopeIdentifier,
|
178
180
|
/,
|
179
181
|
) -> None:
|
180
|
-
assert
|
181
|
-
self.scopes
|
182
|
+
assert self.root_scope is not None # nosec: B101
|
183
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
184
|
+
|
185
|
+
self.scopes[scope.scope_id].allow_exit = True
|
186
|
+
|
187
|
+
if not all(nested.exited for nested in self.scopes[scope.scope_id].nested):
|
188
|
+
return # not completed yet
|
189
|
+
|
190
|
+
self.scopes[scope.scope_id].exited = monotonic()
|
191
|
+
|
192
|
+
if scope != self.root_scope and self.scopes[scope.parent_id].allow_exit:
|
193
|
+
self.exit_scope(self.scopes[scope.parent_id].identifier)
|
182
194
|
|
183
195
|
|
184
196
|
@final
|
@@ -213,7 +225,7 @@ class MetricsLogger:
|
|
213
225
|
redact_content: bool,
|
214
226
|
) -> None:
|
215
227
|
self.root_scope: ScopeIdentifier | None = None
|
216
|
-
self.scopes: dict[
|
228
|
+
self.scopes: dict[str, MetricsScopeStore] = {}
|
217
229
|
self.items_limit: int | None = items_limit
|
218
230
|
self.redact_content: bool = redact_content
|
219
231
|
|
@@ -224,10 +236,10 @@ class MetricsLogger:
|
|
224
236
|
metric: State,
|
225
237
|
) -> None:
|
226
238
|
assert self.root_scope is not None # nosec: B101
|
227
|
-
assert scope in self.scopes # nosec: B101
|
239
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
228
240
|
|
229
241
|
metric_type: type[State] = type(metric)
|
230
|
-
metrics: dict[type[State], State] = self.scopes[scope].metrics
|
242
|
+
metrics: dict[type[State], State] = self.scopes[scope.scope_id].metrics
|
231
243
|
if (current := metrics.get(metric_type)) and hasattr(current, "__add__"):
|
232
244
|
metrics[type(metric)] = current.__add__(metric) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
233
245
|
|
@@ -248,29 +260,29 @@ class MetricsLogger:
|
|
248
260
|
merged: bool,
|
249
261
|
) -> Metric | None:
|
250
262
|
assert self.root_scope is not None # nosec: B101
|
251
|
-
assert scope in self.scopes # nosec: B101
|
263
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
252
264
|
|
253
265
|
if merged:
|
254
|
-
return self.scopes[scope].merged(metric)
|
266
|
+
return self.scopes[scope.scope_id].merged(metric)
|
255
267
|
|
256
268
|
else:
|
257
|
-
return cast(Metric | None, self.scopes[scope].metrics.get(metric))
|
269
|
+
return cast(Metric | None, self.scopes[scope.scope_id].metrics.get(metric))
|
258
270
|
|
259
271
|
def enter_scope[Metric: State](
|
260
272
|
self,
|
261
273
|
scope: ScopeIdentifier,
|
262
274
|
/,
|
263
275
|
) -> None:
|
264
|
-
assert scope not in self.scopes # nosec: B101
|
276
|
+
assert scope.scope_id not in self.scopes # nosec: B101
|
265
277
|
scope_metrics = MetricsScopeStore(scope)
|
266
|
-
self.scopes[scope] = scope_metrics
|
278
|
+
self.scopes[scope.scope_id] = scope_metrics
|
267
279
|
|
268
280
|
if self.root_scope is None:
|
269
281
|
self.root_scope = scope
|
270
282
|
|
271
283
|
else:
|
272
284
|
for key in self.scopes.keys():
|
273
|
-
if key
|
285
|
+
if key == scope.parent_id:
|
274
286
|
self.scopes[key].nested.append(scope_metrics)
|
275
287
|
return
|
276
288
|
|
@@ -283,12 +295,22 @@ class MetricsLogger:
|
|
283
295
|
scope: ScopeIdentifier,
|
284
296
|
/,
|
285
297
|
) -> None:
|
286
|
-
assert
|
287
|
-
self.scopes
|
298
|
+
assert self.root_scope is not None # nosec: B101
|
299
|
+
assert scope.scope_id in self.scopes # nosec: B101
|
300
|
+
|
301
|
+
self.scopes[scope.scope_id].allow_exit = True
|
302
|
+
|
303
|
+
if not all(nested.exited for nested in self.scopes[scope.scope_id].nested):
|
304
|
+
return # not completed yet
|
305
|
+
|
306
|
+
self.scopes[scope.scope_id].exited = monotonic()
|
307
|
+
|
308
|
+
if scope != self.root_scope and self.scopes[scope.parent_id].allow_exit:
|
309
|
+
self.exit_scope(self.scopes[scope.parent_id].identifier)
|
288
310
|
|
289
|
-
|
311
|
+
elif scope == self.root_scope and self.scopes[self.root_scope.scope_id].finished:
|
290
312
|
if log := _tree_log(
|
291
|
-
self.scopes[scope],
|
313
|
+
self.scopes[scope.scope_id],
|
292
314
|
list_items_limit=self.items_limit,
|
293
315
|
redact_content=self.redact_content,
|
294
316
|
):
|
@@ -13,9 +13,11 @@ from haiway.utils.logs import setup_logging
|
|
13
13
|
from haiway.utils.mimic import mimic_function
|
14
14
|
from haiway.utils.noop import async_noop, noop
|
15
15
|
from haiway.utils.queue import AsyncQueue
|
16
|
+
from haiway.utils.stream import AsyncStream
|
16
17
|
|
17
18
|
__all__ = [
|
18
19
|
"AsyncQueue",
|
20
|
+
"AsyncStream",
|
19
21
|
"always",
|
20
22
|
"as_dict",
|
21
23
|
"as_list",
|