haiway 0.20.1__tar.gz → 0.21.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haiway-0.20.1 → haiway-0.21.1}/Makefile +1 -1
- {haiway-0.20.1 → haiway-0.21.1}/PKG-INFO +8 -4
- {haiway-0.20.1 → haiway-0.21.1}/README.md +4 -0
- {haiway-0.20.1 → haiway-0.21.1}/guidelines/functionalities.md +46 -80
- haiway-0.21.1/guidelines/llms.txt +259 -0
- {haiway-0.20.1 → haiway-0.21.1}/guidelines/packages.md +37 -40
- haiway-0.21.1/junit/test-results.xml +1 -0
- {haiway-0.20.1 → haiway-0.21.1}/pyproject.toml +4 -4
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/__init__.py +0 -22
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/__init__.py +1 -2
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/asynchrony.py +22 -121
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/throttling.py +33 -9
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/requirement.py +1 -1
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/types/__init__.py +0 -2
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/mimic.py +2 -2
- haiway-0.21.1/tests/test_attribute_requirement.py +121 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_state.py +2 -2
- {haiway-0.20.1 → haiway-0.21.1}/uv.lock +201 -201
- haiway-0.20.1/junit/test-results.xml +0 -1
- haiway-0.20.1/src/haiway/types/frozen.py +0 -21
- {haiway-0.20.1 → haiway-0.21.1}/.github/workflows/ci.yml +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/.github/workflows/publish.yml +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/.gitignore +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/LICENSE +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/config/pre-push +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/.dockerignore +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/Dockerfile +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/Makefile +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/README.md +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/config/.env.example +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/config/unit.json +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/docker-compose.yml +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/pyproject.toml +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/__int__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/todos/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/todos/config.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/todos/state.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/todos/types.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/features/todos/user_tasks.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/postgres/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/postgres/client.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/postgres/config.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/postgres/state.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/integrations/postgres/types.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/__main__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/postgres/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/postgres/execution.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/postgres/migration_0.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/migrations/postgres/types.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/__main__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/application.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/config.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/middlewares/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/middlewares/context.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/routes/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/routes/technical.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/server/routes/todos.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/user_tasks/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/user_tasks/config.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/user_tasks/postgres.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/user_tasks/state.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/src/solutions/user_tasks/types.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/examples/fastAPI/uv.lock +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/access.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/disposables.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/identifier.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/observability.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/state.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/tasks.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/context/types.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/caching.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/concurrent.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/observability.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/retries.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/timeouted.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/helpers/tracing.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/opentelemetry/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/opentelemetry/observability.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/py.typed +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/attributes.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/path.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/structure.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/state/validation.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/types/default.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/types/missing.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/always.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/collections.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/env.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/formatting.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/freezing.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/logs.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/noop.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/queue.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/src/haiway/utils/stream.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/__init__.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_async_queue.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_async_stream.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_attribute_path.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_auto_retry.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_cache.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_context.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_process_concurrently.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/tests/test_streaming.py +0 -0
- {haiway-0.20.1 → haiway-0.21.1}/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.21.1
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Project-URL: Homepage, https://miquido.com
|
6
6
|
Project-URL: Repository, https://github.com/miquido/haiway.git
|
@@ -42,13 +42,17 @@ Requires-Dist: pytest-cov~=6.1; extra == 'dev'
|
|
42
42
|
Requires-Dist: pytest~=8.3; extra == 'dev'
|
43
43
|
Requires-Dist: ruff~=0.11; extra == 'dev'
|
44
44
|
Provides-Extra: opentelemetry
|
45
|
-
Requires-Dist: opentelemetry-api; extra == 'opentelemetry'
|
46
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc; extra == 'opentelemetry'
|
47
|
-
Requires-Dist: opentelemetry-sdk; extra == 'opentelemetry'
|
45
|
+
Requires-Dist: opentelemetry-api~=1.33; extra == 'opentelemetry'
|
46
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc~=1.33; extra == 'opentelemetry'
|
47
|
+
Requires-Dist: opentelemetry-sdk~=1.33; extra == 'opentelemetry'
|
48
48
|
Description-Content-Type: text/markdown
|
49
49
|
|
50
50
|
# 🚗 haiway 🚕 🚚 🚙
|
51
51
|
|
52
|
+

|
53
|
+

|
54
|
+

|
55
|
+
|
52
56
|
haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context vars, haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
|
53
57
|
|
54
58
|
## 🖥️ Install
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# 🚗 haiway 🚕 🚚 🚙
|
2
2
|
|
3
|
+

|
4
|
+

|
5
|
+

|
6
|
+
|
3
7
|
haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context vars, haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
|
4
8
|
|
5
9
|
## 🖥️ Install
|
@@ -1,25 +1,25 @@
|
|
1
1
|
## Functionalities
|
2
2
|
|
3
|
-
|
3
|
+
Haiway is a framework designed to facilitate the development of applications using the functional programming paradigm combined with structured concurrency concepts. Unlike traditional object-oriented frameworks, Haiway emphasizes immutability, pure functions, and context-based state management, enabling developers to build scalable and maintainable applications. By leveraging context managers combined with context variables, Haiway ensures safe state propagation in concurrent environments and simplifies dependency injection through function implementation propagation.
|
4
4
|
|
5
|
-
### Functional
|
5
|
+
### Functional Basics
|
6
6
|
|
7
|
-
Functional programming centers around creating pure functions - functions that have no side effects and rely solely on their inputs to produce outputs. This approach promotes predictability, easier testing, and better modularity. While Python is inherently multi-paradigm and not strictly functional,
|
7
|
+
Functional programming centers around creating pure functions - functions that have no side effects and rely solely on their inputs to produce outputs. This approach promotes predictability, easier testing, and better modularity. While Python is inherently multi-paradigm and not strictly functional, Haiway encourages adopting functional principles where feasible to enhance code clarity and reliability.
|
8
8
|
|
9
9
|
Key functional concepts:
|
10
10
|
- Immutability: Data structures are immutable, preventing unintended side effects.
|
11
11
|
- Pure Functions: Functions depend only on their inputs and produce outputs without altering external state.
|
12
12
|
- Higher-Order Functions: Functions that can take other functions as arguments or return them as results.
|
13
13
|
|
14
|
-
|
14
|
+
Haiway balances functional purity with Python's flexibility by allowing limited side effects when necessary, though minimizing them is recommended for maintainability.
|
15
15
|
|
16
|
-
Instead of preparing objects with internal state and methods,
|
16
|
+
Instead of preparing objects with internal state and methods, Haiway encourages creating structures containing sets of functions and providing state either through function arguments or contextually using execution scope state. Using explicit function arguments is the preferred method; however, some functionalities may benefit from contextual, broader accessible state.
|
17
17
|
|
18
|
-
### Preparing
|
18
|
+
### Preparing Functionalities
|
19
19
|
|
20
|
-
In
|
20
|
+
In Haiway, functionalities are modularized into two primary components: interfaces and implementations. This separation ensures clear contracts for functionalities, promoting modularity and ease of testing.
|
21
21
|
|
22
|
-
### Defining
|
22
|
+
### Defining Types
|
23
23
|
|
24
24
|
Interfaces define the public API of a functionality, specifying the data types and functions it exposes without detailing the underlying implementation. Preparing functionality starts from defining associated types - data structures and function types.
|
25
25
|
|
@@ -40,9 +40,9 @@ class FunctionSignature(Protocol):
|
|
40
40
|
|
41
41
|
In the example above, typing.Protocol is used to fully define the function signature, along with a custom structure serving as its argument. Function type names should emphasize the nature of their operations by using continuous tense adjectives, such as 'ElementCreating' or 'ValueLoading.'
|
42
42
|
|
43
|
-
### Defining
|
43
|
+
### Defining State
|
44
44
|
|
45
|
-
State represents the immutable data required by functionalities. It is propagated through contexts to maintain consistency and support dependency injection.
|
45
|
+
State represents the immutable data required by functionalities. It is propagated through contexts to maintain consistency and support dependency injection. Haiway comes with a helpful base class `State` which utilizes dataclass-like transform combined with runtime type checking and immutability.
|
46
46
|
|
47
47
|
```python
|
48
48
|
# state.py
|
@@ -60,7 +60,7 @@ class Functionality(State):
|
|
60
60
|
|
61
61
|
This example shows a state required by the functionality as well as a container for holding function implementations. Both are intended to be propagated contextually to be accessible throughout the application and possibly altered when needed.
|
62
62
|
|
63
|
-
### Defining
|
63
|
+
### Defining Implementation
|
64
64
|
|
65
65
|
Implementations provide concrete behavior for the defined interfaces, ensuring that they conform to the specified contracts.
|
66
66
|
|
@@ -81,34 +81,15 @@ async def function_implementation(argument: FunctionArgument) -> None:
|
|
81
81
|
# Factory function to instantiate the Functionality with its implementation
|
82
82
|
def functionality_implementation() -> Functionality:
|
83
83
|
return Functionality(function=function_implementation)
|
84
|
-
|
85
84
|
```
|
86
85
|
|
87
|
-
In the example above, function_implementation is
|
86
|
+
In the example above, function_implementation is a concrete implementation of the previously declared function, and functionality_implementation is a factory method suitable for creating a full implementation of the Functionality.
|
88
87
|
|
89
|
-
Alternatively, instead of providing a factory method, some implementations may allow
|
88
|
+
Alternatively, instead of providing a factory method, some implementations may allow defining default values within state. This approach is also valid to implement and allows skipping explicit definitions of state by leveraging automatically created defaults.
|
90
89
|
|
91
|
-
###
|
90
|
+
### Classmethod Calls
|
92
91
|
|
93
|
-
Calls act as intermediaries that invoke the function implementations within the appropriate context. This abstraction simplifies access to functionalities by hiding non-essential details and access to various required components.
|
94
|
-
|
95
|
-
```python
|
96
|
-
# calls.py
|
97
|
-
from my_functionality.types import FunctionArgument
|
98
|
-
from my_functionality.state import FunctionalityState, Functionality
|
99
|
-
from haiway import ctx
|
100
|
-
|
101
|
-
# Call function that invokes the functionality within the current context
|
102
|
-
async def function_call(argument: FunctionArgument) -> None:
|
103
|
-
# Invoke the function implementation from the contextual state
|
104
|
-
await ctx.state(Functionality).function(argument=argument)
|
105
|
-
```
|
106
|
-
|
107
|
-
The example above shows a simple proxy call that accesses the required contextual details of the implementation.
|
108
|
-
|
109
|
-
### Classmethod calls
|
110
|
-
|
111
|
-
When possible, preferred way of defining calls is to put them within the functionality state type as class methods. This approach allows easier access to desired functions and improves egonomy over the free functions access.
|
92
|
+
Calls act as intermediaries that invoke the function implementations within the appropriate context. This abstraction simplifies access to functionalities by hiding non-essential details and access to various required components. When possible, the preferred way of defining calls is to put them within the functionality state type as class methods. This approach allows easier access to desired functions and improves ergonomics over the free functions access.
|
112
93
|
|
113
94
|
```python
|
114
95
|
# state.py - revisited
|
@@ -116,22 +97,22 @@ When possible, preferred way of defining calls is to put them within the functio
|
|
116
97
|
class Functionality(State):
|
117
98
|
# define function call as a class method
|
118
99
|
@classmethod
|
119
|
-
def function_call(cls, argument: FunctionArgument) -> None:
|
100
|
+
async def function_call(cls, argument: FunctionArgument) -> None:
|
120
101
|
# Invoke the function implementation from the contextual state
|
121
102
|
await ctx.state(cls).function(argument=argument)
|
122
103
|
|
123
104
|
function: FunctionSignature
|
124
105
|
```
|
125
106
|
|
126
|
-
|
107
|
+
Keeping it within the functionality interface class allows streamlined access and better control over the calls.
|
127
108
|
|
128
|
-
### Using
|
109
|
+
### Using Implementation
|
129
110
|
|
130
|
-
To utilize the defined functionalities within an application, contexts must be established to provide the necessary state and implementations. Below is an example of how to integrate
|
111
|
+
To utilize the defined functionalities within an application, contexts must be established to provide the necessary state and implementations. Below is an example of how to integrate Haiway functionalities into an application.
|
131
112
|
|
132
113
|
```python
|
133
114
|
# application.py
|
134
|
-
from my_functionality import functionality_implementation,
|
115
|
+
from my_functionality import functionality_implementation, Functionality, FunctionalityState
|
135
116
|
from haiway import ctx
|
136
117
|
|
137
118
|
# Example application function utilizing the functionality
|
@@ -143,42 +124,16 @@ async def application_function(argument: FunctionArgument) -> None:
|
|
143
124
|
FunctionalityState(parameter="ExampleParameter")
|
144
125
|
):
|
145
126
|
# Execute the functionality using the predefined helper
|
146
|
-
await function_call(FunctionArgument(value="SampleValue"))
|
127
|
+
await Functionality.function_call(FunctionArgument(value="SampleValue"))
|
147
128
|
```
|
148
129
|
|
149
130
|
Going through all of these layers may seem unnecessary at first, but in the long term, it creates a robust, modular system that is easy to manage and work with.
|
150
131
|
|
151
|
-
### Flexible arguments
|
152
|
-
|
153
|
-
When a function defined within the functionality is intended to utilize contextual state it might be beneficial to allow it to take any additional keyword arguments that would be used to update contextual state within the implementation. This approach allows to update contextual state locally, only for a single function call without propagating the change deeply into call tree.
|
154
|
-
|
155
|
-
```python
|
156
|
-
...
|
157
|
-
|
158
|
-
# function signature allowing extra arguments
|
159
|
-
@runtime_checkable
|
160
|
-
class FunctionSignature(Protocol):
|
161
|
-
async def __call__(self, argument: FunctionArgument, **extra: Any) -> None: ...
|
162
|
-
|
163
|
-
...
|
164
|
-
|
165
|
-
# function implementation utilizing extra arguments to update local state
|
166
|
-
async def function_implementation(argument: FunctionArgument, **extra: Any) -> None:
|
167
|
-
# Retrieve 'parameter' from the current context's state updated with extra arguments
|
168
|
-
parameter = ctx.state(FunctionalityState).updated(**extra).parameter
|
169
|
-
...
|
170
|
-
|
171
|
-
```
|
172
|
-
|
173
|
-
haiway `State` types allow to create object copies with updated attributes by using `updated` method. The updated object is a swallow copy of the original object allowing to change the state only in local context without affecting other users of that state. When there are no changes to be applied no copy is created. Additionally the `updated` method skips all unnecessary arguments to handle described case without additional code required. However, there is always risk of name collisions, this approach should be carefully considered to avoid any potential issues.
|
174
|
-
|
175
|
-
Additionally, this approach allows to utilize additional, implementation specific arguments, hidden beneatch the usual function signatures.
|
176
|
-
|
177
132
|
## Example
|
178
133
|
|
179
|
-
To better understand the whole idea we can take a look at more concrete example of a notes management functionality:
|
134
|
+
To better understand the whole idea, we can take a look at a more concrete example of a notes management functionality:
|
180
135
|
|
181
|
-
First we define some basic types required by our functionality - management functions signatures and the note itself.
|
136
|
+
First, we define some basic types required by our functionality - management functions signatures and the note itself.
|
182
137
|
|
183
138
|
```python
|
184
139
|
# notes/types.py
|
@@ -208,9 +163,9 @@ Then we can define the state holding our functions and defining some context.
|
|
208
163
|
|
209
164
|
```python
|
210
165
|
# notes/state.py
|
211
|
-
from notes.types import NoteCreating, NoteUpdating
|
212
|
-
|
213
|
-
from
|
166
|
+
from notes.types import NoteCreating, NoteUpdating, Note
|
167
|
+
from haiway import State, ctx
|
168
|
+
from typing import Any
|
214
169
|
|
215
170
|
# State providing contextual state for the functionality
|
216
171
|
class NotesDirectory(State):
|
@@ -219,48 +174,59 @@ class NotesDirectory(State):
|
|
219
174
|
# State encapsulating the functionality with its interface
|
220
175
|
class Notes(State):
|
221
176
|
# Call of note creation function
|
177
|
+
@classmethod
|
222
178
|
async def create_note(cls, content: str, **extra: Any) -> Note:
|
223
179
|
# Invoke the function implementation from the contextual state
|
224
|
-
await ctx.state(cls).
|
180
|
+
return await ctx.state(cls).creating(content=content, **extra)
|
225
181
|
|
226
182
|
# Call of note update function
|
183
|
+
@classmethod
|
227
184
|
async def update_note(cls, note: Note, **extra: Any) -> None:
|
228
185
|
# Invoke the function implementation from the contextual state
|
229
|
-
await ctx.state(cls).
|
186
|
+
await ctx.state(cls).updating(note=note, **extra)
|
230
187
|
|
231
188
|
# instance variables holding function implementations
|
232
189
|
creating: NoteCreating
|
233
190
|
updating: NoteUpdating
|
234
191
|
```
|
235
192
|
|
236
|
-
That allows us to provide a concrete implementation. Note that `extra` arguments would allow us to alter the `NotesDirectory` path for a single function call only. This might be very important feature in some cases i.e
|
193
|
+
That allows us to provide a concrete implementation. Note that `extra` arguments would allow us to alter the `NotesDirectory` path for a single function call only. This might be a very important feature in some cases, i.e., when using recursive function calls.
|
237
194
|
|
238
195
|
```python
|
239
196
|
# notes/files.py
|
240
|
-
from notes.types import
|
197
|
+
from notes.types import Note
|
241
198
|
from notes.state import Notes, NotesDirectory
|
242
199
|
from haiway import ctx
|
200
|
+
from typing import Any
|
201
|
+
from uuid import uuid4
|
202
|
+
from datetime import datetime
|
243
203
|
|
244
204
|
# Implementation of note creation function
|
245
205
|
async def file_note_create(content: str, **extra: Any) -> Note:
|
246
206
|
# Retrieve path from the current context's state, updated if needed
|
247
207
|
path = ctx.state(NotesDirectory).updated(**extra).path
|
248
208
|
# Store note in file within the path...
|
209
|
+
note = Note(
|
210
|
+
identifier=uuid4(),
|
211
|
+
last_update=datetime.now(),
|
212
|
+
content=content
|
213
|
+
)
|
214
|
+
# Implementation for storing note in file...
|
215
|
+
return note
|
249
216
|
|
250
217
|
# Implementation of note update function
|
251
218
|
async def file_note_update(note: Note, **extra: Any) -> None:
|
252
219
|
# Retrieve path from the current context's state, updated if needed
|
253
220
|
path = ctx.state(NotesDirectory).updated(**extra).path
|
254
221
|
# Update the note...
|
255
|
-
|
222
|
+
# Implementation for updating note in file...
|
256
223
|
|
257
224
|
# Factory function to instantiate the Notes utilizing files implementation
|
258
225
|
def file_notes() -> Notes:
|
259
226
|
return Notes(
|
260
|
-
|
261
|
-
|
227
|
+
creating=file_note_create,
|
228
|
+
updating=file_note_update,
|
262
229
|
)
|
263
|
-
|
264
230
|
```
|
265
231
|
|
266
232
|
You can now use the whole functionality by defining implementation for execution context and calling functionality methods.
|
@@ -273,5 +239,5 @@ from haiway import ctx
|
|
273
239
|
# prepare the context with desired implementation
|
274
240
|
async with ctx.scope("example", file_notes(), NotesDirectory(path="./examples/note.txt")):
|
275
241
|
# and access its methods contextually
|
276
|
-
await Notes.create_note("This was an example of
|
242
|
+
await Notes.create_note("This was an example of Haiway")
|
277
243
|
```
|
@@ -0,0 +1,259 @@
|
|
1
|
+
## Package Structure
|
2
|
+
|
3
|
+
Organize your project into five distinct package types:
|
4
|
+
|
5
|
+
```
|
6
|
+
src/
|
7
|
+
├── commons/ # Shared utilities, types, extensions
|
8
|
+
├── integrations/ # Third-party service connections
|
9
|
+
│ ├── integration_a/
|
10
|
+
│ └── integration_b/
|
11
|
+
├── solutions/ # Low-level utilities
|
12
|
+
│ ├── solution_a/
|
13
|
+
│ └── solution_b/
|
14
|
+
├── features/ # High-level functionalities
|
15
|
+
│ ├── feature_a/
|
16
|
+
│ └── feature_b/
|
17
|
+
└── entrypoints/ # Application entry points
|
18
|
+
├── entrypoint_a/
|
19
|
+
└── entrypoint_b/
|
20
|
+
```
|
21
|
+
|
22
|
+
Each functionality package should contain:
|
23
|
+
- `__init__.py`: Exports public API only
|
24
|
+
- `types.py`: Type definitions using Protocol and State
|
25
|
+
- `state.py`: State declarations with classmethod helpers
|
26
|
+
- `config.py`: Configuration constants
|
27
|
+
|
28
|
+
## Implementing Types
|
29
|
+
|
30
|
+
Define interfaces using Protocol and data using State:
|
31
|
+
|
32
|
+
```python
|
33
|
+
from typing import Protocol, Any, runtime_checkable
|
34
|
+
from haiway import State
|
35
|
+
|
36
|
+
# Data structure
|
37
|
+
class UserData(State):
|
38
|
+
id: str
|
39
|
+
name: str
|
40
|
+
email: str | None = None
|
41
|
+
|
42
|
+
# Function interface
|
43
|
+
@runtime_checkable
|
44
|
+
class UserFetching(Protocol):
|
45
|
+
async def __call__(self, id: str) -> UserData: ...
|
46
|
+
```
|
47
|
+
|
48
|
+
## State Management
|
49
|
+
|
50
|
+
Define states for configuration and functionality containers:
|
51
|
+
|
52
|
+
```python
|
53
|
+
from haiway import State
|
54
|
+
from .types import UserFetching, UserData
|
55
|
+
|
56
|
+
# Configuration state
|
57
|
+
class UserServiceConfig(State):
|
58
|
+
api_url: str = "https://api.example.com"
|
59
|
+
timeout_seconds: int = 30
|
60
|
+
|
61
|
+
# Functionality container
|
62
|
+
class UserService(State):
|
63
|
+
# Function implementations
|
64
|
+
fetching: UserFetching
|
65
|
+
|
66
|
+
# Class method interface
|
67
|
+
@classmethod
|
68
|
+
async def fetch_user(cls, id: str) -> UserData:
|
69
|
+
return await ctx.state(cls).fetching(id)
|
70
|
+
```
|
71
|
+
|
72
|
+
## Implementing Functions
|
73
|
+
|
74
|
+
Create concrete implementations and factory methods:
|
75
|
+
|
76
|
+
```python
|
77
|
+
from haiway import ctx
|
78
|
+
from .types import UserData
|
79
|
+
from .state import UserService, UserServiceConfig
|
80
|
+
|
81
|
+
# Concrete implementation
|
82
|
+
async def http_user_fetching(id: str) -> UserData:
|
83
|
+
config = ctx.state(UserServiceConfig)
|
84
|
+
# Implementation using config.api_url
|
85
|
+
# ...
|
86
|
+
return UserData(id=id, name="Example User")
|
87
|
+
|
88
|
+
# Factory function providing implementation
|
89
|
+
def HTTPUserService() -> UserService:
|
90
|
+
return UserService(fetching=http_user_fetching)
|
91
|
+
```
|
92
|
+
|
93
|
+
## Using Context Scopes
|
94
|
+
|
95
|
+
Establish context with state and implementations:
|
96
|
+
|
97
|
+
```python
|
98
|
+
from haiway import ctx
|
99
|
+
from .state import UserService, UserServiceConfig
|
100
|
+
from .implementation import HTTPUserService
|
101
|
+
|
102
|
+
async def main():
|
103
|
+
# Set up execution context
|
104
|
+
async with ctx.scope(
|
105
|
+
"main",
|
106
|
+
HTTPUserService(),
|
107
|
+
UserServiceConfig(api_url="https://custom-api.example.com")
|
108
|
+
):
|
109
|
+
# Use functionality through class methods
|
110
|
+
user = await UserService.fetch_user("user-123")
|
111
|
+
print(f"Found user: {user.name}")
|
112
|
+
```
|
113
|
+
|
114
|
+
## Managing State Updates
|
115
|
+
|
116
|
+
Create state variants without mutation:
|
117
|
+
|
118
|
+
```python
|
119
|
+
# Current state
|
120
|
+
config = ctx.state(UserServiceConfig)
|
121
|
+
|
122
|
+
# Create new instance with updated values
|
123
|
+
dev_config = config.updated(api_url="https://dev-api.example.com")
|
124
|
+
|
125
|
+
# Use in context
|
126
|
+
async with ctx.scope("dev-context", dev_config):
|
127
|
+
# Operations will use dev_config
|
128
|
+
pass
|
129
|
+
```
|
130
|
+
|
131
|
+
## Complete Example: Notes Manager
|
132
|
+
|
133
|
+
```python
|
134
|
+
from typing import Protocol, Any, runtime_checkable
|
135
|
+
from uuid import UUID, uuid4
|
136
|
+
from datetime import datetime
|
137
|
+
from haiway import State, ctx
|
138
|
+
|
139
|
+
# Types
|
140
|
+
class Note(State):
|
141
|
+
id: UUID
|
142
|
+
content: str
|
143
|
+
created_at: datetime
|
144
|
+
updated_at: datetime
|
145
|
+
|
146
|
+
@runtime_checkable
|
147
|
+
class NoteCreating(Protocol):
|
148
|
+
async def __call__(self, content: str) -> Note: ...
|
149
|
+
|
150
|
+
@runtime_checkable
|
151
|
+
class NoteFinding(Protocol):
|
152
|
+
async def __call__(self, id: UUID) -> Note | None: ...
|
153
|
+
|
154
|
+
# State
|
155
|
+
class NotesConfig(State):
|
156
|
+
storage_path: str = "./notes"
|
157
|
+
|
158
|
+
class NotesService(State):
|
159
|
+
creating: NoteCreating
|
160
|
+
finding: NoteFinding
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
async def create_note(cls, content: str) -> Note:
|
164
|
+
return await ctx.state(cls).creating(content)
|
165
|
+
|
166
|
+
@classmethod
|
167
|
+
async def find_note(cls, id: UUID) -> Note | None:
|
168
|
+
return await ctx.state(cls).finding(id)
|
169
|
+
|
170
|
+
# Implementation
|
171
|
+
async def file_note_creating(content: str) -> Note:
|
172
|
+
now = datetime.now()
|
173
|
+
note = Note(
|
174
|
+
id=uuid4(),
|
175
|
+
content=content,
|
176
|
+
created_at=now,
|
177
|
+
updated_at=now
|
178
|
+
)
|
179
|
+
|
180
|
+
config = ctx.state(NotesConfig)
|
181
|
+
# Save note to file at config.storage_path
|
182
|
+
# ...
|
183
|
+
|
184
|
+
return note
|
185
|
+
|
186
|
+
async def file_note_finding(id: UUID) -> Note | None:
|
187
|
+
config = ctx.state(NotesConfig)
|
188
|
+
# Find note in files at config.storage_path
|
189
|
+
# ...
|
190
|
+
|
191
|
+
# Return found note or None
|
192
|
+
# For demonstration:
|
193
|
+
return Note(
|
194
|
+
id=id,
|
195
|
+
content="Example note content",
|
196
|
+
created_at=datetime.now(),
|
197
|
+
updated_at=datetime.now()
|
198
|
+
)
|
199
|
+
|
200
|
+
# Factory
|
201
|
+
def FileNotesService() -> NotesService:
|
202
|
+
return NotesService(
|
203
|
+
creating=file_note_creating,
|
204
|
+
finding=file_note_finding
|
205
|
+
)
|
206
|
+
|
207
|
+
# Usage
|
208
|
+
async def run_notes_app():
|
209
|
+
async with ctx.scope(
|
210
|
+
"notes-app",
|
211
|
+
FileNotesService(),
|
212
|
+
NotesConfig(storage_path="./my_notes")
|
213
|
+
):
|
214
|
+
# Create a note
|
215
|
+
new_note = await NotesService.create_note("This is a test note")
|
216
|
+
print(f"Created note with ID: {new_note.id}")
|
217
|
+
|
218
|
+
# Find the note
|
219
|
+
found_note = await NotesService.find_note(new_note.id)
|
220
|
+
if found_note:
|
221
|
+
print(f"Found note: {found_note.content}")
|
222
|
+
```
|
223
|
+
|
224
|
+
## Best Practices
|
225
|
+
|
226
|
+
1. **Type Definitions**
|
227
|
+
- Use Protocol for function interfaces
|
228
|
+
- Use State for data structures
|
229
|
+
- Apply runtime_checkable for better type safety
|
230
|
+
|
231
|
+
2. **State Management**
|
232
|
+
- Keep states immutable
|
233
|
+
- Use .updated() for state variants
|
234
|
+
- Define defaults for optional values
|
235
|
+
|
236
|
+
3. **Function Implementations**
|
237
|
+
- Access context through ctx.state()
|
238
|
+
- Keep functions pure when possible
|
239
|
+
- Return new objects instead of modifying existing ones
|
240
|
+
|
241
|
+
4. **Class Method Interface**
|
242
|
+
- Define classmethods for cleaner API
|
243
|
+
- Use cls parameter to access current state
|
244
|
+
- Follow consistent naming (verb_noun pattern)
|
245
|
+
|
246
|
+
5. **Context Usage**
|
247
|
+
- Use descriptive context names
|
248
|
+
- Group related states in single context
|
249
|
+
- Keep context hierarchy shallow
|
250
|
+
|
251
|
+
6. **Package Organization**
|
252
|
+
- Separate interfaces from implementations
|
253
|
+
- Export only public API from __init__.py
|
254
|
+
- Group related functionality in dedicated packages
|
255
|
+
|
256
|
+
7. **Error Handling**
|
257
|
+
- Define custom error types in types.py
|
258
|
+
- Handle errors explicitly at appropriate levels
|
259
|
+
- Maintain immutability in error cases
|