nodnod 1.0.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.
- nodnod-1.0.0/.claude/settings.local.json +14 -0
- nodnod-1.0.0/.codecov.yml +9 -0
- nodnod-1.0.0/.github/actions/install-dependencies/action.yml +21 -0
- nodnod-1.0.0/.github/workflows/tests.yml +50 -0
- nodnod-1.0.0/.gitignore +6 -0
- nodnod-1.0.0/PKG-INFO +15 -0
- nodnod-1.0.0/bug.py +26 -0
- nodnod-1.0.0/bug2.py +15 -0
- nodnod-1.0.0/docs/index.md +0 -0
- nodnod-1.0.0/main.py +172 -0
- nodnod-1.0.0/main2.py +79 -0
- nodnod-1.0.0/main3.py +107 -0
- nodnod-1.0.0/nodnod/__init__.py +31 -0
- nodnod-1.0.0/nodnod/agent/__init__.py +4 -0
- nodnod-1.0.0/nodnod/agent/base.py +15 -0
- nodnod-1.0.0/nodnod/agent/event_loop/__init__.py +0 -0
- nodnod-1.0.0/nodnod/agent/event_loop/agent.py +144 -0
- nodnod-1.0.0/nodnod/agent/event_loop/coroutine.py +128 -0
- nodnod-1.0.0/nodnod/agent/layer/__init__.py +0 -0
- nodnod-1.0.0/nodnod/agent/layer/agent.py +68 -0
- nodnod-1.0.0/nodnod/agent/layer/steps.py +26 -0
- nodnod-1.0.0/nodnod/builder/__init__.py +0 -0
- nodnod-1.0.0/nodnod/builder/build_parallels.py +40 -0
- nodnod-1.0.0/nodnod/builder/build_queue.py +30 -0
- nodnod-1.0.0/nodnod/builder/validate_graph.py +11 -0
- nodnod-1.0.0/nodnod/compose.py +74 -0
- nodnod-1.0.0/nodnod/error.py +49 -0
- nodnod-1.0.0/nodnod/interface/__init__.py +29 -0
- nodnod-1.0.0/nodnod/interface/agent_from_node.py +16 -0
- nodnod-1.0.0/nodnod/interface/composable.py +11 -0
- nodnod-1.0.0/nodnod/interface/compose_one.py +28 -0
- nodnod-1.0.0/nodnod/interface/create_result_node.py +44 -0
- nodnod-1.0.0/nodnod/interface/data.py +13 -0
- nodnod-1.0.0/nodnod/interface/either.py +49 -0
- nodnod-1.0.0/nodnod/interface/generic.py +81 -0
- nodnod-1.0.0/nodnod/interface/inject.py +31 -0
- nodnod-1.0.0/nodnod/interface/is_node.py +23 -0
- nodnod-1.0.0/nodnod/interface/node_constructor.py +65 -0
- nodnod-1.0.0/nodnod/interface/node_from_function.py +186 -0
- nodnod-1.0.0/nodnod/interface/option_node.py +81 -0
- nodnod-1.0.0/nodnod/interface/polymorphic.py +76 -0
- nodnod-1.0.0/nodnod/interface/result_node.py +26 -0
- nodnod-1.0.0/nodnod/interface/scalar.py +55 -0
- nodnod-1.0.0/nodnod/interface/union_node.py +94 -0
- nodnod-1.0.0/nodnod/node.py +249 -0
- nodnod-1.0.0/nodnod/node.pyi +32 -0
- nodnod-1.0.0/nodnod/scope.py +100 -0
- nodnod-1.0.0/nodnod/utils/__init__.py +0 -0
- nodnod-1.0.0/nodnod/utils/aio.py +16 -0
- nodnod-1.0.0/nodnod/utils/call.py +23 -0
- nodnod-1.0.0/nodnod/utils/create_node.py +54 -0
- nodnod-1.0.0/nodnod/utils/generator.py +21 -0
- nodnod-1.0.0/nodnod/utils/injection.py +30 -0
- nodnod-1.0.0/nodnod/utils/is_type.py +38 -0
- nodnod-1.0.0/nodnod/utils/misc.py +8 -0
- nodnod-1.0.0/nodnod/utils/prepare_values.py +18 -0
- nodnod-1.0.0/nodnod/utils/repr_type.py +8 -0
- nodnod-1.0.0/nodnod/utils/resolve_signature.py +155 -0
- nodnod-1.0.0/nodnod/utils/type_args.py +29 -0
- nodnod-1.0.0/nodnod/value.py +42 -0
- nodnod-1.0.0/pyproject.toml +118 -0
- nodnod-1.0.0/readme.md +5 -0
- nodnod-1.0.0/tests/conftest.py +0 -0
- nodnod-1.0.0/tests/test_agent_extended.py +23 -0
- nodnod-1.0.0/tests/test_aio_extended.py +61 -0
- nodnod-1.0.0/tests/test_build_queue.py +65 -0
- nodnod-1.0.0/tests/test_compose.py +112 -0
- nodnod-1.0.0/tests/test_compose_one.py +91 -0
- nodnod-1.0.0/tests/test_coroutine.py +258 -0
- nodnod-1.0.0/tests/test_create_node_from_composable.py +345 -0
- nodnod-1.0.0/tests/test_create_result_node_extended.py +48 -0
- nodnod-1.0.0/tests/test_either.py +90 -0
- nodnod-1.0.0/tests/test_either_extended.py +40 -0
- nodnod-1.0.0/tests/test_error.py +63 -0
- nodnod-1.0.0/tests/test_generators.py +48 -0
- nodnod-1.0.0/tests/test_generic.py +296 -0
- nodnod-1.0.0/tests/test_get_injection_type.py +26 -0
- nodnod-1.0.0/tests/test_initialize_forward_refs.py +161 -0
- nodnod-1.0.0/tests/test_injection.py +107 -0
- nodnod-1.0.0/tests/test_is_type_extended.py +50 -0
- nodnod-1.0.0/tests/test_node_basic.py +193 -0
- nodnod-1.0.0/tests/test_node_constructor.py +174 -0
- nodnod-1.0.0/tests/test_node_extended.py +218 -0
- nodnod-1.0.0/tests/test_node_from_function.py +288 -0
- nodnod-1.0.0/tests/test_node_repr.py +30 -0
- nodnod-1.0.0/tests/test_option_node_extended.py +32 -0
- nodnod-1.0.0/tests/test_option_result_nodes.py +135 -0
- nodnod-1.0.0/tests/test_polymorphic.py +87 -0
- nodnod-1.0.0/tests/test_polymorphic_extended.py +41 -0
- nodnod-1.0.0/tests/test_prepare_values.py +39 -0
- nodnod-1.0.0/tests/test_resolve_signature.py +82 -0
- nodnod-1.0.0/tests/test_resolve_signature_extended.py +144 -0
- nodnod-1.0.0/tests/test_result_node_extended.py +69 -0
- nodnod-1.0.0/tests/test_scalar.py +24 -0
- nodnod-1.0.0/tests/test_scope_extended.py +102 -0
- nodnod-1.0.0/tests/test_scope_inject.py +36 -0
- nodnod-1.0.0/tests/test_scope_repr.py +68 -0
- nodnod-1.0.0/tests/test_type_args.py +83 -0
- nodnod-1.0.0/tests/test_union_nodes.py +56 -0
- nodnod-1.0.0/tests/test_union_nodes_extended.py +75 -0
- nodnod-1.0.0/tests/test_value.py +25 -0
- nodnod-1.0.0/tests/test_value_extended.py +56 -0
- nodnod-1.0.0/uv.lock +259 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Install uv and dependencies
|
|
2
|
+
|
|
3
|
+
inputs:
|
|
4
|
+
python-version:
|
|
5
|
+
description: "Python version."
|
|
6
|
+
required: false
|
|
7
|
+
default: "3.13"
|
|
8
|
+
|
|
9
|
+
runs:
|
|
10
|
+
using: composite
|
|
11
|
+
steps:
|
|
12
|
+
- uses: hynek/setup-cached-uv@v2
|
|
13
|
+
with:
|
|
14
|
+
cache-suffix: -${{ inputs.python-version }}-cache
|
|
15
|
+
cache-dependency-path: "**/uv.lock"
|
|
16
|
+
|
|
17
|
+
- name: "Install dependencies"
|
|
18
|
+
run: |
|
|
19
|
+
uv venv --python ${{ inputs.python-version }}
|
|
20
|
+
uv sync --all-groups --dev
|
|
21
|
+
shell: bash
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: tests and coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
lock-file:
|
|
8
|
+
name: "Lock uv"
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version:
|
|
13
|
+
- "3.13"
|
|
14
|
+
- "3.14"
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: asottile/workflows/.github/actions/fast-checkout@v1.10.0
|
|
18
|
+
|
|
19
|
+
- uses: ./.github/actions/install-dependencies
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- run: uv lock --locked
|
|
24
|
+
|
|
25
|
+
tests:
|
|
26
|
+
name: "Tests"
|
|
27
|
+
needs: lock-file
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
python-version:
|
|
32
|
+
- "3.13"
|
|
33
|
+
- "3.14"
|
|
34
|
+
|
|
35
|
+
steps:
|
|
36
|
+
- uses: asottile/workflows/.github/actions/fast-checkout@v1.10.0
|
|
37
|
+
|
|
38
|
+
- uses: ./.github/actions/install-dependencies
|
|
39
|
+
with:
|
|
40
|
+
python-version: ${{ matrix.python-version }}
|
|
41
|
+
|
|
42
|
+
- name: Run tests with coverage
|
|
43
|
+
run: |
|
|
44
|
+
uv run pytest --cov --cov-report=xml --cov-report=term-missing
|
|
45
|
+
|
|
46
|
+
- name: Upload coverage to Codecov
|
|
47
|
+
uses: codecov/codecov-action@v5
|
|
48
|
+
with:
|
|
49
|
+
files: ./coverage.xml
|
|
50
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
nodnod-1.0.0/.gitignore
ADDED
nodnod-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nodnod
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Programming in Python empowered with Nodes DI, implements efficient nodes computation agents and dependency scoping
|
|
5
|
+
Author: timoniq
|
|
6
|
+
Requires-Python: <4.0,>=3.13
|
|
7
|
+
Requires-Dist: kungfu-fp>=1.0.0
|
|
8
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# NodNod
|
|
12
|
+
|
|
13
|
+
🧑🧑🧒🧒 Programming in Python enhanced with Nodes
|
|
14
|
+
|
|
15
|
+
[](https://codecov.io/gh/luwqz1/nodnod)
|
nodnod-1.0.0/bug.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from nodnod.interface.node_from_function import Scope, create_node_from_function, inject_externals
|
|
4
|
+
from nodnod.interface.node_from_function import create_agent_from_node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def func(a: int | None, b: int) -> None:
|
|
8
|
+
print(a)
|
|
9
|
+
print(b)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def main():
|
|
13
|
+
node = create_node_from_function(func)
|
|
14
|
+
agent = create_agent_from_node(node)
|
|
15
|
+
print(node.__dependencies__)
|
|
16
|
+
print(node.__externals__)
|
|
17
|
+
node.__dependencies__ = set()
|
|
18
|
+
scope = Scope(detail="test")
|
|
19
|
+
|
|
20
|
+
async with scope:
|
|
21
|
+
inject_externals(scope, dict(a=1, b=2))
|
|
22
|
+
await agent.run(scope, mapped_scopes={})
|
|
23
|
+
print(scope)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
asyncio.run(main())
|
nodnod-1.0.0/bug2.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from nodnod.node import initialize_forward_refs
|
|
4
|
+
from nodnod.interface.node_from_function import create_node_from_function
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def func(a: A) -> None: ... # ForwardRef(A)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class A:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
create_node_from_function(func)
|
|
File without changes
|
nodnod-1.0.0/main.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import kungfu
|
|
6
|
+
|
|
7
|
+
from nodnod import (
|
|
8
|
+
DataNode,
|
|
9
|
+
EventLoopAgent,
|
|
10
|
+
Node,
|
|
11
|
+
NodeConstructor,
|
|
12
|
+
NodeError,
|
|
13
|
+
Scalar,
|
|
14
|
+
Scope,
|
|
15
|
+
SequentialEither,
|
|
16
|
+
Value,
|
|
17
|
+
case,
|
|
18
|
+
generic_node,
|
|
19
|
+
polymorphic,
|
|
20
|
+
scalar_node,
|
|
21
|
+
)
|
|
22
|
+
from nodnod.utils.prepare_values import prepare_values
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@generic_node
|
|
26
|
+
#@dataclasses.dataclass
|
|
27
|
+
class Lolik[T: (str, int), *Ts, X: str | int](DataNode, abstract=True):
|
|
28
|
+
def __init__(self, t: T, x: X):
|
|
29
|
+
self.t = t
|
|
30
|
+
self.x = x
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
async def __compose__(cls, t: type[T], y: type[X]) -> typing.Self:
|
|
34
|
+
return cls(t("11"), y("22"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@generic_node
|
|
38
|
+
class TypeArgs[*Ts]:
|
|
39
|
+
def __init__(self, type_args: tuple[typing.Unpack[Ts]]) -> None:
|
|
40
|
+
self.type_args = type_args
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def __compose__(cls, type_args: tuple[typing.Unpack[Ts]]) -> typing.Self:
|
|
44
|
+
return cls(type_args)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Interface:
|
|
48
|
+
def get_lol(self) -> str:
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MyInterface:
|
|
53
|
+
def get_lol(self) -> str:
|
|
54
|
+
return "megalol"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@scalar_node
|
|
58
|
+
class A:
|
|
59
|
+
@classmethod
|
|
60
|
+
async def __compose__(cls):
|
|
61
|
+
yield int(11)
|
|
62
|
+
|
|
63
|
+
@scalar_node
|
|
64
|
+
class Bitchnode:
|
|
65
|
+
@classmethod
|
|
66
|
+
async def __compose__(cls):
|
|
67
|
+
raise NodeError("dang")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@scalar_node
|
|
71
|
+
class B(Node[int]):
|
|
72
|
+
@classmethod
|
|
73
|
+
async def __compose__(cls):
|
|
74
|
+
yield 5
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AorB(SequentialEither):
|
|
78
|
+
__either__ = (A, B)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@scalar_node
|
|
82
|
+
@polymorphic[int]
|
|
83
|
+
class MyInt:
|
|
84
|
+
@case
|
|
85
|
+
def from_a(cls, a: A) -> int:
|
|
86
|
+
return a + 8
|
|
87
|
+
|
|
88
|
+
@case
|
|
89
|
+
def from_b(cls, b: B) -> int:
|
|
90
|
+
return b
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclasses.dataclass
|
|
94
|
+
class C(DataNode):
|
|
95
|
+
x: float
|
|
96
|
+
y: float
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def __compose__(cls, aorb: AorB):
|
|
100
|
+
print(aorb)
|
|
101
|
+
print("calculating c")
|
|
102
|
+
print(aorb.value)
|
|
103
|
+
return cls(1.2, aorb.value / 2)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@scalar_node
|
|
107
|
+
class Lol:
|
|
108
|
+
@classmethod
|
|
109
|
+
def __compose__(cls, i: Interface) -> str:
|
|
110
|
+
return i.get_lol()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class MyNode(Node):
|
|
114
|
+
def __init__(self):
|
|
115
|
+
self.bro = 3
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@scalar_node
|
|
119
|
+
class LOL:
|
|
120
|
+
@classmethod
|
|
121
|
+
def __compose__(
|
|
122
|
+
cls,
|
|
123
|
+
x: Lol,
|
|
124
|
+
mi: MyInt,
|
|
125
|
+
opt: kungfu.Option[A],
|
|
126
|
+
ar: kungfu.Result[Bitchnode, Exception],
|
|
127
|
+
) -> str:
|
|
128
|
+
return x.upper() * mi + "d"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@scalar_node
|
|
132
|
+
class Jackpot:
|
|
133
|
+
@classmethod
|
|
134
|
+
def __compose__(cls) -> int:
|
|
135
|
+
return 777
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MultiplyBy(NodeConstructor):
|
|
139
|
+
def __init__(self, multiplier: int = 5) -> None:
|
|
140
|
+
self.multiplier = multiplier
|
|
141
|
+
|
|
142
|
+
def __compose__(self, jackpot: Jackpot) -> int:
|
|
143
|
+
return 123 * self.multiplier + jackpot
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@scalar_node
|
|
147
|
+
class Sum:
|
|
148
|
+
@classmethod
|
|
149
|
+
def __compose__(cls, a: Scalar[int, MultiplyBy], b: Scalar[int, MultiplyBy[10]]) -> int:
|
|
150
|
+
return a + b
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def main():
|
|
154
|
+
agent = EventLoopAgent.build({TypeArgs[int, str], LOL, C, Lolik[str, float, str], Lolik[int, str, int], Sum}) # type: ignore
|
|
155
|
+
|
|
156
|
+
global_scope = Scope(detail="global")
|
|
157
|
+
global_scope.push(Value(Interface, MyInterface()))
|
|
158
|
+
|
|
159
|
+
async with global_scope.create_child("local") as scope:
|
|
160
|
+
await agent.run(
|
|
161
|
+
local_scope=scope,
|
|
162
|
+
mapped_scopes={A: global_scope},
|
|
163
|
+
)
|
|
164
|
+
print(prepare_values(scope.merge()))
|
|
165
|
+
|
|
166
|
+
await global_scope.close()
|
|
167
|
+
|
|
168
|
+
# Firstly closes C
|
|
169
|
+
# then A and B (any order because they are from the same layer of parallels)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
asyncio.run(main())
|
nodnod-1.0.0/main2.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import typing
|
|
3
|
+
import kungfu
|
|
4
|
+
|
|
5
|
+
from nodnod import Node
|
|
6
|
+
from nodnod.agent import EventLoopAgent
|
|
7
|
+
from nodnod.interface.scalar import scalar_node
|
|
8
|
+
from nodnod.node import is_type
|
|
9
|
+
from nodnod.scope import Scope
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def self_hook(cls: type[Node], dep_name: str, dep_type: type[typing.Any]) -> kungfu.Pulse[str]:
|
|
13
|
+
if is_type(dep_type, typing.Self):
|
|
14
|
+
print(cls, dep_name, dep_type)
|
|
15
|
+
return kungfu.Ok()
|
|
16
|
+
return kungfu.Error("cannot process")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NodeConstructor(Node, abstract=True):
|
|
20
|
+
__args__ = ()
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def __construct__(cls, *args: typing.Any) -> typing.Self:
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
def __class_getitem__(cls, item: typing.Any | tuple[typing.Any, ...], /) -> typing.Any:
|
|
27
|
+
data = cls.__construct__(*((item,) if not isinstance(item, tuple) else item))
|
|
28
|
+
node = type(
|
|
29
|
+
cls.__name__,
|
|
30
|
+
(cls,),
|
|
31
|
+
dict(
|
|
32
|
+
__module__=cls.__module__,
|
|
33
|
+
__args__=data,
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
setattr(node, "__type__", node)
|
|
37
|
+
return node
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@scalar_node
|
|
41
|
+
class A(Node):
|
|
42
|
+
@classmethod
|
|
43
|
+
def __compose__(cls) -> int:
|
|
44
|
+
return 123
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Lolik(NodeConstructor):
|
|
48
|
+
def __init__(self, x: type[str | int]) -> None:
|
|
49
|
+
self.x = x
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def __construct__(cls, x: type[str | int] = str) -> typing.Self:
|
|
53
|
+
return cls(x)
|
|
54
|
+
|
|
55
|
+
def __compose__(self, a: A):
|
|
56
|
+
print(a)
|
|
57
|
+
return self.x
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@scalar_node
|
|
61
|
+
class Boba:
|
|
62
|
+
@classmethod
|
|
63
|
+
def __compose__(cls, lolik: Lolik[int], lolik2: Lolik[str]) -> int:
|
|
64
|
+
print("lolik", lolik.x, "lolik2", lolik2.x)
|
|
65
|
+
return lolik
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
agent = EventLoopAgent.build({Boba})
|
|
70
|
+
scope = Scope(detail="test")
|
|
71
|
+
|
|
72
|
+
async with scope:
|
|
73
|
+
await agent.run(scope, {})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ("NodeConstructor",)
|
nodnod-1.0.0/main3.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import datetime
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
import kungfu
|
|
7
|
+
|
|
8
|
+
from nodnod import (
|
|
9
|
+
EventLoopAgent,
|
|
10
|
+
Externals,
|
|
11
|
+
NodeError,
|
|
12
|
+
Scope,
|
|
13
|
+
create_agent_from_node,
|
|
14
|
+
create_node_from_function,
|
|
15
|
+
inject_externals,
|
|
16
|
+
scalar_node,
|
|
17
|
+
)
|
|
18
|
+
from nodnod.interface.compose_one import compose_one
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclasses.dataclass
|
|
22
|
+
class User:
|
|
23
|
+
id: int
|
|
24
|
+
email: kungfu.Option[str]
|
|
25
|
+
last_active: datetime.datetime
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@scalar_node
|
|
29
|
+
class UserId:
|
|
30
|
+
@classmethod
|
|
31
|
+
def __compose__(cls, user: User) -> int:
|
|
32
|
+
return user.id
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@scalar_node
|
|
36
|
+
class Email(str):
|
|
37
|
+
def validate_email(self):
|
|
38
|
+
if "@" not in self:
|
|
39
|
+
raise NodeError("Email is in wrong format")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def __compose__(cls, user: User) -> str:
|
|
43
|
+
email = cls(user.email.expect(NodeError("User has no email")))
|
|
44
|
+
email.validate_email()
|
|
45
|
+
return email
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass
|
|
49
|
+
class UserSource:
|
|
50
|
+
user: User
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def __compose__(cls, user: User) -> typing.Self:
|
|
54
|
+
return cls(user)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@scalar_node
|
|
58
|
+
class EmailProvider:
|
|
59
|
+
@classmethod
|
|
60
|
+
def __compose__(cls, email: Email) -> str:
|
|
61
|
+
_, provider = email.split("@", 1)
|
|
62
|
+
if not provider:
|
|
63
|
+
raise NodeError("Email has no provider")
|
|
64
|
+
return provider
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@scalar_node
|
|
68
|
+
class SinceActive:
|
|
69
|
+
@classmethod
|
|
70
|
+
def __compose__(cls, user: User) -> datetime.timedelta:
|
|
71
|
+
return datetime.datetime.now() - user.last_active
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def handler(email_provider: EmailProvider, boba: str, lol: str, since_active: SinceActive, user: UserSource):
|
|
75
|
+
print(email_provider, boba, lol, since_active, user.user)
|
|
76
|
+
return "handler result"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
# Compile time
|
|
81
|
+
handler_node = create_node_from_function(handler)
|
|
82
|
+
agent = create_agent_from_node(handler_node, EventLoopAgent)
|
|
83
|
+
|
|
84
|
+
user = User(1, kungfu.Some("lol@skibidi.org"), datetime.datetime.now())
|
|
85
|
+
|
|
86
|
+
# Run time
|
|
87
|
+
async with Scope() as scope:
|
|
88
|
+
scope.inject(User, user)
|
|
89
|
+
inject_externals(scope, {"boba": "ahah", "lol": "omg"})
|
|
90
|
+
await agent.run(scope, {})
|
|
91
|
+
|
|
92
|
+
print(
|
|
93
|
+
await compose_one(
|
|
94
|
+
SinceActive,
|
|
95
|
+
{User: User(2, kungfu.Nothing(), datetime.datetime.now())},
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
print(
|
|
100
|
+
await compose_one(
|
|
101
|
+
create_node_from_function(handler),
|
|
102
|
+
{User: user, Externals: {"boba": "tea", "lol": "0"}},
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .agent import Agent, EventLoopAgent
|
|
2
|
+
from .error import NodeError
|
|
3
|
+
from .interface import *
|
|
4
|
+
from .node import Injection, Node, Scalar
|
|
5
|
+
from .scope import Scope
|
|
6
|
+
from .value import Value
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"Agent",
|
|
10
|
+
"ConcurrentEither",
|
|
11
|
+
"DataNode",
|
|
12
|
+
"Externals",
|
|
13
|
+
"EventLoopAgent",
|
|
14
|
+
"Injection",
|
|
15
|
+
"NodeConstructor",
|
|
16
|
+
"Node",
|
|
17
|
+
"NodeError",
|
|
18
|
+
"Scalar",
|
|
19
|
+
"ResultNode",
|
|
20
|
+
"Scope",
|
|
21
|
+
"SequentialEither",
|
|
22
|
+
"Value",
|
|
23
|
+
"create_node_from_function",
|
|
24
|
+
"compose_one",
|
|
25
|
+
"inject_externals",
|
|
26
|
+
"inject_internals",
|
|
27
|
+
"generic_node",
|
|
28
|
+
"case",
|
|
29
|
+
"polymorphic",
|
|
30
|
+
"scalar_node",
|
|
31
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from nodnod.node import Node
|
|
2
|
+
from nodnod.scope import Scope
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Agent:
|
|
7
|
+
@classmethod
|
|
8
|
+
def build(cls, nodes: set[type[Node]]) -> typing.Self:
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
def run(self, local_scope: Scope, mapped_scopes: dict[type[Node], Scope]) -> typing.Any:
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ("Agent",)
|
|
File without changes
|