haiway 0.13.1__tar.gz → 0.14.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.14.0}/PKG-INFO +1 -1
- haiway-0.14.0/junit/test-results.xml +1 -0
- {haiway-0.13.1 → haiway-0.14.0}/pyproject.toml +1 -1
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/__init__.py +2 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/access.py +26 -44
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/state.py +1 -1
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/metrics.py +46 -24
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/__init__.py +2 -0
- haiway-0.14.0/src/haiway/utils/stream.py +97 -0
- haiway-0.14.0/tests/test_async_stream.py +138 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_streaming.py +5 -11
- {haiway-0.13.1 → haiway-0.14.0}/uv.lock +1 -1
- haiway-0.13.1/junit/test-results.xml +0 -1
- {haiway-0.13.1 → haiway-0.14.0}/.github/workflows/ci.yml +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/.github/workflows/publish.yml +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/.gitignore +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/LICENSE +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/Makefile +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/README.md +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/config/pre-push +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/.dockerignore +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/Dockerfile +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/Makefile +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/README.md +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/config/.env.example +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/config/unit.json +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/docker-compose.yml +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/pyproject.toml +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/__int__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/todos/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/todos/config.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/todos/state.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/todos/types.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/__main__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/__main__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/application.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/config.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/middlewares/context.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/routes/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/routes/technical.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/server/routes/todos.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/examples/fastAPI/uv.lock +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/guidelines/functionalities.md +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/guidelines/packages.md +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/disposables.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/identifier.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/logging.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/metrics.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/tasks.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/context/types.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/asynchrony.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/py.typed +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/attributes.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/path.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/requirement.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/structure.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/state/validation.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/types/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/types/default.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/types/frozen.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/types/missing.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/always.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/collections.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/env.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/freezing.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/logs.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/noop.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/src/haiway/utils/queue.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/__init__.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_async_queue.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_attribute_path.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_auto_retry.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_cache.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_context.py +0 -0
- {haiway-0.13.1 → haiway-0.14.0}/tests/test_state.py +0 -0
- {haiway-0.13.1 → haiway-0.14.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.14.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.127" timestamp="2025-04-01T10:53:25.573207" hostname="fv-az1786-487"><testcase classname="tests.test_async_queue" name="test_fails_when_stream_fails" time="0.002" /><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.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.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.011" /></testsuite></testsuites>
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
5
5
|
[project]
|
6
6
|
name = "haiway"
|
7
7
|
description = "Framework for dependency injection and state management within structured concurrency model."
|
8
|
-
version = "0.
|
8
|
+
version = "0.14.0"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -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",
|
@@ -11,7 +11,6 @@ from collections.abc import (
|
|
11
11
|
Coroutine,
|
12
12
|
Iterable,
|
13
13
|
)
|
14
|
-
from contextvars import Context, copy_context
|
15
14
|
from logging import Logger
|
16
15
|
from types import TracebackType
|
17
16
|
from typing import Any, final, overload
|
@@ -24,6 +23,7 @@ from haiway.context.state import StateContext
|
|
24
23
|
from haiway.context.tasks import TaskGroupContext
|
25
24
|
from haiway.state import State
|
26
25
|
from haiway.utils import mimic_function
|
26
|
+
from haiway.utils.stream import AsyncStream
|
27
27
|
|
28
28
|
__all__ = [
|
29
29
|
"ctx",
|
@@ -37,7 +37,6 @@ class ScopeContext:
|
|
37
37
|
"_identifier",
|
38
38
|
"_logger_context",
|
39
39
|
"_metrics_context",
|
40
|
-
"_state",
|
41
40
|
"_state_context",
|
42
41
|
"_task_group_context",
|
43
42
|
)
|
@@ -67,13 +66,12 @@ class ScopeContext:
|
|
67
66
|
)
|
68
67
|
# postponing task group creation to include only when needed
|
69
68
|
self._task_group_context: TaskGroupContext
|
70
|
-
#
|
69
|
+
# prepare state context to capture current state
|
71
70
|
self._state_context: StateContext
|
72
|
-
self._state: tuple[State, ...]
|
73
71
|
object.__setattr__(
|
74
72
|
self,
|
75
|
-
"
|
76
|
-
state,
|
73
|
+
"_state_context",
|
74
|
+
StateContext.updated(state),
|
77
75
|
)
|
78
76
|
self._disposables: Disposables | None
|
79
77
|
object.__setattr__(
|
@@ -115,12 +113,6 @@ class ScopeContext:
|
|
115
113
|
assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
|
116
114
|
self._identifier.__enter__()
|
117
115
|
self._logger_context.__enter__()
|
118
|
-
# lazily initialize state
|
119
|
-
object.__setattr__(
|
120
|
-
self,
|
121
|
-
"_state_context",
|
122
|
-
StateContext.updated(self._state),
|
123
|
-
)
|
124
116
|
self._state_context.__enter__()
|
125
117
|
self._metrics_context.__enter__()
|
126
118
|
|
@@ -169,24 +161,17 @@ class ScopeContext:
|
|
169
161
|
|
170
162
|
# lazily initialize state to include disposables results
|
171
163
|
if self._disposables is not None:
|
164
|
+
assert self._state_context._token is None # nosec: B101
|
172
165
|
object.__setattr__(
|
173
166
|
self,
|
174
167
|
"_state_context",
|
175
|
-
StateContext
|
176
|
-
(
|
177
|
-
|
178
|
-
|
179
|
-
)
|
168
|
+
StateContext(
|
169
|
+
state=self._state_context._state.updated(
|
170
|
+
await self._disposables.__aenter__(),
|
171
|
+
),
|
180
172
|
),
|
181
173
|
)
|
182
174
|
|
183
|
-
else:
|
184
|
-
object.__setattr__(
|
185
|
-
self,
|
186
|
-
"_state_context",
|
187
|
-
StateContext.updated(self._state),
|
188
|
-
)
|
189
|
-
|
190
175
|
self._state_context.__enter__()
|
191
176
|
self._metrics_context.__enter__()
|
192
177
|
|
@@ -401,12 +386,12 @@ class ctx:
|
|
401
386
|
return TaskGroupContext.run(function, *args, **kwargs)
|
402
387
|
|
403
388
|
@staticmethod
|
404
|
-
def stream[
|
405
|
-
source: Callable[Arguments, AsyncGenerator[
|
389
|
+
def stream[Element, **Arguments](
|
390
|
+
source: Callable[Arguments, AsyncGenerator[Element, None]],
|
406
391
|
/,
|
407
392
|
*args: Arguments.args,
|
408
393
|
**kwargs: Arguments.kwargs,
|
409
|
-
) -> AsyncIterator[
|
394
|
+
) -> AsyncIterator[Element]:
|
410
395
|
"""
|
411
396
|
Stream results produced by a generator within the proper context state.
|
412
397
|
|
@@ -427,25 +412,22 @@ class ctx:
|
|
427
412
|
iterator for accessing generated results
|
428
413
|
"""
|
429
414
|
|
430
|
-
|
431
|
-
context_snapshot: Context = copy_context()
|
432
|
-
|
433
|
-
# prepare nested context
|
434
|
-
streaming_context: ScopeContext = ctx.scope(
|
435
|
-
getattr(
|
436
|
-
source,
|
437
|
-
"__name__",
|
438
|
-
"streaming",
|
439
|
-
)
|
440
|
-
)
|
415
|
+
output_stream = AsyncStream[Element]()
|
441
416
|
|
442
|
-
|
443
|
-
|
417
|
+
@ctx.scope("stream")
|
418
|
+
async def stream() -> None:
|
419
|
+
try:
|
444
420
|
async for result in source(*args, **kwargs):
|
445
|
-
|
421
|
+
await output_stream.send(result)
|
422
|
+
|
423
|
+
except BaseException as exc:
|
424
|
+
output_stream.finish(exception=exc)
|
425
|
+
|
426
|
+
else:
|
427
|
+
output_stream.finish()
|
446
428
|
|
447
|
-
|
448
|
-
return
|
429
|
+
TaskGroupContext.run(stream)
|
430
|
+
return output_stream
|
449
431
|
|
450
432
|
@staticmethod
|
451
433
|
def check_cancellation() -> None:
|
@@ -488,7 +470,7 @@ class ctx:
|
|
488
470
|
StateType
|
489
471
|
resolved state instance
|
490
472
|
"""
|
491
|
-
return StateContext.
|
473
|
+
return StateContext.state(
|
492
474
|
state,
|
493
475
|
default=default,
|
494
476
|
)
|
@@ -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",
|
@@ -0,0 +1,97 @@
|
|
1
|
+
from asyncio import (
|
2
|
+
AbstractEventLoop,
|
3
|
+
CancelledError,
|
4
|
+
Future,
|
5
|
+
get_running_loop,
|
6
|
+
)
|
7
|
+
from collections.abc import AsyncIterator
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"AsyncStream",
|
11
|
+
]
|
12
|
+
|
13
|
+
|
14
|
+
class AsyncStream[Element](AsyncIterator[Element]):
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
loop: AbstractEventLoop | None = None,
|
18
|
+
) -> None:
|
19
|
+
self._loop: AbstractEventLoop = loop or get_running_loop()
|
20
|
+
self._ready: Future[None] = self._loop.create_future()
|
21
|
+
self._waiting: Future[Element] | None = None
|
22
|
+
self._finish_reason: BaseException | None = None
|
23
|
+
|
24
|
+
@property
|
25
|
+
def finished(self) -> bool:
|
26
|
+
return self._finish_reason is not None
|
27
|
+
|
28
|
+
async def send(
|
29
|
+
self,
|
30
|
+
element: Element,
|
31
|
+
/,
|
32
|
+
) -> None:
|
33
|
+
if self._finish_reason is not None:
|
34
|
+
return # already finished
|
35
|
+
|
36
|
+
# wait for readiness
|
37
|
+
await self._ready
|
38
|
+
# we could finish while waiting
|
39
|
+
if self._finish_reason is not None:
|
40
|
+
return # already finished
|
41
|
+
|
42
|
+
assert self._waiting is not None and not self._waiting.done() # nosec: B101
|
43
|
+
# send the element
|
44
|
+
self._waiting.set_result(element)
|
45
|
+
# and create new readiness future afterwards
|
46
|
+
self._ready = self._loop.create_future()
|
47
|
+
|
48
|
+
def finish(
|
49
|
+
self,
|
50
|
+
exception: BaseException | None = None,
|
51
|
+
) -> None:
|
52
|
+
if self.finished:
|
53
|
+
return # already finished, ignore
|
54
|
+
|
55
|
+
self._finish_reason = exception or StopAsyncIteration()
|
56
|
+
|
57
|
+
if not self._ready.done():
|
58
|
+
if get_running_loop() is not self._loop:
|
59
|
+
self._loop.call_soon_threadsafe(
|
60
|
+
self._ready.set_result,
|
61
|
+
None,
|
62
|
+
)
|
63
|
+
|
64
|
+
else:
|
65
|
+
self._ready.set_result(None)
|
66
|
+
|
67
|
+
if self._waiting is not None and not self._waiting.done():
|
68
|
+
if get_running_loop() is not self._loop:
|
69
|
+
self._loop.call_soon_threadsafe(
|
70
|
+
self._waiting.set_exception,
|
71
|
+
self._finish_reason,
|
72
|
+
)
|
73
|
+
|
74
|
+
else:
|
75
|
+
self._waiting.set_exception(self._finish_reason)
|
76
|
+
|
77
|
+
def cancel(self) -> None:
|
78
|
+
self.finish(exception=CancelledError())
|
79
|
+
|
80
|
+
async def __anext__(self) -> Element:
|
81
|
+
assert self._waiting is None, "AsyncStream can't be reused" # nosec: B101
|
82
|
+
|
83
|
+
if self._finish_reason:
|
84
|
+
raise self._finish_reason
|
85
|
+
|
86
|
+
try:
|
87
|
+
assert not self._ready.done() # nosec: B101
|
88
|
+
# create new waiting future
|
89
|
+
self._waiting = self._loop.create_future()
|
90
|
+
# and notify readiness
|
91
|
+
self._ready.set_result(None)
|
92
|
+
# and wait for the result
|
93
|
+
return await self._waiting
|
94
|
+
|
95
|
+
finally:
|
96
|
+
# cleanup waiting future
|
97
|
+
self._waiting = None
|
@@ -0,0 +1,138 @@
|
|
1
|
+
from asyncio import CancelledError, Task
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from pytest import raises
|
5
|
+
|
6
|
+
from haiway import AsyncStream, ctx
|
7
|
+
|
8
|
+
|
9
|
+
class FakeException(Exception):
|
10
|
+
pass
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.mark.asyncio
|
14
|
+
async def test_fails_when_stream_fails():
|
15
|
+
stream: AsyncStream[int] = AsyncStream()
|
16
|
+
Task(stream.send(0))
|
17
|
+
elements: int = 0
|
18
|
+
with raises(FakeException):
|
19
|
+
async for _ in stream:
|
20
|
+
elements += 1
|
21
|
+
stream.finish(exception=FakeException())
|
22
|
+
|
23
|
+
assert elements == 1
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.mark.asyncio
|
27
|
+
async def test_cancels_when_iteration_cancels():
|
28
|
+
stream: AsyncStream[int] = AsyncStream()
|
29
|
+
elements: int = 0
|
30
|
+
with raises(CancelledError):
|
31
|
+
ctx.cancel()
|
32
|
+
async for _ in stream:
|
33
|
+
elements += 1
|
34
|
+
|
35
|
+
assert elements == 0
|
36
|
+
|
37
|
+
|
38
|
+
@pytest.mark.asyncio
|
39
|
+
async def test_ends_when_stream_ends():
|
40
|
+
stream: AsyncStream[int] = AsyncStream()
|
41
|
+
stream.finish()
|
42
|
+
elements: int = 0
|
43
|
+
async for _ in stream:
|
44
|
+
elements += 1
|
45
|
+
|
46
|
+
assert elements == 0
|
47
|
+
|
48
|
+
|
49
|
+
@pytest.mark.asyncio
|
50
|
+
async def test_finishes_without_buffer():
|
51
|
+
stream: AsyncStream[int] = AsyncStream()
|
52
|
+
Task(stream.send(0))
|
53
|
+
Task(stream.send(1))
|
54
|
+
Task(stream.send(2))
|
55
|
+
Task(stream.send(3))
|
56
|
+
stream.finish()
|
57
|
+
elements: int = 0
|
58
|
+
|
59
|
+
async for _ in stream:
|
60
|
+
elements += 1
|
61
|
+
|
62
|
+
assert elements == 0
|
63
|
+
|
64
|
+
|
65
|
+
@pytest.mark.asyncio
|
66
|
+
async def test_fails_without_buffer():
|
67
|
+
stream: AsyncStream[int] = AsyncStream()
|
68
|
+
Task(stream.send(0))
|
69
|
+
Task(stream.send(1))
|
70
|
+
Task(stream.send(2))
|
71
|
+
Task(stream.send(3))
|
72
|
+
stream.finish(exception=FakeException())
|
73
|
+
elements: int = 0
|
74
|
+
|
75
|
+
with raises(FakeException):
|
76
|
+
async for _ in stream:
|
77
|
+
elements += 1
|
78
|
+
|
79
|
+
assert elements == 0
|
80
|
+
|
81
|
+
|
82
|
+
@pytest.mark.asyncio
|
83
|
+
async def test_delivers_updates_when_sending():
|
84
|
+
stream: AsyncStream[int] = AsyncStream()
|
85
|
+
Task(stream.send(0))
|
86
|
+
|
87
|
+
elements: list[int] = []
|
88
|
+
|
89
|
+
async for element in stream:
|
90
|
+
elements.append(element)
|
91
|
+
if len(elements) < 10:
|
92
|
+
Task(stream.send(element + 1))
|
93
|
+
else:
|
94
|
+
stream.finish()
|
95
|
+
|
96
|
+
assert elements == list(range(0, 10))
|
97
|
+
|
98
|
+
|
99
|
+
@pytest.mark.asyncio
|
100
|
+
async def test_ignores_when_sending_to_finished():
|
101
|
+
stream: AsyncStream[int] = AsyncStream()
|
102
|
+
stream.finish()
|
103
|
+
|
104
|
+
await stream.send(42)
|
105
|
+
|
106
|
+
|
107
|
+
@pytest.mark.asyncio
|
108
|
+
async def test_ignores_when_sending_to_failed():
|
109
|
+
stream: AsyncStream[int] = AsyncStream()
|
110
|
+
stream.finish(exception=FakeException())
|
111
|
+
|
112
|
+
await stream.send(42)
|
113
|
+
|
114
|
+
|
115
|
+
@pytest.mark.asyncio
|
116
|
+
async def test_ignores_when_finishing_when_finished():
|
117
|
+
stream: AsyncStream[int] = AsyncStream()
|
118
|
+
stream.finish()
|
119
|
+
stream.finish() # should not raise
|
120
|
+
|
121
|
+
|
122
|
+
@pytest.mark.asyncio
|
123
|
+
async def test_delivers_all_when_sending_async():
|
124
|
+
stream: AsyncStream[int] = AsyncStream()
|
125
|
+
|
126
|
+
async def sender() -> None:
|
127
|
+
await stream.send(0)
|
128
|
+
await stream.send(1)
|
129
|
+
await stream.send(2)
|
130
|
+
stream.finish()
|
131
|
+
|
132
|
+
ctx.spawn(sender)
|
133
|
+
elements: list[int] = []
|
134
|
+
|
135
|
+
async for element in stream:
|
136
|
+
elements.append(element)
|
137
|
+
|
138
|
+
assert elements == [0, 1, 2]
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from asyncio import CancelledError, sleep
|
2
|
-
from collections.abc import AsyncGenerator
|
2
|
+
from collections.abc import AsyncGenerator
|
3
3
|
|
4
4
|
from pytest import mark, raises
|
5
5
|
|
@@ -77,14 +77,11 @@ async def test_streaming_context_variables_access_is_preserved():
|
|
77
77
|
with ctx.scope("nested", ctx.state(TestState).updated(value=value)):
|
78
78
|
yield ctx.state(TestState)
|
79
79
|
|
80
|
-
stream: AsyncIterator[TestState]
|
81
80
|
async with ctx.scope("test", TestState(value=42)):
|
82
81
|
elements: list[TestState] = []
|
83
82
|
|
84
|
-
|
85
|
-
|
86
|
-
async for element in stream:
|
87
|
-
elements.append(element)
|
83
|
+
async for element in ctx.stream(generator, 10):
|
84
|
+
elements.append(element)
|
88
85
|
|
89
86
|
assert elements == [
|
90
87
|
TestState(value=42),
|
@@ -109,14 +106,11 @@ async def test_nested_streaming_streams_correctly():
|
|
109
106
|
async for item in ctx.stream(inner, value):
|
110
107
|
yield item
|
111
108
|
|
112
|
-
stream: AsyncIterator[TestState]
|
113
109
|
async with ctx.scope("test", TestState(value=42)):
|
114
110
|
elements: list[TestState] = []
|
115
111
|
|
116
|
-
|
117
|
-
|
118
|
-
async for element in stream:
|
119
|
-
elements.append(element)
|
112
|
+
async for element in ctx.stream(outer, 10):
|
113
|
+
elements.append(element)
|
120
114
|
|
121
115
|
assert elements == [
|
122
116
|
TestState(value=42),
|
@@ -1 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="78" time="1.098" timestamp="2025-03-25T13:23:02.644198" hostname="fv-az1307-33"><testcase classname="tests.test_async_queue" name="test_fails_when_stream_fails" time="0.002" /><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_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.001" /><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.001" /><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.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.002" /><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>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|