Source code for noob.node.spec
from typing import Annotated, TypeAlias
from annotated_types import Len
from pydantic import BaseModel, Field, field_validator
from noob.types import AbsoluteIdentifier, DependencyIdentifier, PythonIdentifier
_DependsBasic: TypeAlias = Annotated[
dict[PythonIdentifier, DependencyIdentifier], Len(min_length=1, max_length=1)
]
"""
Standard dependency declaration, a mapping from a node's `slot` to a `node.signal` pair:
Examples:
for a pair of nodes like this:
```python
def node_a() -> Annotated[Generator[int], Name("index")]:
yield from count()
def node_b(my_value: int) -> None:
print(my_value)
```
one would express "pass node_a.index to node_b's `my_value`" like
```yaml
nodes:
a:
type: node_a
b:
type: node_b
depends:
- my_value: a.index
```
"""
DependsType: TypeAlias = list[DependencyIdentifier | _DependsBasic] | DependencyIdentifier
"""
Either an absolute identifier (which is treated as a positional-only arg)
or a dict mapping as described in _DependsBasic.
Examples:
```python
def example(positional_only: int, /, another_arg: str) -> None:
return another_arg * positional_only
```
```yaml
nodes:
zzz:
type: example
depends:
- a.value
- another_arg: b.value
```
When a dependency is a scalar value passed to the first positional argument,
it can be specified with a scalar reference to an absolute identifier.
For example, if one wanted to return a scalar value from a `return` node,
specify the dependency like this:
```yaml
nodes:
yyy:
type: return
depends: a.value
```
and to return the same value wrapped in a list...
```yaml
nodes:
yyy:
type: return
depends:
- a.value
```
"""
[docs]
class NodeSpecification(BaseModel):
"""
Specification for a single processing node within a tube .yaml file.
"""
type_: AbsoluteIdentifier = Field(..., alias="type")
"""
Shortname of the type of node this configuration is for.
Subclasses should override this with a default.
"""
id: PythonIdentifier
"""The unique identifier of the node"""
depends: DependsType | None = None
"""Dependency specification for the node.
Can be specified as a simple mapping from this node's input slots
to another node's output signals passed as kwargs,
or as a flat list of node.signal identifiers that are passed as positional args.
"""
params: dict | None = None
"""Static kwargs to pass to this node,
parameterized the signature of a function node, or
by a TypedDict for a class node.
"""
enabled: bool = True
"""
If this flag is False, the node will not be initialized
or included in the `:meth:.Tube.graph`.
"""
stateful: bool | None = None
"""
See :attr:`.Node.stateful` ,
explicitly set statefulness on a node, overriding its default.
If ``None`` , use the default set on the node class.
"""
description: str | None = None
"""An optional description of the node"""
[docs]
@field_validator("depends", mode="after")
@classmethod
def slots_unique(cls, val: DependsType | None) -> DependsType | None:
"""
Ensure slots are unique in dependency spec: can't map more than one signal to the same slot
"""
if val is None or isinstance(val, str):
return val
seen = set()
for dep in val:
if isinstance(dep, str):
continue
signal = next(iter(dep.keys()))
if signal in seen:
raise ValueError(f"Duplicate signal in dependencies: {signal}")
seen.add(signal)
return val