Skip to content
Merged

Test #37

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
Expand All @@ -10,14 +10,14 @@ repos:

- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: "0.6.14"
rev: "0.9.4"
hooks:
# Update the uv lockfile
- id: uv-lock

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.6
rev: v0.14.1
hooks:
# Run the linter.
- id: ruff
Expand All @@ -26,7 +26,13 @@ repos:
- id: ruff-format

- repo: https://github.com/pycqa/flake8
rev: 7.2.0 # pick a git hash / tag to point to
rev: 7.3.0 # pick a git hash / tag to point to
hooks:
- id: flake8
args: ["--config=.flake8"]

- repo: https://github.com/commitizen-tools/commitizen
rev: v4.9.1
hooks:
- id: commitizen
stages: [commit-msg]
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## v1.1.0 (2025-11-21)

### Feat

- **logic**: enhance ForNode to support async iterables

### Fix

- **dicts**: improve key retrieval logic in DictGetNode

## v1.0.0 (2025-09-16)

### Feat
Expand Down
6 changes: 6 additions & 0 deletions cz.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "uv"
update_changelog_on_bump = true
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "funcnodes-basic"
version = "1.0.0"
version = "1.1.0"
description = "Basic functionalities for funcnodes"
readme = "README.md"
classifiers = [ "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",]
Expand Down
8 changes: 7 additions & 1 deletion src/funcnodes_basic/dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ def _update_keys(self, **kwargs):
self._keymap = keymap

async def func(self, dictionary: dict, key: str) -> None:
v = dictionary.get(self._keymap[key], fn.NoValue)
try:
v = dictionary[self._keymap[key]]
except KeyError:
try:
v = dictionary[key]
except KeyError:
v = fn.NoValue
self.outputs["value"].value = v
return v

Expand Down
15 changes: 13 additions & 2 deletions src/funcnodes_basic/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from funcnodes_core.node import Node
from typing import Any, List, Optional
from funcnodes_core.io import NodeInput, NodeOutput, NoValue
from collections.abc import AsyncIterable
import asyncio

import funcnodes_core as fn
Expand Down Expand Up @@ -104,8 +105,8 @@ async def func(self, input: list, collector: Optional[Any] = None) -> None:
except Exception:
pass

for i in self.progress(input, desc="Iterating", unit="it", total=iplen):
self.outputs["do"].set_value(i, does_trigger=True)
async def _iterate_value(value: Any):
self.outputs["do"].set_value(value, does_trigger=True)
datapaths = [
ip.datapath
for ip in self.outputs["do"].connections
Expand All @@ -124,6 +125,16 @@ async def func(self, input: list, collector: Optional[Any] = None) -> None:
if v is not NoValue:
results.append(v)
self.inputs["collector"].value = NoValue

if isinstance(input, AsyncIterable):
# handle async iterables/generators without forcing them into sync iteration
with self.progress(desc="Iterating", unit="it", total=iplen) as pbar:
async for i in input:
await _iterate_value(i)
pbar.update(1)
else:
for i in self.progress(input, desc="Iterating", unit="it", total=iplen):
await _iterate_value(i)
self.outputs["done"].value = results


Expand Down
6 changes: 3 additions & 3 deletions src/funcnodes_basic/pyobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Any, List, Annotated, Dict
from typing import Any, List, Annotated

import funcnodes_core as fn
from funcnodes_core.io import InputMeta, OutputMeta
Expand Down Expand Up @@ -46,7 +46,7 @@ def get_attribute(
"attribute",
_list_public_attributes,
)
}
},
),
],
attribute: Annotated[
Expand Down Expand Up @@ -146,7 +146,7 @@ def delete_attribute(
"attribute",
_list_public_attributes,
)
}
},
),
],
attribute: Annotated[
Expand Down
66 changes: 66 additions & 0 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,72 @@ async def test_for_node():
assert node.outputs["done"].value == ["h", "e", "l", "l", "o"]


@pytest_funcnodes.nodetest(logic.ForNode)
async def test_for_node_async_generator():
async def async_generator():
yield "hello"
yield "world"
yield "foo"
yield "bar"

node = logic.ForNode()
waitnode = logic.WaitNode()
waitnode.inputs["delay"].value = 0.5
waitnode.inputs["input"].connect(node.outputs["do"])
waitnode.outputs["output"].connect(node.inputs["collector"])
node.outputs["do"].connect(waitnode.inputs["input"])
node.inputs["input"].value = async_generator()

await node
assert node.outputs["done"].value == ["hello", "world", "foo", "bar"]


@pytest_funcnodes.nodetest(logic.ForNode)
async def test_for_node_async_iterable_object():
class AsyncIterable:
def __init__(self, values):
self._values = values

def __aiter__(self):
self._iter = iter(self._values)
return self

async def __anext__(self):
try:
return next(self._iter)
except StopIteration:
raise StopAsyncIteration

node = logic.ForNode()
waitnode = logic.WaitNode()
waitnode.inputs["delay"].value = 0.1
waitnode.inputs["input"].connect(node.outputs["do"])
waitnode.outputs["output"].connect(node.inputs["collector"])
node.outputs["do"].connect(waitnode.inputs["input"])
node.inputs["input"].value = AsyncIterable(["a", "b", "c"])

await node

assert node.outputs["done"].value == ["a", "b", "c"]

async def async_generator():
yield "d"
yield "e"
yield "f"

node = logic.ForNode()
waitnode = logic.WaitNode()
waitnode.inputs["delay"].value = 0.1
waitnode.inputs["input"].connect(node.outputs["do"])
waitnode.outputs["output"].connect(node.inputs["collector"])
node.outputs["do"].connect(waitnode.inputs["input"])
node.inputs["input"].value = async_generator()

await node

assert node.outputs["done"].value == ["d", "e", "f"]


@pytest_funcnodes.nodetest(logic.CollectorNode)
async def test_collector_node():
node = logic.CollectorNode()
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.