Coverage for formkit_ninja / parser / node_factory.py: 19.67%
45 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-20 04:40 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-20 04:40 +0000
1"""
2Factory for parsing FormKit nodes from structured inputs.
4This factory uses the NodeRegistry to resolve node types and create instances.
5It falls back to FormKitNode.parse_obj for backward compatibility.
6"""
8from __future__ import annotations
10import json
11from typing import Any, cast
13from formkit_ninja import formkit_schema
14from formkit_ninja.formkit_schema import FormKitNode
15from formkit_ninja.parser.node_registry import NodeRegistry, default_registry
18class FormKitNodeFactory:
19 """
20 Create FormKit node instances from dict or JSON input.
22 Uses NodeRegistry to resolve node types when possible, falling back to
23 FormKitNode.parse_obj for backward compatibility.
24 """
26 def __init__(self, registry: NodeRegistry | None = None) -> None:
27 """
28 Initialize the factory with an optional registry.
30 Args:
31 registry: Optional NodeRegistry instance. Defaults to default_registry.
32 """
33 self.registry = registry or default_registry
35 @staticmethod
36 def from_dict(data: dict[str, Any]) -> formkit_schema.FormKitType:
37 """
38 Create a FormKit node from a dictionary.
40 Uses the registry to resolve node types when possible, falling back
41 to FormKitNode.parse_obj for backward compatibility.
43 Args:
44 data: Dictionary containing node data (must include "$formkit" key for formkit nodes)
46 Returns:
47 A FormKit node instance
49 Raises:
50 ValueError: If the data is invalid or cannot be parsed
51 """
52 # Try to use registry if this is a formkit node
53 node: formkit_schema.FormKitType | None = None
54 if "$formkit" in data:
55 formkit_type = data["$formkit"]
56 node_class = default_registry.get_formkit_node_class(formkit_type)
58 if node_class is not None:
59 try:
60 # Use the registered class directly
61 node = node_class.parse_obj(data)
63 # Recursively parse children if present
64 # This is necessary because Pydantic models define children as FormKitSchemaProps
65 # but we need the specific node types
66 if "children" in data and hasattr(node, "children"):
67 children_in = data["children"]
68 if isinstance(children_in, list):
69 children_out: list[Any] = []
70 for child_data in children_in:
71 if isinstance(child_data, dict):
72 # Recursively use the factory
73 children_out.append(FormKitNodeFactory.from_dict(child_data))
74 elif isinstance(child_data, str):
75 children_out.append(child_data)
77 # mypy doesn't know that node.children is a list of FormKitType
78 node.children = children_out # type: ignore[assignment]
80 return cast(formkit_schema.FormKitType, node)
81 except Exception:
82 # If direct parsing fails, fall back to FormKitNode.parse_obj
83 # This maintains backward compatibility
84 pass
86 # Fall back to original behavior for backward compatibility
87 try:
88 node = FormKitNode.parse_obj(data).__root__ # type: ignore[assignment]
89 except Exception as exc:
90 raise ValueError("Invalid FormKit node data") from exc
91 return cast(formkit_schema.FormKitType, node)
93 @staticmethod
94 def from_json(payload: str) -> formkit_schema.FormKitType:
95 """
96 Create a FormKit node from a JSON string.
98 Args:
99 payload: JSON string containing node data
101 Returns:
102 A FormKit node instance
104 Raises:
105 ValueError: If the JSON is invalid or cannot be parsed
106 """
107 try:
108 data = json.loads(payload)
109 except json.JSONDecodeError as exc:
110 raise ValueError("Invalid JSON for FormKit node") from exc
111 if not isinstance(data, dict):
112 raise ValueError("FormKit node JSON must be an object")
113 return FormKitNodeFactory.from_dict(data)