haiway 0.21.1__tar.gz → 0.21.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haiway-0.21.2/CLAUDE.md +163 -0
- {haiway-0.21.1 → haiway-0.21.2}/PKG-INFO +1 -1
- {haiway-0.21.1 → haiway-0.21.2}/guidelines/llms.txt +30 -1
- haiway-0.21.2/guidelines/state.md +282 -0
- {haiway-0.21.1 → haiway-0.21.2}/junit/test-results.xml +1 -1
- {haiway-0.21.1 → haiway-0.21.2}/pyproject.toml +1 -1
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/access.py +6 -6
- haiway-0.21.2/tests/test_state_validation.py +542 -0
- {haiway-0.21.1 → haiway-0.21.2}/uv.lock +1 -1
- {haiway-0.21.1 → haiway-0.21.2}/.github/workflows/ci.yml +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/.github/workflows/publish.yml +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/.gitignore +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/LICENSE +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/Makefile +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/README.md +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/config/pre-push +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/.dockerignore +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/Dockerfile +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/Makefile +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/README.md +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/config/.env.example +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/config/unit.json +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/docker-compose.yml +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/pyproject.toml +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/__int__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/todos/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/todos/config.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/todos/state.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/todos/types.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/__main__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/__main__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/application.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/config.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/middlewares/context.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/routes/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/routes/technical.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/server/routes/todos.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/examples/fastAPI/uv.lock +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/guidelines/functionalities.md +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/guidelines/packages.md +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/disposables.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/identifier.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/observability.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/state.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/tasks.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/context/types.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/asynchrony.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/concurrent.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/observability.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/throttling.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/opentelemetry/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/opentelemetry/observability.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/py.typed +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/attributes.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/path.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/requirement.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/structure.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/state/validation.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/types/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/types/default.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/types/missing.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/always.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/collections.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/env.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/formatting.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/freezing.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/logs.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/mimic.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/noop.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/queue.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/src/haiway/utils/stream.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/__init__.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_async_queue.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_async_stream.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_attribute_path.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_attribute_requirement.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_auto_retry.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_cache.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_context.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_process_concurrently.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_state.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_streaming.py +0 -0
- {haiway-0.21.1 → haiway-0.21.2}/tests/test_timeout.py +0 -0
haiway-0.21.2/CLAUDE.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Development Commands
|
6
|
+
|
7
|
+
```bash
|
8
|
+
# Setup development environment
|
9
|
+
make venv
|
10
|
+
. ./.venv/bin/activate
|
11
|
+
|
12
|
+
# Sync dependencies
|
13
|
+
make sync
|
14
|
+
|
15
|
+
# Update dependencies
|
16
|
+
make update
|
17
|
+
|
18
|
+
# Run formatter - make sure to run with activated venv
|
19
|
+
make format
|
20
|
+
|
21
|
+
# Run linters and type checker - make sure to run with activated venv
|
22
|
+
make lint
|
23
|
+
|
24
|
+
# Run test suite - make sure to run with activated venv
|
25
|
+
make test
|
26
|
+
|
27
|
+
# Run a single test - make sure to run with activated venv
|
28
|
+
python -B -m pytest -v tests/test_file.py::test_function
|
29
|
+
```
|
30
|
+
|
31
|
+
## Architecture Overview
|
32
|
+
|
33
|
+
Haiway is a Python framework (3.12+) designed for functional programming with structured concurrency. It focuses on:
|
34
|
+
|
35
|
+
1. **Immutable State Management**: Using State classes for type-safe, immutable data structures
|
36
|
+
2. **Context-based Dependency Injection**: Propagating state through execution contexts
|
37
|
+
3. **Functional Approach**: Emphasizing pure functions over objects with methods
|
38
|
+
|
39
|
+
### Core Components
|
40
|
+
|
41
|
+
- **Context System** (`haiway.context`): Provides scoped execution environments with access to state
|
42
|
+
- **State Management** (`haiway.state`): Immutable data structures with validation
|
43
|
+
- **Helpers** (`haiway.helpers`): Utilities for async operations, caching, retries, etc.
|
44
|
+
- **Types** (`haiway.types`): Base type definitions
|
45
|
+
|
46
|
+
## Development Patterns
|
47
|
+
|
48
|
+
### Defining Types
|
49
|
+
|
50
|
+
```python
|
51
|
+
from typing import Protocol, runtime_checkable
|
52
|
+
from haiway import State
|
53
|
+
|
54
|
+
# Data structure
|
55
|
+
class UserData(State):
|
56
|
+
id: str
|
57
|
+
name: str
|
58
|
+
email: str | None = None
|
59
|
+
|
60
|
+
# Function interface
|
61
|
+
@runtime_checkable
|
62
|
+
class UserFetching(Protocol):
|
63
|
+
async def __call__(self, id: str) -> UserData: ...
|
64
|
+
```
|
65
|
+
|
66
|
+
### Managing State
|
67
|
+
|
68
|
+
```python
|
69
|
+
from haiway import State, ctx
|
70
|
+
from .types import UserFetching, UserData
|
71
|
+
|
72
|
+
# Configuration state
|
73
|
+
class UserServiceConfig(State):
|
74
|
+
api_url: str = "https://api.example.com"
|
75
|
+
timeout_seconds: int = 30
|
76
|
+
|
77
|
+
# Functionality container
|
78
|
+
class UserService(State):
|
79
|
+
# Function implementations
|
80
|
+
fetching: UserFetching
|
81
|
+
|
82
|
+
# Class method interface
|
83
|
+
@classmethod
|
84
|
+
async def fetch_user(cls, id: str) -> UserData:
|
85
|
+
return await ctx.state(cls).fetching(id)
|
86
|
+
```
|
87
|
+
|
88
|
+
### Implementation
|
89
|
+
|
90
|
+
```python
|
91
|
+
from haiway import ctx
|
92
|
+
from .types import UserData
|
93
|
+
from .state import UserService, UserServiceConfig
|
94
|
+
|
95
|
+
# Concrete implementation
|
96
|
+
async def http_user_fetching(id: str) -> UserData:
|
97
|
+
config = ctx.state(UserServiceConfig)
|
98
|
+
# Implementation using config.api_url
|
99
|
+
return UserData(id=id, name="Example User")
|
100
|
+
|
101
|
+
# Factory function
|
102
|
+
def http_user_service() -> UserService:
|
103
|
+
return UserService(fetching=http_user_fetching)
|
104
|
+
```
|
105
|
+
|
106
|
+
### Context Usage
|
107
|
+
|
108
|
+
```python
|
109
|
+
from haiway import ctx
|
110
|
+
from .implementation import http_user_service
|
111
|
+
from .state import UserServiceConfig
|
112
|
+
|
113
|
+
async def main():
|
114
|
+
# Set up execution context
|
115
|
+
async with ctx.scope(
|
116
|
+
"main",
|
117
|
+
http_user_service(),
|
118
|
+
UserServiceConfig(api_url="https://custom-api.example.com")
|
119
|
+
):
|
120
|
+
# Use functionality through class methods
|
121
|
+
user = await UserService.fetch_user("user-123")
|
122
|
+
```
|
123
|
+
|
124
|
+
## Common Patterns
|
125
|
+
|
126
|
+
1. **Immutable Updates**:
|
127
|
+
```python
|
128
|
+
# Create new instance with updated values
|
129
|
+
updated_config = config.updated(api_url="https://new-api.example.com")
|
130
|
+
```
|
131
|
+
|
132
|
+
2. **Context Access**:
|
133
|
+
```python
|
134
|
+
# Get state from current context
|
135
|
+
config = ctx.state(ConfigType)
|
136
|
+
```
|
137
|
+
|
138
|
+
3. **Disposable Resources**:
|
139
|
+
```python
|
140
|
+
# Resources that need cleanup
|
141
|
+
async def create_resource() -> tuple[Resource, Disposable]:
|
142
|
+
resource = Resource()
|
143
|
+
async def cleanup(): await resource.close()
|
144
|
+
return resource, ctx.disposable(cleanup)
|
145
|
+
```
|
146
|
+
|
147
|
+
## Testing Guidelines
|
148
|
+
|
149
|
+
- Tests use pytest with pytest-asyncio
|
150
|
+
- Mock context values with `ctx.updated`
|
151
|
+
- For async tests, use the `@pytest.mark.asyncio` decorator
|
152
|
+
- Immutable state makes testing simpler - no need to reset between tests
|
153
|
+
|
154
|
+
```python
|
155
|
+
import pytest
|
156
|
+
from haiway import ctx
|
157
|
+
|
158
|
+
@pytest.mark.asyncio
|
159
|
+
async def test_functionality():
|
160
|
+
async with ctx.scope("test", TestState(value="test")):
|
161
|
+
result = await some_function()
|
162
|
+
assert result == expected_result
|
163
|
+
```
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.21.
|
3
|
+
Version: 0.21.2
|
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
|
@@ -31,13 +31,17 @@ Define interfaces using Protocol and data using State:
|
|
31
31
|
|
32
32
|
```python
|
33
33
|
from typing import Protocol, Any, runtime_checkable
|
34
|
+
from collections.abc import Sequence, Mapping, Set
|
34
35
|
from haiway import State
|
35
36
|
|
36
|
-
# Data structure
|
37
|
+
# Data structure - use abstract collection types
|
37
38
|
class UserData(State):
|
38
39
|
id: str
|
39
40
|
name: str
|
40
41
|
email: str | None = None
|
42
|
+
tags: Sequence[str] = () # Not list[str] - becomes tuple
|
43
|
+
metadata: Mapping[str, str] = {} # Not dict[str, str] - stays dict
|
44
|
+
roles: Set[str] = frozenset() # Not set[str] - becomes frozenset
|
41
45
|
|
42
46
|
# Function interface
|
43
47
|
@runtime_checkable
|
@@ -132,6 +136,7 @@ async with ctx.scope("dev-context", dev_config):
|
|
132
136
|
|
133
137
|
```python
|
134
138
|
from typing import Protocol, Any, runtime_checkable
|
139
|
+
from collections.abc import Sequence
|
135
140
|
from uuid import UUID, uuid4
|
136
141
|
from datetime import datetime
|
137
142
|
from haiway import State, ctx
|
@@ -142,6 +147,7 @@ class Note(State):
|
|
142
147
|
content: str
|
143
148
|
created_at: datetime
|
144
149
|
updated_at: datetime
|
150
|
+
tags: Sequence[str] = () # Use Sequence, not list
|
145
151
|
|
146
152
|
@runtime_checkable
|
147
153
|
class NoteCreating(Protocol):
|
@@ -232,6 +238,8 @@ async def run_notes_app():
|
|
232
238
|
- Keep states immutable
|
233
239
|
- Use .updated() for state variants
|
234
240
|
- Define defaults for optional values
|
241
|
+
- Use abstract collection types: Sequence[T] not list[T], Mapping[K,V] not dict[K,V], Set[T] not set[T]
|
242
|
+
- Lists become tuples, sets become frozensets for immutability
|
235
243
|
|
236
244
|
3. **Function Implementations**
|
237
245
|
- Access context through ctx.state()
|
@@ -257,3 +265,24 @@ async def run_notes_app():
|
|
257
265
|
- Define custom error types in types.py
|
258
266
|
- Handle errors explicitly at appropriate levels
|
259
267
|
- Maintain immutability in error cases
|
268
|
+
|
269
|
+
## State Type Validation
|
270
|
+
|
271
|
+
State classes validate all supported Python types:
|
272
|
+
|
273
|
+
- **Basic Types**: int, str, bool, float, bytes, None
|
274
|
+
- **Collection Types**:
|
275
|
+
- Sequence[T] (converts lists to tuples)
|
276
|
+
- Mapping[K,V] (keeps as dict)
|
277
|
+
- Set[T] (converts to frozenset)
|
278
|
+
- tuple[T, ...] (fixed/variable length)
|
279
|
+
- **Special Types**: UUID, datetime, date, time, timedelta, timezone, Path, re.Pattern
|
280
|
+
- **Union Types**: str | None, int | float
|
281
|
+
- **Literal Types**: Literal["a", "b", "c"]
|
282
|
+
- **Enum Types**: Standard Enum and StrEnum classes
|
283
|
+
- **Callable Types**: Function types and Protocol interfaces
|
284
|
+
- **TypedDict**: Structure validation with Required/NotRequired
|
285
|
+
- **Nested State**: Recursive validation including generics
|
286
|
+
- **Any Type**: Accepts any value without validation
|
287
|
+
|
288
|
+
**Critical Rule**: Always use abstract collection types (Sequence, Mapping, Set) instead of concrete types (list, dict, set) to ensure immutability and proper validation.
|
@@ -0,0 +1,282 @@
|
|
1
|
+
## State Management
|
2
|
+
|
3
|
+
Haiway's state management system is built around the `State` class, which provides immutable, type-safe data structures with validation. Unlike traditional mutable objects, State instances cannot be modified after creation, ensuring predictable behavior, especially in concurrent environments. This guide explains how to effectively use the State class to manage your application's data.
|
4
|
+
|
5
|
+
### Defining State Classes
|
6
|
+
|
7
|
+
State classes are defined by subclassing the `State` base class and declaring typed attributes:
|
8
|
+
|
9
|
+
```python
|
10
|
+
from haiway import State
|
11
|
+
from uuid import UUID
|
12
|
+
from datetime import datetime
|
13
|
+
|
14
|
+
class User(State):
|
15
|
+
id: UUID
|
16
|
+
name: str
|
17
|
+
email: str | None = None
|
18
|
+
created_at: datetime
|
19
|
+
```
|
20
|
+
|
21
|
+
Key features of State class definitions:
|
22
|
+
|
23
|
+
- **Type Annotations**: All attributes must have type annotations
|
24
|
+
- **Optional Attributes**: Provide default values for optional attributes
|
25
|
+
- **Immutability**: All instances are immutable once created
|
26
|
+
- **Validation**: Values are validated against their type annotations at creation time
|
27
|
+
|
28
|
+
### Creating State Instances
|
29
|
+
|
30
|
+
State instances are created using standard constructor syntax:
|
31
|
+
|
32
|
+
```python
|
33
|
+
from uuid import uuid4
|
34
|
+
from datetime import datetime
|
35
|
+
|
36
|
+
user = User(
|
37
|
+
id=uuid4(),
|
38
|
+
name="Alice Smith",
|
39
|
+
created_at=datetime.now()
|
40
|
+
)
|
41
|
+
```
|
42
|
+
|
43
|
+
All required attributes must be provided, and all values are validated against their type annotations. If a value fails validation, an exception will be raised.
|
44
|
+
|
45
|
+
### Immutability and Updates
|
46
|
+
|
47
|
+
State instances are immutable, so you cannot modify them directly:
|
48
|
+
|
49
|
+
```python
|
50
|
+
user.name = "Bob" # Raises AttributeError
|
51
|
+
```
|
52
|
+
|
53
|
+
Instead, create new instances with updated values using the `updated` method:
|
54
|
+
|
55
|
+
```python
|
56
|
+
updated_user = user.updated(name="Bob Smith")
|
57
|
+
```
|
58
|
+
|
59
|
+
This creates a new instance with the updated value, leaving the original instance unchanged. The `updated` method accepts keyword arguments for any attributes you want to change.
|
60
|
+
|
61
|
+
### Path-Based Updates
|
62
|
+
|
63
|
+
For nested updates, you can use the path-based `updating` method:
|
64
|
+
|
65
|
+
```python
|
66
|
+
class Address(State):
|
67
|
+
street: str
|
68
|
+
city: str
|
69
|
+
postal_code: str
|
70
|
+
|
71
|
+
class Contact(State):
|
72
|
+
name: str
|
73
|
+
address: Address
|
74
|
+
|
75
|
+
# Create an instance
|
76
|
+
contact = Contact(
|
77
|
+
name="Alice",
|
78
|
+
address=Address(
|
79
|
+
street="123 Main St",
|
80
|
+
city="Springfield",
|
81
|
+
postal_code="12345"
|
82
|
+
)
|
83
|
+
)
|
84
|
+
|
85
|
+
# Update a nested value using path syntax
|
86
|
+
updated_contact = contact.updating(Contact._.address.city, "New City")
|
87
|
+
```
|
88
|
+
|
89
|
+
The `Class._.attribute` syntax creates an `AttributePath` that can be used to update nested attributes.
|
90
|
+
|
91
|
+
### Generic State Classes
|
92
|
+
|
93
|
+
State supports generic type parameters, allowing you to create reusable containers:
|
94
|
+
|
95
|
+
```python
|
96
|
+
from typing import Generic, TypeVar
|
97
|
+
|
98
|
+
T = TypeVar('T')
|
99
|
+
|
100
|
+
class Container(State, Generic[T]):
|
101
|
+
value: T
|
102
|
+
|
103
|
+
# Create specialized instances
|
104
|
+
int_container = Container[int](value=42)
|
105
|
+
str_container = Container[str](value="hello")
|
106
|
+
```
|
107
|
+
|
108
|
+
The type parameter is enforced during validation:
|
109
|
+
|
110
|
+
```python
|
111
|
+
int_container.updated(value="string") # Raises TypeError
|
112
|
+
```
|
113
|
+
|
114
|
+
### Conversion to Dictionary
|
115
|
+
|
116
|
+
You can convert a State instance to a dictionary using the `to_mapping` method:
|
117
|
+
|
118
|
+
```python
|
119
|
+
user_dict = user.to_mapping()
|
120
|
+
# {"id": UUID('...'), "name": "Alice Smith", "email": None, "created_at": datetime(...)}
|
121
|
+
|
122
|
+
# For nested conversion
|
123
|
+
user_dict = user.to_mapping(recursive=True)
|
124
|
+
```
|
125
|
+
|
126
|
+
This is useful for serialization or when you need to work with plain dictionaries.
|
127
|
+
|
128
|
+
### Type Validation
|
129
|
+
|
130
|
+
State classes perform thorough type validation for all supported Python types:
|
131
|
+
|
132
|
+
- **Basic Types**: int, str, bool, float, bytes
|
133
|
+
- **Container Types**:
|
134
|
+
- **Sequence[T]**: Use `Sequence[T]` instead of `list[T]` - converted to immutable tuples
|
135
|
+
- **Mapping[K, V]**: Use `Mapping[K, V]` instead of `dict[K, V]` - remains as dict
|
136
|
+
- **Set[T]**: Use `Set[T]` instead of `set[T]` - converted to immutable frozensets
|
137
|
+
- **tuple[T, ...]**: Fixed or variable-length tuples
|
138
|
+
- **Special Types**: UUID, datetime, date, time, timedelta, timezone, Path, re.Pattern
|
139
|
+
- **Union Types**: str | None, int | float
|
140
|
+
- **Literal Types**: Literal["a", "b", "c"]
|
141
|
+
- **Enum Types**: Standard Enum and StrEnum classes
|
142
|
+
- **Callable Types**: Function types and Protocol interfaces
|
143
|
+
- **TypedDict**: Validates structure with Required/NotRequired fields
|
144
|
+
- **Nested State Classes**: Validates recursively including generic State types
|
145
|
+
- **Any Type**: Accepts any value without validation
|
146
|
+
|
147
|
+
#### Important Typing Requirements
|
148
|
+
|
149
|
+
**Always use abstract collection types instead of concrete types:**
|
150
|
+
|
151
|
+
```python
|
152
|
+
# ✅ Correct - Use abstract types
|
153
|
+
from collections.abc import Sequence, Mapping, Set
|
154
|
+
|
155
|
+
class Config(State):
|
156
|
+
items: Sequence[str] # Not list[str]
|
157
|
+
data: Mapping[str, int] # Not dict[str, int]
|
158
|
+
tags: Set[str] # Not set[str]
|
159
|
+
|
160
|
+
# ✅ Lists are converted to tuples (immutable)
|
161
|
+
config = Config(
|
162
|
+
items=["a", "b", "c"], # Becomes ("a", "b", "c")
|
163
|
+
data={"key": 1}, # Remains {"key": 1}
|
164
|
+
tags={"tag1", "tag2"} # Becomes frozenset({"tag1", "tag2"})
|
165
|
+
)
|
166
|
+
|
167
|
+
# ❌ Incorrect - Don't use concrete types
|
168
|
+
class BadConfig(State):
|
169
|
+
items: list[str] # Will cause validation errors
|
170
|
+
data: dict[str, int] # Will cause validation errors
|
171
|
+
tags: set[str] # Will cause validation errors
|
172
|
+
```
|
173
|
+
|
174
|
+
This requirement ensures immutability and type safety within the State system.
|
175
|
+
|
176
|
+
### Best Practices
|
177
|
+
|
178
|
+
1. **Use Immutability**: Embrace the immutable nature of State - never try to modify instances.
|
179
|
+
2. **Make Small States**: Keep State classes focused on a single concern.
|
180
|
+
3. **Provide Defaults**: Use default values for optional attributes to make creation easier.
|
181
|
+
4. **Use Type Annotations**: Always provide accurate type annotations for all attributes.
|
182
|
+
5. **Consistent Updates**: Always use `updated` or `updating` methods for changes.
|
183
|
+
6. **Composition**: Compose complex states from simpler ones.
|
184
|
+
|
185
|
+
### Example: Complex State Management
|
186
|
+
|
187
|
+
Here's a more complete example showing complex state management:
|
188
|
+
|
189
|
+
```python
|
190
|
+
from haiway import State
|
191
|
+
from uuid import UUID, uuid4
|
192
|
+
from datetime import datetime
|
193
|
+
from collections.abc import Sequence
|
194
|
+
|
195
|
+
class Address(State):
|
196
|
+
street: str
|
197
|
+
city: str
|
198
|
+
country: str = "USA"
|
199
|
+
|
200
|
+
class Contact(State):
|
201
|
+
email: str
|
202
|
+
phone: str | None = None
|
203
|
+
|
204
|
+
class User(State):
|
205
|
+
id: UUID
|
206
|
+
name: str
|
207
|
+
address: Address
|
208
|
+
contact: Contact
|
209
|
+
roles: Sequence[str] = ()
|
210
|
+
active: bool = True
|
211
|
+
created_at: datetime
|
212
|
+
updated_at: datetime
|
213
|
+
|
214
|
+
# Create an instance
|
215
|
+
user = User(
|
216
|
+
id=uuid4(),
|
217
|
+
name="Alice Smith",
|
218
|
+
address=Address(
|
219
|
+
street="123 Main St",
|
220
|
+
city="Springfield",
|
221
|
+
),
|
222
|
+
contact=Contact(
|
223
|
+
email="alice@example.com",
|
224
|
+
),
|
225
|
+
created_at=datetime.now(),
|
226
|
+
updated_at=datetime.now(),
|
227
|
+
)
|
228
|
+
|
229
|
+
# Update a simple attribute
|
230
|
+
user1 = user.updated(name="Alice Johnson")
|
231
|
+
|
232
|
+
# Update a nested attribute
|
233
|
+
user2 = user.updating(User._.address.city, "New City")
|
234
|
+
|
235
|
+
# Update multiple attributes
|
236
|
+
user3 = user.updated(
|
237
|
+
active=False,
|
238
|
+
updated_at=datetime.now(),
|
239
|
+
)
|
240
|
+
|
241
|
+
# Update a nested attribute directly
|
242
|
+
new_address = user.address.updated(street="456 Oak Ave")
|
243
|
+
user4 = user.updated(address=new_address)
|
244
|
+
|
245
|
+
# Chain updates
|
246
|
+
user5 = user.updated(name="Bob").updating(User._.contact.phone, "555-1234")
|
247
|
+
```
|
248
|
+
|
249
|
+
### Performance Considerations
|
250
|
+
|
251
|
+
While State instances are immutable, creating new instances for updates has minimal overhead as only the changed paths are reconstructed. The validation system is optimized to be fast for typical use cases.
|
252
|
+
|
253
|
+
For high-performance scenarios:
|
254
|
+
- Keep State classes relatively small and focused
|
255
|
+
- Consider using path-based updates for nested changes
|
256
|
+
- If needed, batch multiple updates into a single `updated` call
|
257
|
+
|
258
|
+
### Integration with Haiway Context
|
259
|
+
|
260
|
+
State classes are designed to work seamlessly with Haiway's context system:
|
261
|
+
|
262
|
+
```python
|
263
|
+
from haiway import ctx, State
|
264
|
+
|
265
|
+
class AppConfig(State):
|
266
|
+
debug: bool = False
|
267
|
+
log_level: str = "INFO"
|
268
|
+
|
269
|
+
async def main():
|
270
|
+
# Provide state to context
|
271
|
+
async with ctx.scope("main", AppConfig(debug=True)):
|
272
|
+
# Access state from context
|
273
|
+
config = ctx.state(AppConfig)
|
274
|
+
|
275
|
+
# Create updated state in nested context
|
276
|
+
async with ctx.scope("debug", config.updated(log_level="DEBUG")):
|
277
|
+
# Use updated state
|
278
|
+
debug_config = ctx.state(AppConfig)
|
279
|
+
assert debug_config.log_level == "DEBUG"
|
280
|
+
```
|
281
|
+
|
282
|
+
This pattern enables effective dependency injection and state propagation throughout your application.
|
@@ -1 +1 @@
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="105" time="1.822" timestamp="2025-05-22T14:52:29.443450+00:00" hostname="pkrvmf6wy0o8zjz"><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.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.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_process_concurrently" name="test_processes_all_elements" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_processes_elements_concurrently" time="0.302" /><testcase classname="tests.test_process_concurrently" name="test_handles_empty_source" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_propagates_handler_exceptions" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_ignores_handler_exceptions_when_configured" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_handles_source_exception" time="0.001" /><testcase classname="tests.test_process_concurrently" name="test_cancels_running_tasks_on_cancellation" time="0.101" /><testcase classname="tests.test_process_concurrently" name="test_respects_concurrency_limit" time="0.202" /><testcase classname="tests.test_process_concurrently" name="test_processes_elements_from_queue" time="0.052" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments" time="0.002" /><testcase classname="tests.test_state" name="test_basic_initializes_with_defaults" time="0.001" /><testcase classname="tests.test_state" name="test_basic_equals_checks_properties" time="0.000" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments_and_defaults" time="0.000" /><testcase classname="tests.test_state" name="test_parametrized_initializes_with_proper_parameters" time="0.001" /><testcase classname="tests.test_state" name="test_nested_initializes_with_proper_arguments" time="0.001" /><testcase classname="tests.test_state" name="test_dict_skips_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_initialization_allows_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_generic_subtypes_validation" time="0.002" /><testcase classname="tests.test_state" name="test_copying_leaves_same_object" time="0.000" /><testcase classname="tests.test_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>
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="126" time="1.863" timestamp="2025-05-23T15:40:02.453645+00:00" hostname="pkrvmf6wy0o8zjz"><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.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_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.201" /><testcase classname="tests.test_process_concurrently" name="test_processes_elements_from_queue" time="0.052" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments" time="0.002" /><testcase classname="tests.test_state" name="test_basic_initializes_with_defaults" time="0.001" /><testcase classname="tests.test_state" name="test_basic_equals_checks_properties" time="0.000" /><testcase classname="tests.test_state" name="test_basic_initializes_with_arguments_and_defaults" time="0.000" /><testcase classname="tests.test_state" name="test_parametrized_initializes_with_proper_parameters" time="0.001" /><testcase classname="tests.test_state" name="test_nested_initializes_with_proper_arguments" time="0.001" /><testcase classname="tests.test_state" name="test_dict_skips_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_initialization_allows_missing_properties" time="0.000" /><testcase classname="tests.test_state" name="test_generic_subtypes_validation" time="0.002" /><testcase classname="tests.test_state" name="test_copying_leaves_same_object" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_basic_types" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_none_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_missing_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_literal_type" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_enum_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_sequence_type" time="0.001" /><testcase classname="tests.test_state_validation" name="test_validator_set_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_mapping_type" time="0.000" /><testcase classname="tests.test_state_validation" name="test_validator_tuple_type" time="0.000" /><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.21.
|
8
|
+
version = "0.21.2"
|
9
9
|
readme = "README.md"
|
10
10
|
maintainers = [
|
11
11
|
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
|
@@ -302,7 +302,7 @@ class ctx:
|
|
302
302
|
def scope(
|
303
303
|
label: str,
|
304
304
|
/,
|
305
|
-
*state: State,
|
305
|
+
*state: State | None,
|
306
306
|
disposables: Disposables | Iterable[Disposable] | None = None,
|
307
307
|
task_group: TaskGroup | None = None,
|
308
308
|
observability: Observability | Logger | None = None,
|
@@ -316,7 +316,7 @@ class ctx:
|
|
316
316
|
label: str
|
317
317
|
name of the scope context
|
318
318
|
|
319
|
-
*state: State |
|
319
|
+
*state: State | None
|
320
320
|
state propagated within the scope context, will be merged with current state by\
|
321
321
|
replacing current with provided on conflict.
|
322
322
|
|
@@ -355,14 +355,14 @@ class ctx:
|
|
355
355
|
return ScopeContext(
|
356
356
|
label=label,
|
357
357
|
task_group=task_group,
|
358
|
-
state=state,
|
358
|
+
state=tuple(element for element in state if element is not None),
|
359
359
|
disposables=resolved_disposables,
|
360
360
|
observability=observability,
|
361
361
|
)
|
362
362
|
|
363
363
|
@staticmethod
|
364
364
|
def updated(
|
365
|
-
*state: State,
|
365
|
+
*state: State | None,
|
366
366
|
) -> StateContext:
|
367
367
|
"""
|
368
368
|
Update scope context with given state. When called within an existing context\
|
@@ -370,7 +370,7 @@ class ctx:
|
|
370
370
|
|
371
371
|
Parameters
|
372
372
|
----------
|
373
|
-
*state: State
|
373
|
+
*state: State | None
|
374
374
|
state propagated within the updated scope context, will be merged with current if any\
|
375
375
|
by replacing current with provided on conflict
|
376
376
|
|
@@ -380,7 +380,7 @@ class ctx:
|
|
380
380
|
state part of context object intended to enter context manager with it
|
381
381
|
"""
|
382
382
|
|
383
|
-
return StateContext.updated(state)
|
383
|
+
return StateContext.updated(element for element in state if element is not None)
|
384
384
|
|
385
385
|
@staticmethod
|
386
386
|
def spawn[Result, **Arguments](
|