Skip to content

NodeBuilder

Base class for creating custom node types using Pydantic models.

NodeBuilder(id=None, data=None, selected=None, **initial_values)

Bases: AnyWidget

Base class for custom node widgets implementing the NodeFactory protocol.

This class can be used in two ways: 1. As a standalone widget for configuration UI 2. As a base class for custom node types registered with NodeFlowWidget

To create a custom node, inherit from this class and define: - parameters: A Pydantic BaseModel class defining configuration fields - label: Display name for the node - Optional: icon, category, description, inputs, outputs

Example

from pydantic import BaseModel, Field

class ProcessingConfig(BaseModel): ... threshold: float = Field(default=0.5, ge=0, le=1) ... mode: str = "auto"

class ProcessingNode(NodeBuilder): ... parameters = ProcessingConfig ... label = "Image Processor" ... icon = "🖼️" ... category = "processing" ... inputs = [{"id": "input", "label": "Image"}] ... outputs = [{"id": "output", "label": "Processed"}]

Initialize the node builder.

Parameters:

Name Type Description Default
id

Widget ID (default: "json-schema-node")

None
data

Initial data dict (for standalone widget mode)

None
selected

Selection state

None
**initial_values

Initial parameter values (passed to Pydantic model)

{}
Source code in pynodewidget/json_schema_node.py
def __init__(self, id=None, data=None, selected=None, **initial_values):
    """Initialize the node builder.

    Args:
        id: Widget ID (default: "json-schema-node")
        data: Initial data dict (for standalone widget mode)
        selected: Selection state
        **initial_values: Initial parameter values (passed to Pydantic model)
    """
    super().__init__()

    # Initialize Pydantic model instance for parameters
    if self.__class__.parameters is not None:
        try:
            self._config = self.__class__.parameters(**initial_values)
        except Exception as e:
            # If initialization fails, create with defaults
            self._config = self.__class__.parameters()
    else:
        self._config = None

    # Set widget properties
    if id is not None:
        self.id = id

    # Handle two modes: standalone widget vs. node definition
    if data is not None:
        # Standalone widget mode: use provided data dict
        self.data = data
    elif self.__class__.parameters is not None:
        # Node definition mode: generate data from class attributes
        self.data = self._generate_data_dict()

    if selected is not None:
        self.selected = selected

get_values()

Get current configuration values.

Returns:

Type Description
Dict[str, Any]

Dictionary containing all current parameter values

Source code in pynodewidget/json_schema_node.py
def get_values(self) -> Dict[str, Any]:
    """Get current configuration values.

    Returns:
        Dictionary containing all current parameter values
    """
    if self._config is not None:
        return self._config.model_dump()

    # Fallback for standalone widget mode
    return self.data.get("values", {})

set_values(values)

Update configuration values.

Parameters:

Name Type Description Default
values Dict[str, Any]

Dictionary of parameter values to update

required
Source code in pynodewidget/json_schema_node.py
def set_values(self, values: Dict[str, Any]) -> None:
    """Update configuration values.

    Args:
        values: Dictionary of parameter values to update
    """
    if self._config is not None:
        # Update Pydantic model with new values
        current_values = self._config.model_dump()
        current_values.update(values)
        self._config = self.__class__.parameters(**current_values)

        # Update widget data
        if "values" in self.data:
            self.data = {**self.data, "values": self._config.model_dump()}
    else:
        # Fallback for standalone widget mode
        if "values" in self.data:
            self.data = {**self.data, "values": {**self.data["values"], **values}}

set_value(key, value)

Update a single configuration value.

Parameters:

Name Type Description Default
key str

Parameter name

required
value Any

New value

required
Source code in pynodewidget/json_schema_node.py
def set_value(self, key: str, value: Any) -> None:
    """Update a single configuration value.

    Args:
        key: Parameter name
        value: New value
    """
    self.set_values({key: value})

validate()

Validate current configuration.

Returns:

Type Description
bool

True if configuration is valid, False otherwise

Source code in pynodewidget/json_schema_node.py
def validate(self) -> bool:
    """Validate current configuration.

    Returns:
        True if configuration is valid, False otherwise
    """
    if self._config is None:
        return True

    try:
        # Pydantic validation happens automatically on assignment
        # If we can recreate the model with current values, it's valid
        self.__class__.parameters(**self._config.model_dump())
        return True
    except Exception:
        return False

execute(inputs)

Execute node logic (to be overridden in subclasses).

This is a placeholder for future execution engine support. Subclasses can override this to implement custom node logic.

Parameters:

Name Type Description Default
inputs Dict[str, Any]

Dictionary of input values from connected nodes

required

Returns:

Type Description
Dict[str, Any]

Dictionary of output values

Source code in pynodewidget/json_schema_node.py
def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
    """Execute node logic (to be overridden in subclasses).

    This is a placeholder for future execution engine support.
    Subclasses can override this to implement custom node logic.

    Args:
        inputs: Dictionary of input values from connected nodes

    Returns:
        Dictionary of output values
    """
    raise NotImplementedError(
        f"{self.__class__.__name__} does not implement execute(). "
        "Override this method to add execution logic."
    )

from_pydantic(model_class, label=None, icon='', category='general', description='', grid_layout=None, initial_values=None, header=None, footer=None, style=None, validation=None, fieldConfigs=None, **kwargs) classmethod

Create a node from a Pydantic model (factory method for convenience).

This is a convenience method for creating a node widget without defining a full subclass. For better code organization, prefer creating a proper subclass instead.

Parameters:

Name Type Description Default
model_class Type[BaseModel]

Pydantic BaseModel class

required
label Optional[str]

Display name

None
icon str

Unicode emoji or icon

''
category str

Node category

'general'
description str

Help text

''
grid_layout Optional[Dict[str, Any]]

Grid layout configuration dict

None
initial_values Optional[Dict[str, Any]]

Initial parameter values

None
header Optional[Dict[str, Any]]

Header configuration dict

None
footer Optional[Dict[str, Any]]

Footer configuration dict

None
style Optional[Dict[str, Any]]

Style configuration dict

None
validation Optional[Dict[str, Any]]

Validation configuration dict

None
fieldConfigs Optional[Dict[str, Dict[str, Any]]]

Per-field configuration dict

None
**kwargs Any

Additional configuration options

{}

Returns:

Type Description
NodeBuilder

New JsonSchemaNodeWidget instance

Source code in pynodewidget/json_schema_node.py
@classmethod
def from_pydantic(
    cls,
    model_class: Type[BaseModel],
    label: Optional[str] = None,
    icon: str = "",
    category: str = "general",
    description: str = "",
    grid_layout: Optional[Dict[str, Any]] = None,
    initial_values: Optional[Dict[str, Any]] = None,
    # Enhanced configuration options
    header: Optional[Dict[str, Any]] = None,
    footer: Optional[Dict[str, Any]] = None,
    style: Optional[Dict[str, Any]] = None,
    validation: Optional[Dict[str, Any]] = None,
    fieldConfigs: Optional[Dict[str, Dict[str, Any]]] = None,
    **kwargs: Any,  # Catch any additional config options
) -> "NodeBuilder":
    """Create a node from a Pydantic model (factory method for convenience).

    This is a convenience method for creating a node widget without
    defining a full subclass. For better code organization, prefer
    creating a proper subclass instead.

    Args:
        model_class: Pydantic BaseModel class
        label: Display name
        icon: Unicode emoji or icon
        category: Node category
        description: Help text
        grid_layout: Grid layout configuration dict
        initial_values: Initial parameter values
        header: Header configuration dict
        footer: Footer configuration dict
        style: Style configuration dict
        validation: Validation configuration dict
        fieldConfigs: Per-field configuration dict
        **kwargs: Additional configuration options

    Returns:
        New JsonSchemaNodeWidget instance
    """
    # Create anonymous subclass
    class AnonymousNode(cls):
        pass

    # Set class attributes
    AnonymousNode.parameters = model_class
    AnonymousNode.label = label or model_class.__name__
    AnonymousNode.icon = icon
    AnonymousNode.category = category
    AnonymousNode.description = description
    AnonymousNode.grid_layout = grid_layout

    # Create instance with initial values
    instance = AnonymousNode(**(initial_values or {}))

    # Apply enhanced configuration to the data dict
    data = instance.data.copy()

    if header:
        data["header"] = header
    if footer:
        data["footer"] = footer
    if style:
        data["style"] = style
    if validation:
        data["validation"] = validation
    if fieldConfigs:
        data["fieldConfigs"] = fieldConfigs

    # Apply any additional kwargs to data
    for key, value in kwargs.items():
        if value is not None:
            data[key] = value

    instance.data = data
    return instance

from_schema(schema, label, icon='', category='general', description='', grid_layout=None, initial_values=None) classmethod

Create a node from a JSON schema (legacy support for rapid prototyping).

This method provides backward compatibility for code using raw JSON schemas. For new code, use Pydantic models with the parameters attribute instead.

Parameters:

Name Type Description Default
schema Dict[str, Any]

JSON Schema definition

required
label str

Display name

required
icon str

Unicode emoji or icon

''
category str

Node category

'general'
description str

Help text

''
grid_layout Optional[Dict[str, Any]]

Grid layout configuration dict

None
initial_values Optional[Dict[str, Any]]

Initial parameter values

None

Returns:

Type Description
NodeBuilder

New JsonSchemaNodeWidget instance

Source code in pynodewidget/json_schema_node.py
@classmethod
def from_schema(
    cls,
    schema: Dict[str, Any],
    label: str,
    icon: str = "",
    category: str = "general",
    description: str = "",
    grid_layout: Optional[Dict[str, Any]] = None,
    initial_values: Optional[Dict[str, Any]] = None,
) -> "NodeBuilder":
    """Create a node from a JSON schema (legacy support for rapid prototyping).

    This method provides backward compatibility for code using raw JSON schemas.
    For new code, use Pydantic models with the parameters attribute instead.

    Args:
        schema: JSON Schema definition
        label: Display name
        icon: Unicode emoji or icon
        category: Node category
        description: Help text
        grid_layout: Grid layout configuration dict
        initial_values: Initial parameter values

    Returns:
        New JsonSchemaNodeWidget instance
    """
    # Extract default values from schema
    default_values = {}
    if schema and "properties" in schema:
        for key, prop in schema["properties"].items():
            if "default" in prop:
                default_values[key] = prop["default"]

    # Merge with initial values
    if initial_values:
        default_values.update(initial_values)

    # Create standalone widget with data dict
    data = {
        "label": label,
        "grid": {"cells": [], "rows": ["1fr"], "columns": ["1fr"], "gap": "8px"},
        "values": default_values,
    }

    widget = cls()
    widget.data = data
    return widget

Overview

JsonSchemaNodeWidget implements the NodeFactory protocol and provides a convenient base class for defining custom nodes. It automatically generates UI from Pydantic models and handles value synchronization.

Basic Usage

Creating a Node Class

from pydantic import BaseModel, Field
from pynodewidget import JsonSchemaNodeWidget

# 1. Define parameters with Pydantic
class ProcessorParams(BaseModel):
    threshold: float = Field(default=0.5, ge=0, le=1)
    mode: str = "auto"
    enabled: bool = True

# 2. Create node class
class ProcessorNode(JsonSchemaNodeWidget):
    label = "Processor"
    parameters = ProcessorParams
    icon = "⚙️"
    category = "processing"
    description = "Process data with threshold"

    inputs = [{"id": "in", "label": "Input"}]
    outputs = [{"id": "out", "label": "Output"}]

    layout_type = "horizontal"
    handle_type = "button"

Required Attributes

  • label: Display name (string)
  • parameters: Pydantic BaseModel class

Optional Attributes

  • icon: Emoji or Unicode symbol (default: "")
  • category: Grouping category (default: "general")
  • description: Help text (default: "")
  • inputs: List of input handle configs (default: [])
  • outputs: List of output handle configs (default: [])
  • layout_type: Layout style - "horizontal", "vertical", "compact" (default: "horizontal")
  • handle_type: Handle type - "base", "button", "labeled" (default: "base")

Handle Configuration

Input and Output Handles

class MyNode(JsonSchemaNodeWidget):
    label = "My Node"
    parameters = MyParams

    inputs = [
        {"id": "data_in", "label": "Data"},
        {"id": "config_in", "label": "Config"}
    ]

    outputs = [
        {"id": "result", "label": "Result"},
        {"id": "metadata", "label": "Metadata"}
    ]

Using Pydantic for Handles

from pydantic import BaseModel

class InputHandles(BaseModel):
    data_in: str
    config_in: str

class OutputHandles(BaseModel):
    result: str
    metadata: str

class MyNode(JsonSchemaNodeWidget):
    label = "My Node"
    parameters = MyParams
    inputs = InputHandles
    outputs = OutputHandles

Working with Values

Getting Values

# Inside node class
def execute(self, inputs):
    config = self.get_values()
    threshold = config["threshold"]
    mode = config["mode"]
    # ... use values

# From widget instance
node = ProcessorNode(threshold=0.8, enabled=True)
values = node.get_values()
# {"threshold": 0.8, "mode": "auto", "enabled": True}

Setting Values

# Set multiple values
node.set_values({"threshold": 0.9, "mode": "manual"})

# Set single value
node.set_value("threshold", 0.9)

# During initialization
node = ProcessorNode(threshold=0.7, mode="advanced")

Validation

# Check if current values are valid
if node.validate():
    print("Configuration is valid")
else:
    print("Invalid configuration")

Execution Logic

Implementing execute()

class ProcessorNode(JsonSchemaNodeWidget):
    label = "Processor"
    parameters = ProcessorParams
    inputs = [{"id": "data_in", "label": "Data"}]
    outputs = [{"id": "data_out", "label": "Processed"}]

    def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
        """Process input data."""
        # Get configuration
        config = self.get_values()
        threshold = config["threshold"]

        # Get input data
        data = inputs.get("data_in", [])

        # Process
        filtered = [x for x in data if x >= threshold]

        # Return outputs
        return {"data_out": filtered}

execute() Signature

def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
    """Execute node logic.

    Args:
        inputs: Dictionary mapping input handle IDs to values
                e.g., {"data_in": [1, 2, 3], "config_in": {...}}

    Returns:
        Dictionary mapping output handle IDs to values
        e.g., {"data_out": [1, 2], "metadata": {...}}
    """
    pass

Execution is Optional

PyNodeWidget provides the graph structure but doesn't enforce an execution model. Implement execute() if you plan to run workflows programmatically.

Factory Methods

from_pydantic()

Create a node without defining a full subclass:

from pynodewidget import JsonSchemaNodeWidget
from pynodeflow.node_builder import with_style

# Quick node creation
node = JsonSchemaNodeWidget.from_pydantic(
    model_class=ProcessorParams,
    label="Quick Processor",
    icon="⚡",
    category="processing",
    inputs=[{"id": "in", "label": "Input"}],
    outputs=[{"id": "out", "label": "Output"}],
    initial_values={"threshold": 0.7}
)

# With styling
node = JsonSchemaNodeWidget.from_pydantic(
    model_class=ProcessorParams,
    label="Styled Processor",
    header={
        "show": True,
        "icon": "✨",
        "className": "bg-blue-600 text-white"
    },
    style={
        "minWidth": "300px",
        "shadow": "lg"
    }
)

from_schema()

Create a node from raw JSON schema (legacy):

schema = {
    "type": "object",
    "properties": {
        "threshold": {
            "type": "number",
            "default": 0.5,
            "minimum": 0,
            "maximum": 1
        }
    }
}

node = JsonSchemaNodeWidget.from_schema(
    schema=schema,
    label="Schema Node",
    icon="📋",
    inputs=[{"id": "in", "label": "Input"}],
    outputs=[{"id": "out", "label": "Output"}]
)

Layout and Styling

Layout Types

# Horizontal: inputs left, outputs right
class HorizontalNode(JsonSchemaNodeWidget):
    layout_type = "horizontal"

# Vertical: inputs top, outputs bottom
class VerticalNode(JsonSchemaNodeWidget):
    layout_type = "vertical"

# Compact: minimal spacing
class CompactNode(JsonSchemaNodeWidget):
    layout_type = "compact"

Handle Types

# Base: small dot handles (default)
class BaseNode(JsonSchemaNodeWidget):
    handle_type = "base"

# Button: larger, interactive buttons
class ButtonNode(JsonSchemaNodeWidget):
    handle_type = "button"

# Labeled: handles with visible labels
class LabeledNode(JsonSchemaNodeWidget):
    handle_type = "labeled"

Mixed Handle Types

# Different types for inputs and outputs
node = JsonSchemaNodeWidget.from_pydantic(
    MyParams,
    label="Mixed Handles",
    input_handle_type="labeled",
    output_handle_type="button"
)

Advanced Configuration

Enhanced Styling

from pynodeflow.node_builder import create_processing_node, with_style

# Using node builder utilities
config = create_processing_node("Advanced Node", icon="🚀")
config = with_style(config, 
    min_width="350px",
    max_width="600px",
    border_radius="12px",
    shadow="xl",
    class_name="border-2 border-blue-500"
)

node = JsonSchemaNodeWidget.from_pydantic(
    MyParams,
    **config
)
node = JsonSchemaNodeWidget.from_pydantic(
    MyParams,
    label="Custom Node",
    header={
        "show": True,
        "icon": "✨",
        "bgColor": "#3b82f6",
        "textColor": "#ffffff",
        "className": "font-bold"
    },
    footer={
        "show": True,
        "text": "v1.0.0",
        "className": "text-xs text-gray-500"
    }
)

Conditional Fields

from pynodeflow.node_builder import make_fields_conditional

fieldConfigs = make_fields_conditional(
    trigger_field="mode",
    trigger_value="advanced",
    dependent_fields=["threshold", "iterations"]
)

node = JsonSchemaNodeWidget.from_pydantic(
    MyParams,
    label="Conditional Node",
    fieldConfigs=fieldConfigs
)

Validation Configuration

node = JsonSchemaNodeWidget.from_pydantic(
    MyParams,
    label="Validated Node",
    validation={
        "showErrors": True,
        "errorPosition": "inline",  # or "tooltip", "footer"
        "validateOnChange": True
    }
)

Complete Example

from pydantic import BaseModel, Field
from typing import Literal
from pynodewidget import JsonSchemaNodeWidget, NodeFlowWidget

# Define parameters
class ImageProcessorParams(BaseModel):
    algorithm: Literal["blur", "sharpen", "edge"] = "blur"
    strength: float = Field(default=0.5, ge=0, le=1, description="Effect strength")
    preserve_alpha: bool = Field(default=True, description="Preserve transparency")
    iterations: int = Field(default=1, ge=1, le=10, description="Number of passes")

# Create node class
class ImageProcessorNode(JsonSchemaNodeWidget):
    """Image processing node with multiple algorithms."""

    label = "Image Processor"
    parameters = ImageProcessorParams
    icon = "🖼️"
    category = "image"
    description = "Apply various image processing algorithms"

    inputs = [
        {"id": "image", "label": "Image"},
        {"id": "mask", "label": "Mask (optional)"}
    ]
    outputs = [
        {"id": "result", "label": "Processed Image"},
        {"id": "metadata", "label": "Processing Info"}
    ]

    layout_type = "vertical"
    handle_type = "labeled"

    def execute(self, inputs):
        """Execute image processing."""
        config = self.get_values()
        image = inputs.get("image")
        mask = inputs.get("mask")

        # Processing logic here
        result = self._process_image(
            image,
            algorithm=config["algorithm"],
            strength=config["strength"],
            iterations=config["iterations"]
        )

        metadata = {
            "algorithm": config["algorithm"],
            "iterations": config["iterations"]
        }

        return {
            "result": result,
            "metadata": metadata
        }

    def _process_image(self, image, algorithm, strength, iterations):
        """Internal processing method."""
        # Your image processing code
        pass

# Use in workflow
flow = NodeFlowWidget(nodes=[ImageProcessorNode])
flow

See Also