Coverage for formkit_ninja / management / commands / generate_all_schemas.py: 0.00%

77 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-06 04:12 +0000

1""" 

2Django management command to generate code from all root FormKit nodes in the database. 

3 

4This command reads all root nodes (groups/repeaters without parents) from the database 

5and generates Django models, Pydantic schemas, admin classes, and API endpoints 

6using the per-schema subdirectory structure. 

7""" 

8 

9from pathlib import Path 

10 

11from django.core.management.base import BaseCommand, CommandError 

12from django.db import connection 

13 

14from formkit_ninja import formkit_schema, models 

15from formkit_ninja.parser.formatter import CodeFormatter 

16from formkit_ninja.parser.generator import CodeGenerator 

17from formkit_ninja.parser.generator_config import GeneratorConfig 

18from formkit_ninja.parser.template_loader import DefaultTemplateLoader 

19 

20 

21class Command(BaseCommand): 

22 """Management command to generate code from all root FormKit nodes.""" 

23 

24 help = "Generate Django models, schemas, admin, and API code from all root FormKit nodes in the database" 

25 

26 def add_arguments(self, parser): 

27 """Add command-line arguments.""" 

28 parser.add_argument( 

29 "--app-name", 

30 type=str, 

31 required=True, 

32 help="Name of the Django app (required)", 

33 ) 

34 parser.add_argument( 

35 "--output-dir", 

36 type=str, 

37 required=True, 

38 help="Directory where generated code will be written (required)", 

39 ) 

40 

41 def handle(self, *args, **options): 

42 """Execute the command.""" 

43 app_name = options["app_name"] 

44 output_dir_str = options["output_dir"] 

45 

46 # Validate and convert output directory 

47 try: 

48 output_dir = Path(output_dir_str) 

49 except Exception as e: 

50 raise CommandError(f"Invalid output directory: {e}") from e 

51 

52 # Validate output directory exists (or can be created) 

53 if not output_dir.exists(): 

54 try: 

55 output_dir.mkdir(parents=True, exist_ok=True) 

56 self.stdout.write( 

57 self.style.SUCCESS(f"Created output directory: {output_dir}"), 

58 ) 

59 except OSError as e: 

60 raise CommandError( 

61 f"Cannot create output directory {output_dir}: {e}", 

62 ) from e 

63 elif not output_dir.is_dir(): 

64 raise CommandError(f"Output path exists but is not a directory: {output_dir}") 

65 

66 # Validate database connection 

67 try: 

68 with connection.cursor() as cursor: 

69 cursor.execute("SELECT 1") 

70 except Exception as e: 

71 raise CommandError( 

72 f"Cannot connect to database. Check your DATABASES setting and environment variables: {e}", 

73 ) from e 

74 

75 # Query all root nodes from database 

76 # Root nodes are groups or repeaters that don't have a parent 

77 # (i.e., they're not children of other nodes) 

78 # Get all nodes that are children (to exclude them) 

79 child_node_ids = models.NodeChildren.objects.values_list("child_id", flat=True) 

80 

81 # Query for active formkit nodes that are not children 

82 # and filter for groups/repeaters in Python (since $ is special in JSONField lookups) 

83 all_candidate_nodes = models.FormKitSchemaNode.objects.filter( 

84 is_active=True, 

85 node_type="$formkit", 

86 ).exclude(pk__in=child_node_ids) 

87 

88 # Filter in Python for groups and repeaters 

89 root_nodes = [] 

90 for node in all_candidate_nodes: 

91 if not node.node or not isinstance(node.node, dict): 

92 continue 

93 formkit_type = node.node.get("$formkit") or node.node.get("formkit") 

94 if formkit_type in ["group", "repeater"]: 

95 root_nodes.append(node) 

96 

97 if not root_nodes: 

98 raise CommandError("No root nodes found in database") 

99 

100 root_count = len(root_nodes) 

101 self.stdout.write( 

102 self.style.SUCCESS(f"Found {root_count} root node(s) in database"), 

103 ) 

104 

105 # Initialize shared generator components 

106 template_loader = DefaultTemplateLoader() 

107 formatter = CodeFormatter() 

108 

109 # Generate code for each root node 

110 success_count = 0 

111 error_count = 0 

112 

113 for root_node in root_nodes: 

114 try: 

115 # Get schema name from node label or use node ID 

116 schema_name = root_node.label or root_node.node.get("name") or str(root_node.id) 

117 self.stdout.write(f"Generating code for root node: {schema_name}") 

118 

119 # Initialize generator components with schema-specific config 

120 config = GeneratorConfig( 

121 app_name=app_name, 

122 output_dir=output_dir, 

123 schema_name=schema_name, 

124 ) 

125 generator = CodeGenerator( 

126 config=config, 

127 template_loader=template_loader, 

128 formatter=formatter, 

129 ) 

130 

131 # Convert root node and its descendants to Pydantic format 

132 # Use the node's get_node_values method to build the schema structure 

133 root_dict = root_node.get_node_values(recursive=True, options=True) 

134 if not root_dict: 

135 self.stdout.write( 

136 self.style.WARNING( 

137 f"Skipping root node {schema_name}: unable to build schema structure", 

138 ), 

139 ) 

140 error_count += 1 

141 continue 

142 

143 # Convert to Pydantic schema format 

144 # get_node_values returns a dict, wrap it in a list for FormKitSchema 

145 pydantic_schema = formkit_schema.FormKitSchema.parse_obj([root_dict]) 

146 

147 # Generate code 

148 generator.generate(pydantic_schema) 

149 

150 self.stdout.write( 

151 self.style.SUCCESS( 

152 f"Successfully generated code for root node: {schema_name}", 

153 ), 

154 ) 

155 success_count += 1 

156 except Exception as e: 

157 error_count += 1 

158 self.stdout.write( 

159 self.style.ERROR( 

160 f"Error generating code for root node {schema_name}: {e}", 

161 ), 

162 ) 

163 # Continue processing remaining schemas 

164 continue 

165 

166 # Summary 

167 self.stdout.write("") 

168 if success_count > 0: 

169 self.stdout.write( 

170 self.style.SUCCESS( 

171 f"Successfully generated code for {success_count} root node(s)", 

172 ), 

173 ) 

174 if error_count > 0: 

175 self.stdout.write( 

176 self.style.WARNING( 

177 f"Failed to generate code for {error_count} root node(s)", 

178 ), 

179 ) 

180 

181 if error_count > 0 and success_count == 0: 

182 raise CommandError("Code generation failed for all root nodes") 

183 

184 self.stdout.write( 

185 self.style.SUCCESS( 

186 f"\nCode generation complete. Output directory: {output_dir}", 

187 ), 

188 )