Coverage for formkit_ninja / parser / generator_config.py: 35.90%
56 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"""
2Generator configuration for code generation.
4This module provides the GeneratorConfig class which holds all configuration
5needed for code generation, including app name, output directory, NodePath class,
6template packages, and custom imports.
7"""
9import re
10from pathlib import Path
11from typing import Any, Optional, Type
13from pydantic import BaseModel, root_validator, validator
15from formkit_ninja.parser.type_convert import NodePath
17# Import DatabaseNodePath for default
18try:
19 from formkit_ninja.parser.database_node_path import DatabaseNodePath
21 DEFAULT_NODE_PATH_CLASS: type[NodePath] = DatabaseNodePath
22except ImportError:
23 # Fallback if database module not available
24 DEFAULT_NODE_PATH_CLASS = NodePath
27def schema_name_to_filename(schema_name: str) -> str:
28 """
29 Convert a schema name to a valid Python module filename.
31 Examples:
32 TF_6_1_1 -> tf611
33 MySchema -> myschema
34 Schema_1_2_3 -> schema123
36 Args:
37 schema_name: The schema name/label
39 Returns:
40 A valid Python module filename (lowercase, no special chars except underscores)
41 """
42 # Remove all non-alphanumeric characters except underscores
43 cleaned = re.sub(r"[^a-zA-Z0-9_]", "", schema_name)
44 # Convert to lowercase
45 cleaned = cleaned.lower()
46 # Remove underscores
47 cleaned = cleaned.replace("_", "")
48 return cleaned
51class GeneratorConfig(BaseModel):
52 """
53 Configuration for code generation.
55 Attributes:
56 app_name: Name of the Django app (required)
57 output_dir: Directory where generated code will be written (required)
58 node_path_class: Custom NodePath subclass to use (default: NodePath)
59 template_packages: List of package paths for template loading (default: [])
60 custom_imports: List of custom import statements to include (default: [])
61 include_ordinality: Whether to include ordinality field in repeater models (default: True)
62 merge_top_level_groups: Whether to merge top-level groups using abstract inheritance (default: False)
63 schema_name: Optional schema name/label used for generating model filenames (default: None)
64 """
66 app_name: str
67 output_dir: Path
68 node_path_class: Type[NodePath] = DEFAULT_NODE_PATH_CLASS
69 template_packages: list[str] = []
70 custom_imports: list[str] = []
71 include_ordinality: bool = True
72 merge_top_level_groups: bool = False
73 schema_name: Optional[str] = None
75 @validator("app_name")
76 def validate_app_name(cls, v: str) -> str:
77 """Validate that app_name is not empty."""
78 if not v or not v.strip():
79 raise ValueError("app_name cannot be empty")
80 return v.strip()
82 @validator("output_dir", pre=True)
83 def validate_output_dir(cls, v: str | Path) -> Path:
84 """Convert string to Path if necessary."""
85 if isinstance(v, str):
86 return Path(v)
87 return v
89 @validator("node_path_class")
90 def validate_node_path_class(cls, v: Type[NodePath]) -> Type[NodePath]:
91 """Validate that node_path_class is a subclass of NodePath."""
92 if not isinstance(v, type) or not issubclass(v, NodePath):
93 raise ValueError("node_path_class must be a subclass of NodePath")
94 return v
96 @root_validator(pre=True)
97 def validate_list_items_before_coercion(cls, values: dict[str, Any]) -> dict[str, Any]:
98 """Validate that list items are strings before Pydantic coerces them."""
99 if "template_packages" in values:
100 template_packages = values["template_packages"]
101 if isinstance(template_packages, list):
102 for item in template_packages:
103 if not isinstance(item, str):
104 raise ValueError("All items in template_packages must be strings")
106 if "custom_imports" in values:
107 custom_imports = values["custom_imports"]
108 if isinstance(custom_imports, list):
109 for item in custom_imports:
110 if not isinstance(item, str):
111 raise ValueError("All items in custom_imports must be strings")
113 return values
115 class Config:
116 """Pydantic configuration."""
118 arbitrary_types_allowed = True