Declarative template for creating nodes inside graphs. A `NodeTemplate` is a lightweight configuration object that can be reused across graphs. It does not instantiate nodes directly; graphs materialize templates via `Graph.create_node()` so the created node is always owned by a graph.
| 248 | |
| 249 | |
| 250 | class NodeTemplate(Generic[T]): |
| 251 | """Declarative template for creating nodes inside graphs. |
| 252 | |
| 253 | A `NodeTemplate` is a lightweight configuration object that can be reused across graphs. |
| 254 | It does not instantiate nodes directly; graphs materialize templates via `Graph.create_node()` |
| 255 | so the created node is always owned by a graph. |
| 256 | """ |
| 257 | |
| 258 | def __init__(self, node_cls: Type[T], **default_kwargs): |
| 259 | """Create a NodeTemplate. |
| 260 | |
| 261 | Args: |
| 262 | node_cls: Node class to be materialized. |
| 263 | **default_kwargs: Default constructor kwargs applied during materialization. |
| 264 | """ |
| 265 | self.node_cls = node_cls |
| 266 | self.prototype_config = default_kwargs |
| 267 | |
| 268 | def __deepcopy__(self, memo: dict[int, Any]) -> "NodeTemplate[T]": |
| 269 | """Deep-copy via MASFactory clone semantics instead of Python's generic object walk. |
| 270 | |
| 271 | This ensures nested NodeTemplate declarations still respect `Shared(...)`, |
| 272 | `Factory(...)`, and `__node_template_scope__` when an outer template clones |
| 273 | them as part of its prototype config. |
| 274 | """ |
| 275 | obj_id = id(self) |
| 276 | if obj_id in memo: |
| 277 | return memo[obj_id] |
| 278 | |
| 279 | cloned = object.__new__(type(self)) |
| 280 | memo[obj_id] = cloned |
| 281 | cloned.node_cls = self.node_cls |
| 282 | cloned.prototype_config = _safe_clone(self.prototype_config, memo) |
| 283 | return cloned |
| 284 | |
| 285 | def render_config(self, **override_kwargs) -> dict[str, Any]: |
| 286 | final_config = _safe_clone(self.prototype_config) |
| 287 | final_config.update(_safe_clone(override_kwargs)) |
| 288 | return final_config |
| 289 | |
| 290 | def __call__(self, *args: Any, **override_kwargs) -> "NodeTemplate[T]": |
| 291 | """ |
| 292 | Create a derived template with overridden defaults. |
| 293 | |
| 294 | MASFactory graphs only accept Nodes created by Graph.create_node(), so NodeTemplate intentionally does NOT |
| 295 | construct a Node instance directly. |
| 296 | """ |
| 297 | if args: |
| 298 | raise TypeError( |
| 299 | "NodeTemplate(...) no longer materializes a Node. " |
| 300 | "Use Graph.create_node(template, name=...) or declare nodes=[(name, template)]." |
| 301 | ) |
| 302 | if "name" in override_kwargs: |
| 303 | raise TypeError( |
| 304 | "NodeTemplate(...) does not accept 'name'. " |
| 305 | "Use Graph.create_node(template, name=...) or declare nodes=[(name, template)]." |
| 306 | ) |
| 307 | if not override_kwargs: |
no outgoing calls
no test coverage detected