funcnodes-core 2.3.2__tar.gz → 2.4.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.
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/CHANGELOG.md +11 -0
- funcnodes_core-2.4.0/CONTRIBUTING.md +49 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/PKG-INFO +26 -3
- funcnodes_core-2.4.0/README.md +25 -0
- funcnodes_core-2.4.0/context7.json +4 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/pyproject.toml +1 -1
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/io.py +11 -5
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_config.py +1 -1
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodeclass.py +174 -0
- funcnodes_core-2.4.0/tests/test_trigger_propagation_matrix.py +353 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/uv.lock +1 -1
- funcnodes_core-2.3.2/README.md +0 -2
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.coveragerc +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.flake8 +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.github/actions/install_package/action.yml +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.github/workflows/py_test.yml +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.github/workflows/version_publish_main.yml +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.gitignore +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/.pre-commit-config.yaml +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/LICENSE +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/MANIFEST.in +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/THIRD_PARTY_NOTICES.md +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/conftest.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/cz.toml +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/pytest.ini +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/_logging.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/_setup.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/config.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/data.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/datapath.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/decorator/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/eventmanager.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/exceptions.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/graph.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/grouping_logic.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/lib/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/lib/lib.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/lib/libfinder.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/lib/libparser.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/node.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/nodemaker.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/nodespace.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/testing.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/triggerstack.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/cache.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/data.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/deprecations.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/files.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/functions.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/modules.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/nodetqdm.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/nodeutils.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/plugins.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/plugins_types.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/saving.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/serialization.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/special_types.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/src/funcnodes_core/utils/wrapper.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/conftest.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_cache_utils.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_data.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_datapaths.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_decorator.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_eventmanager.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_exceptions.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_functions.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_graph.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_grouping_logic.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_lib.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_libfinder.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodeclassmixin.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodeio.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodemaker.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodespace.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_nodeutils.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_public_api.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_setup.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_triggering.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_triggerstack.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/__init__.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_datautils.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_deprecations.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_files.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_logging.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_modules.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_nodetqdm.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_plugins.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_plugins_types.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_saving.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_serialization.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_special_types.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_testing.py +0 -0
- {funcnodes_core-2.3.2 → funcnodes_core-2.4.0}/tests/test_utils/test_wrapper.py +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v2.4.0 (2026-03-17)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- **context**: add context7 configuration file with URL and public key
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
- **io**: preserve trigger propagation semantics across node chains
|
|
10
|
+
- **tests**: correct typo in test function name for clarity
|
|
11
|
+
|
|
1
12
|
## v2.3.2 (2025-12-24)
|
|
2
13
|
|
|
3
14
|
### Fix
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributing to funcnodes-core
|
|
2
|
+
|
|
3
|
+
This repository contains the **core runtime** (nodes, IO, nodespace, library, config, serialization).
|
|
4
|
+
|
|
5
|
+
## Development setup (Python)
|
|
6
|
+
|
|
7
|
+
Prereqs:
|
|
8
|
+
|
|
9
|
+
- Python **3.11+**
|
|
10
|
+
- `uv` (https://github.com/astral-sh/uv)
|
|
11
|
+
|
|
12
|
+
Recommended environment variables (keep caches/config local):
|
|
13
|
+
|
|
14
|
+
- `UV_CACHE_DIR=.cache/uv`
|
|
15
|
+
- `FUNCNODES_CONFIG_DIR=.funcnodes`
|
|
16
|
+
|
|
17
|
+
Install dev dependencies:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd funcnodes_core
|
|
21
|
+
UV_CACHE_DIR=.cache/uv uv sync --group dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Run tests:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd funcnodes_core
|
|
28
|
+
FUNCNODES_CONFIG_DIR=.funcnodes UV_CACHE_DIR=.cache/uv uv run pytest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Code style & hooks
|
|
32
|
+
|
|
33
|
+
Run pre-commit:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd funcnodes_core
|
|
37
|
+
UV_CACHE_DIR=.cache/uv uv run pre-commit install
|
|
38
|
+
UV_CACHE_DIR=.cache/uv uv run pre-commit run -a
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## TDD expectations
|
|
42
|
+
|
|
43
|
+
- Write tests first; add edge cases as separate tests.
|
|
44
|
+
- Avoid mocks unless simulating external resources.
|
|
45
|
+
|
|
46
|
+
## Pull requests
|
|
47
|
+
|
|
48
|
+
- Work on a feature branch (direct commits to `main`/`master`/`test` are blocked by pre-commit).
|
|
49
|
+
- Keep changes scoped: core is widely used across FuncNodes packages.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: funcnodes-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: core package for funcnodes
|
|
5
5
|
Project-URL: homepage, https://github.com/Linkdlab/funcnodes_core
|
|
6
6
|
Project-URL: source, https://github.com/Linkdlab/funcnodes_core
|
|
@@ -37,5 +37,28 @@ Requires-Dist: vulture>=2.14; extra == 'dev'
|
|
|
37
37
|
Requires-Dist: yappi>=1.6.10; extra == 'dev'
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# FuncNodes Core
|
|
41
|
+
|
|
42
|
+
This is the core package for **FuncNodes**, a flexible and modular framework for building and managing computational graphs.
|
|
43
|
+
|
|
44
|
+
`funcnodes_core` contains the fundamental logic for Nodes, NodeSpaces, and the event-driven architecture that powers the system.
|
|
45
|
+
|
|
46
|
+
> [!NOTE]
|
|
47
|
+
> If you are looking for the full application, including the web interface and worker management, please visit the main [FuncNodes repository](https://github.com/Linkdlab/FuncNodes) or install the `funcnodes` package.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
If you are developing a custom implementation or only need the core logic:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install funcnodes-core
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For the full experience:
|
|
58
|
+
```bash
|
|
59
|
+
pip install funcnodes
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
For detailed instructions and documentation, please visit the [FuncNodes Documentation](https://linkdlab.github.io/FuncNodes).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# FuncNodes Core
|
|
2
|
+
|
|
3
|
+
This is the core package for **FuncNodes**, a flexible and modular framework for building and managing computational graphs.
|
|
4
|
+
|
|
5
|
+
`funcnodes_core` contains the fundamental logic for Nodes, NodeSpaces, and the event-driven architecture that powers the system.
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> If you are looking for the full application, including the web interface and worker management, please visit the main [FuncNodes repository](https://github.com/Linkdlab/FuncNodes) or install the `funcnodes` package.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
If you are developing a custom implementation or only need the core logic:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install funcnodes-core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For the full experience:
|
|
19
|
+
```bash
|
|
20
|
+
pip install funcnodes
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
For detailed instructions and documentation, please visit the [FuncNodes Documentation](https://linkdlab.github.io/FuncNodes).
|
|
@@ -1039,10 +1039,11 @@ class NodeInput(NodeIO, Generic[NodeIOType]):
|
|
|
1039
1039
|
|
|
1040
1040
|
self.datapath = new_datapath
|
|
1041
1041
|
|
|
1042
|
+
resolved_does_trigger = does_trigger
|
|
1042
1043
|
if self.node is not None:
|
|
1043
|
-
if
|
|
1044
|
-
|
|
1045
|
-
if
|
|
1044
|
+
if resolved_does_trigger is None:
|
|
1045
|
+
resolved_does_trigger = self.does_trigger
|
|
1046
|
+
if resolved_does_trigger:
|
|
1046
1047
|
self.node.request_trigger()
|
|
1047
1048
|
|
|
1048
1049
|
for other in self._forwards:
|
|
@@ -1153,7 +1154,7 @@ class NodeInput(NodeIO, Generic[NodeIOType]):
|
|
|
1153
1154
|
self._forwards.add(other)
|
|
1154
1155
|
other.forwards_from(self, replace=replace)
|
|
1155
1156
|
|
|
1156
|
-
other.set_value(self.value)
|
|
1157
|
+
other.set_value(self.value, does_trigger=False)
|
|
1157
1158
|
|
|
1158
1159
|
return [
|
|
1159
1160
|
self.node.uuid if self.node else None,
|
|
@@ -1304,6 +1305,7 @@ class NodeOutput(NodeIO):
|
|
|
1304
1305
|
else:
|
|
1305
1306
|
datapath = None
|
|
1306
1307
|
# input_paths = []
|
|
1308
|
+
propagated_does_trigger = False if does_trigger is False else None
|
|
1307
1309
|
for other in self.connections:
|
|
1308
1310
|
# if self.node is not None:
|
|
1309
1311
|
# for input_path in input_paths:
|
|
@@ -1311,7 +1313,11 @@ class NodeOutput(NodeIO):
|
|
|
1311
1313
|
# else:
|
|
1312
1314
|
# datapath = None
|
|
1313
1315
|
|
|
1314
|
-
other.set_value(
|
|
1316
|
+
other.set_value(
|
|
1317
|
+
value,
|
|
1318
|
+
does_trigger=propagated_does_trigger,
|
|
1319
|
+
datapath=datapath,
|
|
1320
|
+
)
|
|
1315
1321
|
|
|
1316
1322
|
def post_connect(self, other: NodeIO):
|
|
1317
1323
|
"""Called after a connection is made.
|
|
@@ -37,6 +37,25 @@ class DummyNode(Node):
|
|
|
37
37
|
return input
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def make_counter_node(node_id: str, *, does_trigger: bool = True):
|
|
41
|
+
resolved_node_id = node_id
|
|
42
|
+
|
|
43
|
+
class CounterNode(Node):
|
|
44
|
+
node_id = resolved_node_id
|
|
45
|
+
value = NodeInput(id="value", type=int, does_trigger=does_trigger)
|
|
46
|
+
output = NodeOutput(id="output", type=int)
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs):
|
|
49
|
+
super().__init__(*args, pretrigger_delay=0.0, **kwargs)
|
|
50
|
+
self.call_count = 0
|
|
51
|
+
|
|
52
|
+
async def func(self, value: int):
|
|
53
|
+
self.call_count += 1
|
|
54
|
+
self.outputs["output"].value = value
|
|
55
|
+
|
|
56
|
+
return CounterNode
|
|
57
|
+
|
|
58
|
+
|
|
40
59
|
@funcnodes_test
|
|
41
60
|
async def test_nodeclass_initialization():
|
|
42
61
|
with pytest.raises(TypeError):
|
|
@@ -128,6 +147,161 @@ async def test_trigger_stack():
|
|
|
128
147
|
assert not trigger_stack.done()
|
|
129
148
|
|
|
130
149
|
|
|
150
|
+
@funcnodes_test
|
|
151
|
+
async def test_forwarded_input_respects_target_does_trigger_flag():
|
|
152
|
+
ForwardSourceNode = make_counter_node("forward_source_node_test")
|
|
153
|
+
ForwardTargetNode = make_counter_node(
|
|
154
|
+
"forward_target_node_test", does_trigger=False
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
node_a = ForwardSourceNode()
|
|
158
|
+
node_b = ForwardTargetNode()
|
|
159
|
+
node_a.inputs["value"].connect(node_b.inputs["value"])
|
|
160
|
+
|
|
161
|
+
node_a.inputs["value"].value = 42
|
|
162
|
+
await fn.run_until_complete(node_a, node_b)
|
|
163
|
+
|
|
164
|
+
assert node_a.inputs["value"].value == 42
|
|
165
|
+
assert node_b.inputs["value"].value == 42
|
|
166
|
+
assert node_a.call_count == 1
|
|
167
|
+
assert node_b.call_count == 0
|
|
168
|
+
assert node_a.outputs["output"].value == 42
|
|
169
|
+
assert node_b.outputs["output"].value is fn.NoValue
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@funcnodes_test
|
|
173
|
+
async def test_forwarded_input_uses_target_flag_when_source_flag_disables_self_only():
|
|
174
|
+
ForwardSourceNode = make_counter_node(
|
|
175
|
+
"forward_source_node_no_trigger_test", does_trigger=False
|
|
176
|
+
)
|
|
177
|
+
ForwardTargetNode = make_counter_node("forward_target_node_allow_test")
|
|
178
|
+
|
|
179
|
+
node_a = ForwardSourceNode()
|
|
180
|
+
node_b = ForwardTargetNode()
|
|
181
|
+
node_a.inputs["value"].connect(node_b.inputs["value"])
|
|
182
|
+
|
|
183
|
+
node_a.inputs["value"].value = 42
|
|
184
|
+
await fn.run_until_complete(node_a, node_b)
|
|
185
|
+
|
|
186
|
+
assert node_a.call_count == 0
|
|
187
|
+
assert node_b.call_count == 1
|
|
188
|
+
assert node_b.inputs["value"].value == 42
|
|
189
|
+
assert node_b.outputs["output"].value == 42
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@funcnodes_test
|
|
193
|
+
async def test_forwarded_input_propagates_explicit_false_downstream():
|
|
194
|
+
ForwardSourceNode = make_counter_node("forward_source_node_explicit_false_test")
|
|
195
|
+
ForwardTargetNode = make_counter_node("forward_target_node_explicit_false_test")
|
|
196
|
+
|
|
197
|
+
node_a = ForwardSourceNode()
|
|
198
|
+
node_b = ForwardTargetNode()
|
|
199
|
+
node_a.inputs["value"].connect(node_b.inputs["value"])
|
|
200
|
+
|
|
201
|
+
node_a.inputs["value"].set_value(42, does_trigger=False)
|
|
202
|
+
await fn.run_until_complete(node_a, node_b)
|
|
203
|
+
|
|
204
|
+
assert node_a.call_count == 0
|
|
205
|
+
assert node_b.call_count == 0
|
|
206
|
+
assert node_b.inputs["value"].value == 42
|
|
207
|
+
assert node_b.outputs["output"].value is fn.NoValue
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@funcnodes_test
|
|
211
|
+
async def test_forwarded_input_triggers_downstream_when_both_allow_triggering():
|
|
212
|
+
ForwardSourceNode = make_counter_node("forward_source_node_trigger_test")
|
|
213
|
+
ForwardTargetNode = make_counter_node("forward_target_node_trigger_test")
|
|
214
|
+
|
|
215
|
+
node_a = ForwardSourceNode()
|
|
216
|
+
node_b = ForwardTargetNode()
|
|
217
|
+
node_a.inputs["value"].connect(node_b.inputs["value"])
|
|
218
|
+
|
|
219
|
+
node_a.inputs["value"].value = 42
|
|
220
|
+
await fn.run_until_complete(node_a, node_b)
|
|
221
|
+
|
|
222
|
+
assert node_a.call_count == 1
|
|
223
|
+
assert node_b.call_count == 1
|
|
224
|
+
assert node_b.outputs["output"].value == 42
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@funcnodes_test
|
|
228
|
+
async def test_forward_chain_skips_non_triggering_middle_node_but_triggers_downstream():
|
|
229
|
+
NodeA = make_counter_node("forward_chain_source_node_test")
|
|
230
|
+
NodeB = make_counter_node("forward_chain_middle_node_test", does_trigger=False)
|
|
231
|
+
NodeC = make_counter_node("forward_chain_target_node_test")
|
|
232
|
+
|
|
233
|
+
node_a = NodeA()
|
|
234
|
+
node_b = NodeB()
|
|
235
|
+
node_c = NodeC()
|
|
236
|
+
|
|
237
|
+
node_a.inputs["value"].connect(node_b.inputs["value"])
|
|
238
|
+
node_b.inputs["value"].connect(node_c.inputs["value"])
|
|
239
|
+
|
|
240
|
+
node_a.inputs["value"].value = 42
|
|
241
|
+
await fn.run_until_complete(node_a, node_b, node_c)
|
|
242
|
+
|
|
243
|
+
assert node_a.call_count == 1
|
|
244
|
+
assert node_b.call_count == 0
|
|
245
|
+
assert node_c.call_count == 1
|
|
246
|
+
assert node_b.inputs["value"].value == 42
|
|
247
|
+
assert node_c.inputs["value"].value == 42
|
|
248
|
+
assert node_b.outputs["output"].value is fn.NoValue
|
|
249
|
+
assert node_c.outputs["output"].value == 42
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@funcnodes_test
|
|
253
|
+
async def test_output_set_value_respects_explicit_false_downstream():
|
|
254
|
+
SourceNode = make_counter_node("output_source_node_explicit_false_test")
|
|
255
|
+
TargetNode = make_counter_node("output_target_node_explicit_false_test")
|
|
256
|
+
|
|
257
|
+
node_a = SourceNode()
|
|
258
|
+
node_b = TargetNode()
|
|
259
|
+
node_a.outputs["output"].connect(node_b.inputs["value"])
|
|
260
|
+
|
|
261
|
+
node_a.outputs["output"].set_value(42, does_trigger=False)
|
|
262
|
+
await fn.run_until_complete(node_a, node_b)
|
|
263
|
+
|
|
264
|
+
assert node_b.inputs["value"].value == 42
|
|
265
|
+
assert node_b.call_count == 0
|
|
266
|
+
assert node_b.outputs["output"].value is fn.NoValue
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@funcnodes_test
|
|
270
|
+
async def test_output_set_value_uses_target_does_trigger_flag_when_unspecified():
|
|
271
|
+
SourceNode = make_counter_node("output_source_node_unspecified_test")
|
|
272
|
+
TargetNode = make_counter_node(
|
|
273
|
+
"output_target_node_unspecified_false_test", does_trigger=False
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
node_a = SourceNode()
|
|
277
|
+
node_b = TargetNode()
|
|
278
|
+
node_a.outputs["output"].connect(node_b.inputs["value"])
|
|
279
|
+
|
|
280
|
+
node_a.outputs["output"].set_value(42)
|
|
281
|
+
await fn.run_until_complete(node_a, node_b)
|
|
282
|
+
|
|
283
|
+
assert node_b.inputs["value"].value == 42
|
|
284
|
+
assert node_b.call_count == 0
|
|
285
|
+
assert node_b.outputs["output"].value is fn.NoValue
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@funcnodes_test
|
|
289
|
+
async def test_output_set_value_triggers_target_when_target_allows_triggering():
|
|
290
|
+
SourceNode = make_counter_node("output_source_node_trigger_test")
|
|
291
|
+
TargetNode = make_counter_node("output_target_node_trigger_test")
|
|
292
|
+
|
|
293
|
+
node_a = SourceNode()
|
|
294
|
+
node_b = TargetNode()
|
|
295
|
+
node_a.outputs["output"].connect(node_b.inputs["value"])
|
|
296
|
+
|
|
297
|
+
node_a.outputs["output"].set_value(42)
|
|
298
|
+
await fn.run_until_complete(node_a, node_b)
|
|
299
|
+
|
|
300
|
+
assert node_b.inputs["value"].value == 42
|
|
301
|
+
assert node_b.call_count == 1
|
|
302
|
+
assert node_b.outputs["output"].value == 42
|
|
303
|
+
|
|
304
|
+
|
|
131
305
|
@funcnodes_test
|
|
132
306
|
def test_nodeclass_string():
|
|
133
307
|
test_node = DummyNode(uuid="test_uuid")
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import funcnodes_core as fn
|
|
4
|
+
from funcnodes_core.node import Node, NodeInput, NodeOutput
|
|
5
|
+
from pytest_funcnodes import funcnodes_test
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
UNSPECIFIED = object()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_counter_node(node_id: str, *, does_trigger: bool = True):
|
|
12
|
+
resolved_node_id = node_id
|
|
13
|
+
|
|
14
|
+
class CounterNode(Node):
|
|
15
|
+
node_id = resolved_node_id
|
|
16
|
+
value = NodeInput(id="value", type=int, does_trigger=does_trigger)
|
|
17
|
+
output = NodeOutput(id="output", type=int)
|
|
18
|
+
|
|
19
|
+
def __init__(self, *args, **kwargs):
|
|
20
|
+
super().__init__(*args, pretrigger_delay=0.0, **kwargs)
|
|
21
|
+
self.call_count = 0
|
|
22
|
+
|
|
23
|
+
async def func(self, value: int):
|
|
24
|
+
self.call_count += 1
|
|
25
|
+
self.outputs["output"].value = value
|
|
26
|
+
|
|
27
|
+
return CounterNode
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_nodes(scenario_name: str, node_flags: dict[str, bool]) -> dict[str, Node]:
|
|
31
|
+
nodes: dict[str, Node] = {}
|
|
32
|
+
for name, does_trigger in node_flags.items():
|
|
33
|
+
node_cls = make_counter_node(
|
|
34
|
+
f"trigger_matrix_{scenario_name.lower()}_{name.lower()}",
|
|
35
|
+
does_trigger=does_trigger,
|
|
36
|
+
)
|
|
37
|
+
nodes[name] = node_cls()
|
|
38
|
+
return nodes
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def connect_nodes(
|
|
42
|
+
nodes: dict[str, Node], connections: list[tuple[str, str, str]]
|
|
43
|
+
) -> None:
|
|
44
|
+
for connection_type, src, dst in connections:
|
|
45
|
+
if connection_type == "forward":
|
|
46
|
+
nodes[src].inputs["value"].connect(nodes[dst].inputs["value"])
|
|
47
|
+
elif connection_type == "output":
|
|
48
|
+
nodes[src].outputs["output"].connect(nodes[dst].inputs["value"])
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"Unknown connection type: {connection_type}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def apply_action(nodes: dict[str, Node], action: dict[str, object]) -> None:
|
|
54
|
+
node = nodes[action["node"]] # type: ignore[index]
|
|
55
|
+
value = action["value"]
|
|
56
|
+
does_trigger = action.get("does_trigger", UNSPECIFIED)
|
|
57
|
+
|
|
58
|
+
if action["io"] == "input":
|
|
59
|
+
setter = node.inputs["value"].set_value
|
|
60
|
+
elif action["io"] == "output":
|
|
61
|
+
setter = node.outputs["output"].set_value
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError(f"Unknown io type: {action['io']}")
|
|
64
|
+
|
|
65
|
+
if does_trigger is UNSPECIFIED:
|
|
66
|
+
setter(value)
|
|
67
|
+
else:
|
|
68
|
+
setter(value, does_trigger=does_trigger) # type: ignore[arg-type]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def run_scenario(scenario: dict[str, object]) -> None:
|
|
72
|
+
nodes = build_nodes(scenario["name"], scenario["node_flags"]) # type: ignore[arg-type]
|
|
73
|
+
connect_nodes(nodes, scenario["connections"]) # type: ignore[arg-type]
|
|
74
|
+
apply_action(nodes, scenario["action"]) # type: ignore[arg-type]
|
|
75
|
+
await fn.run_until_complete(*nodes.values())
|
|
76
|
+
|
|
77
|
+
expected_counts = scenario["expected_counts"] # type: ignore[assignment]
|
|
78
|
+
for name, expected_count in expected_counts.items():
|
|
79
|
+
assert nodes[name].call_count == expected_count
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
INPUT_FORWARDING_SCENARIOS = [
|
|
83
|
+
{
|
|
84
|
+
"name": "input_forward_tt_unspecified",
|
|
85
|
+
"node_flags": {"A": True, "B": True},
|
|
86
|
+
"connections": [("forward", "A", "B")],
|
|
87
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
88
|
+
"expected_counts": {"A": 1, "B": 1},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "input_forward_ft_unspecified",
|
|
92
|
+
"node_flags": {"A": False, "B": True},
|
|
93
|
+
"connections": [("forward", "A", "B")],
|
|
94
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
95
|
+
"expected_counts": {"A": 0, "B": 1},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "input_forward_tf_unspecified",
|
|
99
|
+
"node_flags": {"A": True, "B": False},
|
|
100
|
+
"connections": [("forward", "A", "B")],
|
|
101
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
102
|
+
"expected_counts": {"A": 1, "B": 0},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "input_forward_ff_unspecified",
|
|
106
|
+
"node_flags": {"A": False, "B": False},
|
|
107
|
+
"connections": [("forward", "A", "B")],
|
|
108
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
109
|
+
"expected_counts": {"A": 0, "B": 0},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "input_forward_tt_explicit_false",
|
|
113
|
+
"node_flags": {"A": True, "B": True},
|
|
114
|
+
"connections": [("forward", "A", "B")],
|
|
115
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": False},
|
|
116
|
+
"expected_counts": {"A": 0, "B": 0},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "input_forward_chain_tft_unspecified",
|
|
120
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
121
|
+
"connections": [("forward", "A", "B"), ("forward", "B", "C")],
|
|
122
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
123
|
+
"expected_counts": {"A": 1, "B": 0, "C": 1},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "input_forward_chain_tft_explicit_false",
|
|
127
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
128
|
+
"connections": [("forward", "A", "B"), ("forward", "B", "C")],
|
|
129
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": False},
|
|
130
|
+
"expected_counts": {"A": 0, "B": 0, "C": 0},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "input_forward_chain_ftft_unspecified",
|
|
134
|
+
"node_flags": {"A": False, "B": True, "C": False, "D": True},
|
|
135
|
+
"connections": [
|
|
136
|
+
("forward", "A", "B"),
|
|
137
|
+
("forward", "B", "C"),
|
|
138
|
+
("forward", "C", "D"),
|
|
139
|
+
],
|
|
140
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
141
|
+
"expected_counts": {"A": 0, "B": 1, "C": 0, "D": 1},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "input_forward_fanout_t_tf_unspecified",
|
|
145
|
+
"node_flags": {"A": True, "B": True, "C": False},
|
|
146
|
+
"connections": [("forward", "A", "B"), ("forward", "A", "C")],
|
|
147
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
148
|
+
"expected_counts": {"A": 1, "B": 1, "C": 0},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "input_forward_fanout_t_tt_explicit_false",
|
|
152
|
+
"node_flags": {"A": True, "B": True, "C": True},
|
|
153
|
+
"connections": [("forward", "A", "B"), ("forward", "A", "C")],
|
|
154
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": False},
|
|
155
|
+
"expected_counts": {"A": 0, "B": 0, "C": 0},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "input_forward_tt_explicit_true",
|
|
159
|
+
"node_flags": {"A": True, "B": True},
|
|
160
|
+
"connections": [("forward", "A", "B")],
|
|
161
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
162
|
+
"expected_counts": {"A": 1, "B": 1},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "input_forward_tf_explicit_true",
|
|
166
|
+
"node_flags": {"A": True, "B": False},
|
|
167
|
+
"connections": [("forward", "A", "B")],
|
|
168
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
169
|
+
"expected_counts": {"A": 1, "B": 1},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "input_forward_ft_explicit_true",
|
|
173
|
+
"node_flags": {"A": False, "B": True},
|
|
174
|
+
"connections": [("forward", "A", "B")],
|
|
175
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
176
|
+
"expected_counts": {"A": 1, "B": 1},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "input_forward_ff_explicit_true",
|
|
180
|
+
"node_flags": {"A": False, "B": False},
|
|
181
|
+
"connections": [("forward", "A", "B")],
|
|
182
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
183
|
+
"expected_counts": {"A": 1, "B": 1},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "input_forward_chain_tft_explicit_true",
|
|
187
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
188
|
+
"connections": [("forward", "A", "B"), ("forward", "B", "C")],
|
|
189
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
190
|
+
"expected_counts": {"A": 1, "B": 1, "C": 1},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "input_forward_chain_tff_explicit_true",
|
|
194
|
+
"node_flags": {"A": True, "B": False, "C": False},
|
|
195
|
+
"connections": [("forward", "A", "B"), ("forward", "B", "C")],
|
|
196
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
197
|
+
"expected_counts": {"A": 1, "B": 1, "C": 1},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "input_forward_fanout_t_ft_explicit_true",
|
|
201
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
202
|
+
"connections": [("forward", "A", "B"), ("forward", "A", "C")],
|
|
203
|
+
"action": {"node": "A", "io": "input", "value": 1, "does_trigger": True},
|
|
204
|
+
"expected_counts": {"A": 1, "B": 1, "C": 1},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "input_forward_diamond",
|
|
208
|
+
"node_flags": {"A": True, "B": True, "C": True, "D": True, "E": False},
|
|
209
|
+
"connections": [
|
|
210
|
+
("forward", "A", "B"),
|
|
211
|
+
("forward", "A", "C"),
|
|
212
|
+
("forward", "B", "D"),
|
|
213
|
+
("forward", "C", "E"),
|
|
214
|
+
],
|
|
215
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
216
|
+
"expected_counts": {"A": 1, "B": 1, "C": 1, "D": 1, "E": 0},
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
OUTPUT_PROPAGATION_SCENARIOS = [
|
|
222
|
+
{
|
|
223
|
+
"name": "output_to_input_t_unspecified",
|
|
224
|
+
"node_flags": {"A": True, "B": True},
|
|
225
|
+
"connections": [("output", "A", "B")],
|
|
226
|
+
"action": {"node": "A", "io": "output", "value": 1},
|
|
227
|
+
"expected_counts": {"A": 0, "B": 1},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"name": "output_to_input_f_unspecified",
|
|
231
|
+
"node_flags": {"A": True, "B": False},
|
|
232
|
+
"connections": [("output", "A", "B")],
|
|
233
|
+
"action": {"node": "A", "io": "output", "value": 1},
|
|
234
|
+
"expected_counts": {"A": 0, "B": 0},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "output_to_input_t_explicit_false",
|
|
238
|
+
"node_flags": {"A": True, "B": True},
|
|
239
|
+
"connections": [("output", "A", "B")],
|
|
240
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": False},
|
|
241
|
+
"expected_counts": {"A": 0, "B": 0},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"name": "output_forward_chain_ft_unspecified",
|
|
245
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
246
|
+
"connections": [("output", "A", "B"), ("forward", "B", "C")],
|
|
247
|
+
"action": {"node": "A", "io": "output", "value": 1},
|
|
248
|
+
"expected_counts": {"A": 0, "B": 0, "C": 1},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "output_forward_chain_ft_explicit_false",
|
|
252
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
253
|
+
"connections": [("output", "A", "B"), ("forward", "B", "C")],
|
|
254
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": False},
|
|
255
|
+
"expected_counts": {"A": 0, "B": 0, "C": 0},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"name": "output_to_input_t_explicit_true",
|
|
259
|
+
"node_flags": {"A": True, "B": True},
|
|
260
|
+
"connections": [("output", "A", "B")],
|
|
261
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
262
|
+
"expected_counts": {"A": 0, "B": 1},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"name": "output_to_input_f_explicit_true",
|
|
266
|
+
"node_flags": {"A": True, "B": False},
|
|
267
|
+
"connections": [("output", "A", "B")],
|
|
268
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
269
|
+
"expected_counts": {"A": 0, "B": 0},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "output_forward_chain_ft_explicit_true",
|
|
273
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
274
|
+
"connections": [("output", "A", "B"), ("forward", "B", "C")],
|
|
275
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
276
|
+
"expected_counts": {"A": 0, "B": 0, "C": 1},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "output_forward_chain_tf_explicit_true",
|
|
280
|
+
"node_flags": {"A": True, "B": True, "C": False},
|
|
281
|
+
"connections": [("output", "A", "B"), ("forward", "B", "C")],
|
|
282
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
283
|
+
"expected_counts": {"A": 0, "B": 1, "C": 0},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"name": "output_forward_chain_ff_explicit_true",
|
|
287
|
+
"node_flags": {"A": True, "B": False, "C": False},
|
|
288
|
+
"connections": [("output", "A", "B"), ("forward", "B", "C")],
|
|
289
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
290
|
+
"expected_counts": {"A": 0, "B": 0, "C": 0},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "output_fanout_explicit_true",
|
|
294
|
+
"node_flags": {"A": True, "B": False, "C": True},
|
|
295
|
+
"connections": [("output", "A", "B"), ("output", "A", "C")],
|
|
296
|
+
"action": {"node": "A", "io": "output", "value": 1, "does_trigger": True},
|
|
297
|
+
"expected_counts": {"A": 0, "B": 0, "C": 1},
|
|
298
|
+
},
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
MIXED_PROPAGATION_SCENARIOS = [
|
|
303
|
+
{
|
|
304
|
+
"name": "execution_chain_output_output",
|
|
305
|
+
"node_flags": {"A": True, "B": True, "C": True},
|
|
306
|
+
"connections": [("output", "A", "B"), ("output", "B", "C")],
|
|
307
|
+
"action": {"node": "A", "io": "output", "value": 1},
|
|
308
|
+
"expected_counts": {"A": 0, "B": 1, "C": 1},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "input_then_execution_chain_output_false",
|
|
312
|
+
"node_flags": {"A": True, "B": True, "C": False},
|
|
313
|
+
"connections": [("forward", "A", "B"), ("output", "B", "C")],
|
|
314
|
+
"action": {"node": "A", "io": "input", "value": 1},
|
|
315
|
+
"expected_counts": {"A": 1, "B": 1, "C": 0},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"name": "mixed_output_diamond",
|
|
319
|
+
"node_flags": {"A": True, "B": False, "C": True, "D": True, "E": True},
|
|
320
|
+
"connections": [
|
|
321
|
+
("output", "A", "B"),
|
|
322
|
+
("output", "A", "C"),
|
|
323
|
+
("forward", "B", "D"),
|
|
324
|
+
("output", "C", "E"),
|
|
325
|
+
],
|
|
326
|
+
"action": {"node": "A", "io": "output", "value": 1},
|
|
327
|
+
"expected_counts": {"A": 0, "B": 0, "C": 1, "D": 1, "E": 1},
|
|
328
|
+
},
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.mark.parametrize(
|
|
333
|
+
"scenario", INPUT_FORWARDING_SCENARIOS, ids=lambda s: s["name"]
|
|
334
|
+
)
|
|
335
|
+
@funcnodes_test
|
|
336
|
+
async def test_input_forwarding_trigger_matrix(scenario):
|
|
337
|
+
await run_scenario(scenario)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@pytest.mark.parametrize(
|
|
341
|
+
"scenario", OUTPUT_PROPAGATION_SCENARIOS, ids=lambda s: s["name"]
|
|
342
|
+
)
|
|
343
|
+
@funcnodes_test
|
|
344
|
+
async def test_output_propagation_trigger_matrix(scenario):
|
|
345
|
+
await run_scenario(scenario)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@pytest.mark.parametrize(
|
|
349
|
+
"scenario", MIXED_PROPAGATION_SCENARIOS, ids=lambda s: s["name"]
|
|
350
|
+
)
|
|
351
|
+
@funcnodes_test
|
|
352
|
+
async def test_mixed_trigger_propagation_matrix(scenario):
|
|
353
|
+
await run_scenario(scenario)
|
funcnodes_core-2.3.2/README.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|