Coverage for src/lexigram/web/cli/generators/graphql.py: 29%
41 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""GraphQL generator for the web package."""
3from __future__ import annotations
5from typing import Any
7from lexigram.codegen import GenerationResult, GeneratorBase, parse_fields
9GRAPHQL_TYPE_MAP = {
10 "str": "str",
11 "string": "str",
12 "int": "int",
13 "integer": "int",
14 "float": "float",
15 "bool": "bool",
16 "boolean": "bool",
17 "datetime": "datetime",
18 "text": "str",
19}
21SAMPLE_VALUES = {
22 "str": '"sample_string"',
23 "string": '"sample_string"',
24 "int": "1",
25 "integer": "1",
26 "float": "1.5",
27 "bool": "True",
28 "boolean": "True",
29 "datetime": "datetime.now()",
30 "text": '"sample text"',
31}
34class GraphQLGenerator(GeneratorBase):
35 """Generate a GraphQL schema scaffold."""
37 template_name = "graphql.py.jinja2"
39 def __init__(self, output_dir: str = "src/graphql") -> None:
40 super().__init__(output_dir=output_dir)
42 def generate(
43 self,
44 name: str,
45 *,
46 fields_str: str | None = None,
47 doc: str | None = None,
48 dry_run: bool = False,
49 force: bool = False,
50 **options: object,
51 ) -> GenerationResult:
52 type_name = self._to_snake_case(name)
53 resource_name = self._pluralize(type_name)
54 file_path = self.output_dir / f"{type_name}.py"
55 fields = parse_fields(fields_str or "")
57 prepared_fields: list[dict[str, Any]] = []
58 required_fields: list[dict[str, Any]] = []
59 optional_fields: list[dict[str, Any]] = []
60 filterable_fields: list[dict[str, Any]] = []
62 for field in fields:
63 prepared_field = {
64 "name": field.name,
65 "type": field.type,
66 "graphql_type": GRAPHQL_TYPE_MAP.get(field.type, "str"),
67 "description": f"The {field.name} field",
68 "sample_value": SAMPLE_VALUES.get(field.type, '"sample"'),
69 }
70 prepared_fields.append(prepared_field)
71 if field.required:
72 required_fields.append(prepared_field)
73 else:
74 optional_fields.append(prepared_field)
75 if field.type in {"str", "int", "bool"}:
76 filterable_fields.append(prepared_field)
78 if not prepared_fields:
79 prepared_fields = [
80 {
81 "name": "name",
82 "type": "str",
83 "graphql_type": "str",
84 "description": "The name",
85 "sample_value": '"Test"',
86 },
87 {
88 "name": "active",
89 "type": "bool",
90 "graphql_type": "bool",
91 "description": "Is active",
92 "sample_value": "True",
93 },
94 ]
95 required_fields = [prepared_fields[0]]
96 optional_fields = [prepared_fields[1]]
97 filterable_fields = list(prepared_fields)
99 content = self.render_template(
100 self.template_name,
101 {
102 "name": name,
103 "class_name": self._to_pascal_case(name),
104 "resource_name": resource_name,
105 "doc": doc,
106 "fields": prepared_fields,
107 "required_fields": required_fields,
108 "optional_fields": optional_fields,
109 "filterable_fields": filterable_fields,
110 "has_timestamps": True,
111 },
112 )
113 return self.write_file(file_path, content, dry_run=dry_run, force=force)
115 @staticmethod
116 def _pluralize(value: str) -> str:
117 if value.endswith("y") and value[-2:-1] not in {"a", "e", "i", "o", "u"}:
118 return f"{value[:-1]}ies"
119 if value.endswith("s"):
120 return value
121 return f"{value}s"
124__all__ = ["GraphQLGenerator"]