structurize 2.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avrotize/__init__.py +64 -0
- avrotize/__main__.py +6 -0
- avrotize/_version.py +34 -0
- avrotize/asn1toavro.py +160 -0
- avrotize/avrotize.py +152 -0
- avrotize/avrotocpp.py +483 -0
- avrotize/avrotocsharp.py +1075 -0
- avrotize/avrotocsv.py +121 -0
- avrotize/avrotodatapackage.py +173 -0
- avrotize/avrotodb.py +1383 -0
- avrotize/avrotogo.py +476 -0
- avrotize/avrotographql.py +197 -0
- avrotize/avrotoiceberg.py +210 -0
- avrotize/avrotojava.py +2156 -0
- avrotize/avrotojs.py +250 -0
- avrotize/avrotojsons.py +481 -0
- avrotize/avrotojstruct.py +345 -0
- avrotize/avrotokusto.py +364 -0
- avrotize/avrotomd.py +137 -0
- avrotize/avrotools.py +168 -0
- avrotize/avrotoparquet.py +208 -0
- avrotize/avrotoproto.py +359 -0
- avrotize/avrotopython.py +624 -0
- avrotize/avrotorust.py +435 -0
- avrotize/avrotots.py +598 -0
- avrotize/avrotoxsd.py +344 -0
- avrotize/cddltostructure.py +1841 -0
- avrotize/commands.json +3337 -0
- avrotize/common.py +834 -0
- avrotize/constants.py +72 -0
- avrotize/csvtoavro.py +132 -0
- avrotize/datapackagetoavro.py +76 -0
- avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
- avrotize/dependencies/typescript/node22/package.json +16 -0
- avrotize/dependency_resolver.py +348 -0
- avrotize/dependency_version.py +432 -0
- avrotize/jsonstoavro.py +2167 -0
- avrotize/jsonstostructure.py +2642 -0
- avrotize/jstructtoavro.py +878 -0
- avrotize/kstructtoavro.py +93 -0
- avrotize/kustotoavro.py +455 -0
- avrotize/parquettoavro.py +157 -0
- avrotize/proto2parser.py +498 -0
- avrotize/proto3parser.py +403 -0
- avrotize/prototoavro.py +382 -0
- avrotize/structuretocddl.py +597 -0
- avrotize/structuretocpp.py +697 -0
- avrotize/structuretocsharp.py +2295 -0
- avrotize/structuretocsv.py +365 -0
- avrotize/structuretodatapackage.py +659 -0
- avrotize/structuretodb.py +1125 -0
- avrotize/structuretogo.py +720 -0
- avrotize/structuretographql.py +502 -0
- avrotize/structuretoiceberg.py +355 -0
- avrotize/structuretojava.py +853 -0
- avrotize/structuretojsons.py +498 -0
- avrotize/structuretokusto.py +639 -0
- avrotize/structuretomd.py +322 -0
- avrotize/structuretoproto.py +764 -0
- avrotize/structuretopython.py +772 -0
- avrotize/structuretorust.py +714 -0
- avrotize/structuretots.py +653 -0
- avrotize/structuretoxsd.py +679 -0
- avrotize/xsdtoavro.py +413 -0
- structurize-2.19.0.dist-info/METADATA +107 -0
- structurize-2.19.0.dist-info/RECORD +70 -0
- structurize-2.19.0.dist-info/WHEEL +5 -0
- structurize-2.19.0.dist-info/entry_points.txt +2 -0
- structurize-2.19.0.dist-info/licenses/LICENSE +201 -0
- structurize-2.19.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
# pylint: disable=line-too-long
|
|
2
|
+
|
|
3
|
+
""" StructureToPython class for converting JSON Structure schema to Python classes """
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import random
|
|
9
|
+
from typing import Any, Dict, List, Set, Tuple, Union, Optional
|
|
10
|
+
|
|
11
|
+
from avrotize.common import pascal, process_template
|
|
12
|
+
from avrotize.jstructtoavro import JsonStructureToAvro
|
|
13
|
+
|
|
14
|
+
JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
15
|
+
|
|
16
|
+
INDENT = ' '
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_python_reserved_word(word: str) -> bool:
|
|
20
|
+
"""Checks if a word is a Python reserved word"""
|
|
21
|
+
reserved_words = [
|
|
22
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
|
|
23
|
+
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
|
|
24
|
+
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
|
|
25
|
+
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
|
|
26
|
+
'try', 'while', 'with', 'yield', 'record', 'self', 'cls'
|
|
27
|
+
]
|
|
28
|
+
return word in reserved_words
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StructureToPython:
|
|
32
|
+
""" Converts JSON Structure schema to Python classes """
|
|
33
|
+
|
|
34
|
+
def __init__(self, base_package: str = '', dataclasses_json_annotation=False, avro_annotation=False) -> None:
|
|
35
|
+
self.base_package = base_package
|
|
36
|
+
self.dataclasses_json_annotation = dataclasses_json_annotation
|
|
37
|
+
self.avro_annotation = avro_annotation
|
|
38
|
+
self.output_dir = os.getcwd()
|
|
39
|
+
self.schema_doc: JsonNode = None
|
|
40
|
+
self.generated_types: Dict[str, str] = {}
|
|
41
|
+
self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
|
|
42
|
+
self.type_dict: Dict[str, Dict] = {}
|
|
43
|
+
self.definitions: Dict[str, Any] = {}
|
|
44
|
+
self.schema_registry: Dict[str, Dict] = {}
|
|
45
|
+
|
|
46
|
+
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
47
|
+
""" Concatenates namespace and name with a dot separator """
|
|
48
|
+
return f"{namespace}.{name}" if namespace != '' else name
|
|
49
|
+
|
|
50
|
+
def concat_namespace(self, namespace: str, name: str) -> str:
|
|
51
|
+
""" Concatenates namespace and name with a dot separator """
|
|
52
|
+
if namespace and name:
|
|
53
|
+
return f"{namespace}.{name}"
|
|
54
|
+
elif namespace:
|
|
55
|
+
return namespace
|
|
56
|
+
else:
|
|
57
|
+
return name
|
|
58
|
+
|
|
59
|
+
def map_primitive_to_python(self, structure_type: str) -> str:
|
|
60
|
+
""" Maps JSON Structure primitive types to Python types """
|
|
61
|
+
mapping = {
|
|
62
|
+
'null': 'None',
|
|
63
|
+
'boolean': 'bool',
|
|
64
|
+
'string': 'str',
|
|
65
|
+
'integer': 'int',
|
|
66
|
+
'number': 'float',
|
|
67
|
+
'int8': 'int',
|
|
68
|
+
'uint8': 'int',
|
|
69
|
+
'int16': 'int',
|
|
70
|
+
'uint16': 'int',
|
|
71
|
+
'int32': 'int',
|
|
72
|
+
'uint32': 'int',
|
|
73
|
+
'int64': 'int',
|
|
74
|
+
'uint64': 'int',
|
|
75
|
+
'int128': 'int',
|
|
76
|
+
'uint128': 'int',
|
|
77
|
+
'float8': 'float',
|
|
78
|
+
'float': 'float',
|
|
79
|
+
'double': 'float',
|
|
80
|
+
'binary32': 'float',
|
|
81
|
+
'binary64': 'float',
|
|
82
|
+
'decimal': 'decimal.Decimal',
|
|
83
|
+
'binary': 'bytes',
|
|
84
|
+
'date': 'datetime.date',
|
|
85
|
+
'time': 'datetime.time',
|
|
86
|
+
'datetime': 'datetime.datetime',
|
|
87
|
+
'timestamp': 'datetime.datetime',
|
|
88
|
+
'duration': 'datetime.timedelta',
|
|
89
|
+
'uuid': 'uuid.UUID',
|
|
90
|
+
'uri': 'str',
|
|
91
|
+
'jsonpointer': 'str',
|
|
92
|
+
'any': 'typing.Any'
|
|
93
|
+
}
|
|
94
|
+
qualified_class_name = self.get_qualified_name(
|
|
95
|
+
self.base_package.lower(), structure_type.lower())
|
|
96
|
+
if qualified_class_name in self.generated_types:
|
|
97
|
+
result = qualified_class_name
|
|
98
|
+
else:
|
|
99
|
+
result = mapping.get(structure_type, 'typing.Any')
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
def is_python_primitive(self, type_name: str) -> bool:
|
|
103
|
+
""" Checks if a type is a Python primitive type """
|
|
104
|
+
return type_name in ['None', 'bool', 'int', 'float', 'str', 'bytes']
|
|
105
|
+
|
|
106
|
+
def is_python_typing_struct(self, type_name: str) -> bool:
|
|
107
|
+
""" Checks if a type is a Python typing type """
|
|
108
|
+
return type_name.startswith('typing.Dict[') or type_name.startswith('typing.List[') or \
|
|
109
|
+
type_name.startswith('typing.Optional[') or type_name.startswith('typing.Union[') or \
|
|
110
|
+
type_name == 'typing.Any'
|
|
111
|
+
|
|
112
|
+
def safe_name(self, name: str) -> str:
|
|
113
|
+
"""Converts a name to a safe Python name"""
|
|
114
|
+
if is_python_reserved_word(name):
|
|
115
|
+
return name + "_"
|
|
116
|
+
return name
|
|
117
|
+
|
|
118
|
+
def pascal_type_name(self, ref: str) -> str:
|
|
119
|
+
"""Converts a reference to a type name"""
|
|
120
|
+
return '_'.join([pascal(part) for part in ref.split('.')[-1].split('_')])
|
|
121
|
+
|
|
122
|
+
def python_package_from_structure_type(self, namespace: str, type_name: str) -> str:
|
|
123
|
+
"""Gets the Python package from a type name"""
|
|
124
|
+
type_name_package = '.'.join([part.lower() for part in type_name.split('.')]) if '.' in type_name else type_name.lower()
|
|
125
|
+
if '.' in type_name:
|
|
126
|
+
package = type_name_package
|
|
127
|
+
else:
|
|
128
|
+
namespace_package = '.'.join([part.lower() for part in namespace.split('.')]) if namespace else ''
|
|
129
|
+
package = namespace_package + ('.' if namespace_package and type_name_package else '') + type_name_package
|
|
130
|
+
if self.base_package:
|
|
131
|
+
package = self.base_package + '.' + package
|
|
132
|
+
return package
|
|
133
|
+
|
|
134
|
+
def python_type_from_structure_type(self, type_name: str) -> str:
|
|
135
|
+
"""Gets the Python class from a type name"""
|
|
136
|
+
return self.pascal_type_name(type_name)
|
|
137
|
+
|
|
138
|
+
def python_fully_qualified_name_from_structure_type(self, namespace: str, type_name: str) -> str:
|
|
139
|
+
"""Gets the fully qualified Python class name from a Structure type."""
|
|
140
|
+
package = self.python_package_from_structure_type(namespace, type_name)
|
|
141
|
+
return package + ('.' if package else '') + self.python_type_from_structure_type(type_name)
|
|
142
|
+
|
|
143
|
+
def strip_package_from_fully_qualified_name(self, fully_qualified_name: str) -> str:
|
|
144
|
+
"""Strips the package from a fully qualified name"""
|
|
145
|
+
return fully_qualified_name.split('.')[-1]
|
|
146
|
+
|
|
147
|
+
def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
|
|
148
|
+
""" Resolves a $ref to the actual schema definition """
|
|
149
|
+
if not ref.startswith('#/'):
|
|
150
|
+
if ref in self.schema_registry:
|
|
151
|
+
return self.schema_registry[ref]
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
path = ref[2:].split('/')
|
|
155
|
+
schema = context_schema if context_schema else self.schema_doc
|
|
156
|
+
for part in path:
|
|
157
|
+
if not isinstance(schema, dict) or part not in schema:
|
|
158
|
+
return None
|
|
159
|
+
schema = schema[part]
|
|
160
|
+
return schema
|
|
161
|
+
|
|
162
|
+
def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
|
|
163
|
+
""" Recursively registers schemas with $id keywords """
|
|
164
|
+
if not isinstance(schema, dict):
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if '$id' in schema:
|
|
168
|
+
schema_id = schema['$id']
|
|
169
|
+
if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
|
|
170
|
+
from urllib.parse import urljoin
|
|
171
|
+
schema_id = urljoin(base_uri, schema_id)
|
|
172
|
+
self.schema_registry[schema_id] = schema
|
|
173
|
+
base_uri = schema_id
|
|
174
|
+
|
|
175
|
+
if 'definitions' in schema:
|
|
176
|
+
for def_name, def_schema in schema['definitions'].items():
|
|
177
|
+
if isinstance(def_schema, dict):
|
|
178
|
+
self.register_schema_ids(def_schema, base_uri)
|
|
179
|
+
|
|
180
|
+
if 'properties' in schema:
|
|
181
|
+
for prop_name, prop_schema in schema['properties'].items():
|
|
182
|
+
if isinstance(prop_schema, dict):
|
|
183
|
+
self.register_schema_ids(prop_schema, base_uri)
|
|
184
|
+
|
|
185
|
+
for key in ['items', 'values', 'additionalProperties']:
|
|
186
|
+
if key in schema and isinstance(schema[key], dict):
|
|
187
|
+
self.register_schema_ids(schema[key], base_uri)
|
|
188
|
+
|
|
189
|
+
def convert_structure_type_to_python(self, class_name: str, field_name: str,
|
|
190
|
+
structure_type: JsonNode, parent_namespace: str,
|
|
191
|
+
import_types: Set[str]) -> str:
|
|
192
|
+
""" Converts JSON Structure type to Python type """
|
|
193
|
+
if isinstance(structure_type, str):
|
|
194
|
+
python_type = self.map_primitive_to_python(structure_type)
|
|
195
|
+
if python_type.startswith('datetime.') or python_type == 'decimal.Decimal' or python_type == 'uuid.UUID':
|
|
196
|
+
import_types.add(python_type)
|
|
197
|
+
return python_type
|
|
198
|
+
elif isinstance(structure_type, list):
|
|
199
|
+
# Handle type unions
|
|
200
|
+
non_null_types = [t for t in structure_type if t != 'null']
|
|
201
|
+
if len(non_null_types) == 1:
|
|
202
|
+
inner_type = self.convert_structure_type_to_python(
|
|
203
|
+
class_name, field_name, non_null_types[0], parent_namespace, import_types)
|
|
204
|
+
if 'null' in structure_type:
|
|
205
|
+
return f'typing.Optional[{inner_type}]'
|
|
206
|
+
return inner_type
|
|
207
|
+
else:
|
|
208
|
+
union_types = [self.convert_structure_type_to_python(
|
|
209
|
+
class_name, field_name, t, parent_namespace, import_types) for t in non_null_types]
|
|
210
|
+
return f"typing.Union[{', '.join(union_types)}]"
|
|
211
|
+
elif isinstance(structure_type, dict):
|
|
212
|
+
# Handle $ref
|
|
213
|
+
if '$ref' in structure_type:
|
|
214
|
+
ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc)
|
|
215
|
+
if ref_schema:
|
|
216
|
+
ref_path = structure_type['$ref'].split('/')
|
|
217
|
+
type_name = ref_path[-1]
|
|
218
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
219
|
+
ref = self.generate_class_or_choice(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
|
|
220
|
+
import_types.add(ref)
|
|
221
|
+
return self.strip_package_from_fully_qualified_name(ref)
|
|
222
|
+
return 'typing.Any'
|
|
223
|
+
|
|
224
|
+
# Handle enum keyword
|
|
225
|
+
if 'enum' in structure_type:
|
|
226
|
+
enum_ref = self.generate_enum(structure_type, field_name, parent_namespace, write_file=True)
|
|
227
|
+
import_types.add(enum_ref)
|
|
228
|
+
return self.strip_package_from_fully_qualified_name(enum_ref)
|
|
229
|
+
|
|
230
|
+
# Handle type keyword
|
|
231
|
+
if 'type' not in structure_type:
|
|
232
|
+
return 'typing.Any'
|
|
233
|
+
|
|
234
|
+
struct_type = structure_type['type']
|
|
235
|
+
|
|
236
|
+
# Handle complex types
|
|
237
|
+
if struct_type == 'object':
|
|
238
|
+
class_ref = self.generate_class(structure_type, parent_namespace, write_file=True)
|
|
239
|
+
import_types.add(class_ref)
|
|
240
|
+
return self.strip_package_from_fully_qualified_name(class_ref)
|
|
241
|
+
elif struct_type == 'array':
|
|
242
|
+
items_type = self.convert_structure_type_to_python(
|
|
243
|
+
class_name, field_name+'List', structure_type.get('items', {'type': 'any'}),
|
|
244
|
+
parent_namespace, import_types)
|
|
245
|
+
return f"typing.List[{items_type}]"
|
|
246
|
+
elif struct_type == 'set':
|
|
247
|
+
items_type = self.convert_structure_type_to_python(
|
|
248
|
+
class_name, field_name+'Set', structure_type.get('items', {'type': 'any'}),
|
|
249
|
+
parent_namespace, import_types)
|
|
250
|
+
return f"typing.Set[{items_type}]"
|
|
251
|
+
elif struct_type == 'map':
|
|
252
|
+
values_type = self.convert_structure_type_to_python(
|
|
253
|
+
class_name, field_name+'Map', structure_type.get('values', {'type': 'any'}),
|
|
254
|
+
parent_namespace, import_types)
|
|
255
|
+
return f"typing.Dict[str, {values_type}]"
|
|
256
|
+
elif struct_type == 'choice':
|
|
257
|
+
# Generate choice returns a Union type and populates import_types with the choice types
|
|
258
|
+
return self.generate_choice(structure_type, parent_namespace, write_file=True, import_types=import_types)
|
|
259
|
+
elif struct_type == 'tuple':
|
|
260
|
+
tuple_ref = self.generate_tuple(structure_type, parent_namespace, write_file=True)
|
|
261
|
+
import_types.add(tuple_ref)
|
|
262
|
+
return self.strip_package_from_fully_qualified_name(tuple_ref)
|
|
263
|
+
else:
|
|
264
|
+
return self.convert_structure_type_to_python(class_name, field_name, struct_type, parent_namespace, import_types)
|
|
265
|
+
return 'typing.Any'
|
|
266
|
+
|
|
267
|
+
def generate_class_or_choice(self, structure_schema: Dict, parent_namespace: str,
|
|
268
|
+
write_file: bool = True, explicit_name: str = '') -> str:
|
|
269
|
+
""" Generates a Class or Choice """
|
|
270
|
+
struct_type = structure_schema.get('type', 'object')
|
|
271
|
+
if struct_type == 'object':
|
|
272
|
+
return self.generate_class(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
273
|
+
elif struct_type == 'choice':
|
|
274
|
+
return self.generate_choice(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
275
|
+
elif struct_type == 'tuple':
|
|
276
|
+
return self.generate_tuple(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
277
|
+
return 'typing.Any'
|
|
278
|
+
|
|
279
|
+
def generate_class(self, structure_schema: Dict, parent_namespace: str,
|
|
280
|
+
write_file: bool, explicit_name: str = '') -> str:
|
|
281
|
+
""" Generates a Python dataclass from JSON Structure object type """
|
|
282
|
+
import_types: Set[str] = set()
|
|
283
|
+
|
|
284
|
+
# Get name and namespace
|
|
285
|
+
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
|
|
286
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
287
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
288
|
+
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
289
|
+
|
|
290
|
+
if python_qualified_name in self.generated_types:
|
|
291
|
+
return python_qualified_name
|
|
292
|
+
|
|
293
|
+
# Check if this is an abstract type
|
|
294
|
+
is_abstract = structure_schema.get('abstract', False)
|
|
295
|
+
|
|
296
|
+
# Handle inheritance ($extends)
|
|
297
|
+
base_class = None
|
|
298
|
+
if '$extends' in structure_schema:
|
|
299
|
+
base_ref = structure_schema['$extends']
|
|
300
|
+
if isinstance(self.schema_doc, dict):
|
|
301
|
+
base_schema = self.resolve_ref(base_ref, self.schema_doc)
|
|
302
|
+
if base_schema:
|
|
303
|
+
ref_path = base_ref.split('/')
|
|
304
|
+
base_name = ref_path[-1]
|
|
305
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
306
|
+
base_class = self.generate_class(base_schema, ref_namespace, write_file=True, explicit_name=base_name)
|
|
307
|
+
import_types.add(base_class)
|
|
308
|
+
|
|
309
|
+
# Generate properties
|
|
310
|
+
properties = structure_schema.get('properties', {})
|
|
311
|
+
required_props = structure_schema.get('required', [])
|
|
312
|
+
|
|
313
|
+
fields = []
|
|
314
|
+
for prop_name, prop_schema in properties.items():
|
|
315
|
+
field_def = self.generate_field(prop_name, prop_schema, class_name, schema_namespace,
|
|
316
|
+
required_props, import_types)
|
|
317
|
+
fields.append(field_def)
|
|
318
|
+
|
|
319
|
+
# Get docstring
|
|
320
|
+
doc = structure_schema.get('description', structure_schema.get('doc', class_name))
|
|
321
|
+
|
|
322
|
+
# Generate field docstrings
|
|
323
|
+
field_docstrings = [{
|
|
324
|
+
'name': self.safe_name(field['name']),
|
|
325
|
+
'original_name': field['name'],
|
|
326
|
+
'type': field['type'],
|
|
327
|
+
'is_primitive': field['is_primitive'],
|
|
328
|
+
'is_enum': field['is_enum'],
|
|
329
|
+
'docstring': self.generate_field_docstring(field, schema_namespace),
|
|
330
|
+
'test_value': self.generate_test_value(field),
|
|
331
|
+
'source_type': field.get('source_type', 'string'),
|
|
332
|
+
} for field in fields]
|
|
333
|
+
|
|
334
|
+
# If avro_annotation is enabled, convert JSON Structure schema to Avro schema
|
|
335
|
+
# This is embedded in the generated class for runtime Avro serialization
|
|
336
|
+
avro_schema_json = ''
|
|
337
|
+
if self.avro_annotation:
|
|
338
|
+
# Use JsonStructureToAvro to convert the schema
|
|
339
|
+
converter = JsonStructureToAvro()
|
|
340
|
+
schema_copy = structure_schema.copy()
|
|
341
|
+
avro_schema = converter.convert(schema_copy)
|
|
342
|
+
avro_schema_json = json.dumps(avro_schema).replace('\\"', '\'').replace('"', '\\"')
|
|
343
|
+
|
|
344
|
+
# Process template
|
|
345
|
+
class_definition = process_template(
|
|
346
|
+
"structuretopython/dataclass_core.jinja",
|
|
347
|
+
class_name=class_name,
|
|
348
|
+
docstring=doc,
|
|
349
|
+
fields=field_docstrings,
|
|
350
|
+
import_types=import_types,
|
|
351
|
+
base_package=self.base_package,
|
|
352
|
+
dataclasses_json_annotation=self.dataclasses_json_annotation,
|
|
353
|
+
avro_annotation=self.avro_annotation,
|
|
354
|
+
avro_schema_json=avro_schema_json,
|
|
355
|
+
is_abstract=is_abstract,
|
|
356
|
+
base_class=base_class,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if write_file:
|
|
360
|
+
self.write_to_file(namespace, class_name, class_definition)
|
|
361
|
+
self.generate_test_class(namespace, class_name, field_docstrings, import_types)
|
|
362
|
+
|
|
363
|
+
self.generated_types[python_qualified_name] = 'class'
|
|
364
|
+
self.generated_structure_types[python_qualified_name] = structure_schema
|
|
365
|
+
return python_qualified_name
|
|
366
|
+
|
|
367
|
+
def generate_field(self, prop_name: str, prop_schema: Dict, class_name: str,
|
|
368
|
+
parent_namespace: str, required_props: List, import_types: Set[str]) -> Dict:
|
|
369
|
+
""" Generates a field for a Python dataclass """
|
|
370
|
+
field_name = prop_name
|
|
371
|
+
|
|
372
|
+
# Check if this is a const field
|
|
373
|
+
if 'const' in prop_schema:
|
|
374
|
+
# Const fields are treated as class variables with default values
|
|
375
|
+
prop_type = self.convert_structure_type_to_python(
|
|
376
|
+
class_name, field_name, prop_schema, parent_namespace, import_types)
|
|
377
|
+
return {
|
|
378
|
+
'name': field_name,
|
|
379
|
+
'type': prop_type,
|
|
380
|
+
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
381
|
+
'is_enum': False,
|
|
382
|
+
'is_const': True,
|
|
383
|
+
'const_value': prop_schema['const'],
|
|
384
|
+
'source_type': prop_schema.get('type', 'string')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Determine if required
|
|
388
|
+
is_required = prop_name in required_props if not isinstance(required_props, list) or \
|
|
389
|
+
len(required_props) == 0 or not isinstance(required_props[0], list) else \
|
|
390
|
+
any(prop_name in req_set for req_set in required_props)
|
|
391
|
+
|
|
392
|
+
# Get property type
|
|
393
|
+
prop_type = self.convert_structure_type_to_python(
|
|
394
|
+
class_name, field_name, prop_schema, parent_namespace, import_types)
|
|
395
|
+
|
|
396
|
+
# Add Optional if not required
|
|
397
|
+
if not is_required and not prop_type.startswith('typing.Optional['):
|
|
398
|
+
prop_type = f'typing.Optional[{prop_type}]'
|
|
399
|
+
|
|
400
|
+
# Get source type from structure schema
|
|
401
|
+
source_type = prop_schema.get('type', 'string') if isinstance(prop_schema.get('type'), str) else 'object'
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
'name': field_name,
|
|
405
|
+
'type': prop_type,
|
|
406
|
+
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
407
|
+
'is_enum': prop_type in self.generated_types and self.generated_types[prop_type] == 'enum',
|
|
408
|
+
'is_const': False,
|
|
409
|
+
'source_type': source_type
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
def generate_field_docstring(self, field: Dict, parent_namespace: str) -> str:
|
|
413
|
+
"""Generates a field docstring for a Python dataclass"""
|
|
414
|
+
field_type = field['type']
|
|
415
|
+
field_name = self.safe_name(field['name'])
|
|
416
|
+
field_docstring = f"{field_name} ({field_type})"
|
|
417
|
+
return field_docstring
|
|
418
|
+
|
|
419
|
+
def generate_enum(self, structure_schema: Dict, field_name: str, parent_namespace: str,
|
|
420
|
+
write_file: bool) -> str:
|
|
421
|
+
""" Generates a Python enum from JSON Structure enum """
|
|
422
|
+
# Generate enum name from field name if not provided
|
|
423
|
+
class_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
424
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
425
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
426
|
+
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
427
|
+
|
|
428
|
+
if python_qualified_name in self.generated_types:
|
|
429
|
+
return python_qualified_name
|
|
430
|
+
|
|
431
|
+
symbols = [symbol if not is_python_reserved_word(symbol) else symbol + "_"
|
|
432
|
+
for symbol in structure_schema.get('enum', [])]
|
|
433
|
+
|
|
434
|
+
doc = structure_schema.get('description', structure_schema.get('doc', f'A {class_name} enum.'))
|
|
435
|
+
|
|
436
|
+
enum_definition = process_template(
|
|
437
|
+
"structuretopython/enum_core.jinja",
|
|
438
|
+
class_name=class_name,
|
|
439
|
+
docstring=doc,
|
|
440
|
+
symbols=symbols,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if write_file:
|
|
444
|
+
self.write_to_file(namespace, class_name, enum_definition)
|
|
445
|
+
self.generate_test_enum(namespace, class_name, symbols)
|
|
446
|
+
|
|
447
|
+
self.generated_types[python_qualified_name] = 'enum'
|
|
448
|
+
return python_qualified_name
|
|
449
|
+
|
|
450
|
+
def generate_choice(self, structure_schema: Dict, parent_namespace: str,
|
|
451
|
+
write_file: bool, explicit_name: str = '', import_types: Optional[Set[str]] = None) -> str:
|
|
452
|
+
""" Generates a Python Union type from JSON Structure choice """
|
|
453
|
+
choice_name = explicit_name if explicit_name else structure_schema.get('name', 'UnnamedChoice')
|
|
454
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
455
|
+
if import_types is None:
|
|
456
|
+
import_types = set()
|
|
457
|
+
|
|
458
|
+
# If the choice extends a base class, generate the base and derived classes first
|
|
459
|
+
if '$extends' in structure_schema:
|
|
460
|
+
base_ref = structure_schema['$extends']
|
|
461
|
+
if isinstance(self.schema_doc, dict):
|
|
462
|
+
base_schema = self.resolve_ref(base_ref, self.schema_doc)
|
|
463
|
+
if base_schema:
|
|
464
|
+
# Generate the base class
|
|
465
|
+
ref_path = base_ref.split('/')
|
|
466
|
+
base_name = ref_path[-1]
|
|
467
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
468
|
+
self.generate_class(base_schema, ref_namespace, write_file=True, explicit_name=base_name)
|
|
469
|
+
|
|
470
|
+
# Generate types for each choice
|
|
471
|
+
choice_types = []
|
|
472
|
+
choices = structure_schema.get('choices', {})
|
|
473
|
+
|
|
474
|
+
for choice_key, choice_schema in choices.items():
|
|
475
|
+
if isinstance(choice_schema, dict):
|
|
476
|
+
if '$ref' in choice_schema:
|
|
477
|
+
# Resolve reference and generate the type
|
|
478
|
+
ref_schema = self.resolve_ref(choice_schema['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
|
|
479
|
+
if ref_schema:
|
|
480
|
+
ref_path = choice_schema['$ref'].split('/')
|
|
481
|
+
ref_name = ref_path[-1]
|
|
482
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
483
|
+
qualified_name = self.generate_class(ref_schema, ref_namespace, write_file=True, explicit_name=ref_name)
|
|
484
|
+
import_types.add(qualified_name)
|
|
485
|
+
choice_types.append(qualified_name.split('.')[-1])
|
|
486
|
+
elif 'type' in choice_schema:
|
|
487
|
+
# Generate inline type
|
|
488
|
+
python_type = self.convert_structure_type_to_python(choice_name, choice_key, choice_schema, schema_namespace, import_types)
|
|
489
|
+
choice_types.append(python_type)
|
|
490
|
+
|
|
491
|
+
# Return Union type
|
|
492
|
+
if len(choice_types) == 0:
|
|
493
|
+
return 'typing.Any'
|
|
494
|
+
elif len(choice_types) == 1:
|
|
495
|
+
return choice_types[0]
|
|
496
|
+
else:
|
|
497
|
+
return f"typing.Union[{', '.join(choice_types)}]"
|
|
498
|
+
|
|
499
|
+
def generate_tuple(self, structure_schema: Dict, parent_namespace: str,
|
|
500
|
+
write_file: bool, explicit_name: str = '') -> str:
|
|
501
|
+
""" Generates a Python Tuple type from JSON Structure tuple """
|
|
502
|
+
# For now, return typing.Any as tuples need special handling
|
|
503
|
+
return 'typing.Any'
|
|
504
|
+
|
|
505
|
+
def generate_map_alias(self, structure_schema: Dict, parent_namespace: str,
|
|
506
|
+
write_file: bool) -> str:
|
|
507
|
+
""" Generates a Python TypeAlias for a top-level map type """
|
|
508
|
+
import_types: Set[str] = set()
|
|
509
|
+
|
|
510
|
+
# Get name and namespace
|
|
511
|
+
class_name = pascal(structure_schema.get('name', 'UnnamedMap'))
|
|
512
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
513
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
514
|
+
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
515
|
+
|
|
516
|
+
if python_qualified_name in self.generated_types:
|
|
517
|
+
return python_qualified_name
|
|
518
|
+
|
|
519
|
+
# Get the value type
|
|
520
|
+
values_schema = structure_schema.get('values', {'type': 'any'})
|
|
521
|
+
values_type = self.convert_structure_type_to_python(
|
|
522
|
+
class_name, 'Values', values_schema, schema_namespace, import_types)
|
|
523
|
+
|
|
524
|
+
# Get docstring
|
|
525
|
+
doc = structure_schema.get('description', structure_schema.get('doc', f'A {class_name} map type.'))
|
|
526
|
+
|
|
527
|
+
# Generate the type alias module
|
|
528
|
+
map_definition = process_template(
|
|
529
|
+
"structuretopython/map_alias.jinja",
|
|
530
|
+
class_name=class_name,
|
|
531
|
+
docstring=doc,
|
|
532
|
+
values_type=values_type,
|
|
533
|
+
import_types=import_types,
|
|
534
|
+
base_package=self.base_package
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
if write_file:
|
|
538
|
+
self.write_to_file(namespace, class_name, map_definition)
|
|
539
|
+
|
|
540
|
+
self.generated_types[python_qualified_name] = 'map'
|
|
541
|
+
return python_qualified_name
|
|
542
|
+
|
|
543
|
+
def generate_test_value(self, field: Dict) -> Any:
|
|
544
|
+
"""Generates a test value for a given field"""
|
|
545
|
+
field_type = field['type']
|
|
546
|
+
|
|
547
|
+
def generate_value(field_type: str):
|
|
548
|
+
test_values = {
|
|
549
|
+
'str': chr(39) + ''.join([chr(random.randint(97, 122)) for _ in range(0, 20)]) + chr(39),
|
|
550
|
+
'bool': str(random.choice([True, False])),
|
|
551
|
+
'int': f'int({random.randint(0, 100)})',
|
|
552
|
+
'float': f'float({random.uniform(0, 100)})',
|
|
553
|
+
'bytes': 'b"test_bytes"',
|
|
554
|
+
'None': 'None',
|
|
555
|
+
'datetime.date': 'datetime.date.today()',
|
|
556
|
+
'datetime.datetime': 'datetime.datetime.now(datetime.timezone.utc)',
|
|
557
|
+
'datetime.time': 'datetime.datetime.now(datetime.timezone.utc).time()',
|
|
558
|
+
'decimal.Decimal': f'decimal.Decimal("{random.randint(0, 100)}.{random.randint(0, 100)}")',
|
|
559
|
+
'datetime.timedelta': 'datetime.timedelta(days=1)',
|
|
560
|
+
'uuid.UUID': 'uuid.uuid4()',
|
|
561
|
+
'typing.Any': '{"test": "test"}'
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
def resolve(field_type: str) -> str:
|
|
565
|
+
pattern = re.compile(r'^(?:typing\.)*(Optional|List|Dict|Union|Set)\[(.+)\]$')
|
|
566
|
+
match = pattern.match(field_type)
|
|
567
|
+
if not match:
|
|
568
|
+
return field_type
|
|
569
|
+
outer_type, inner_type = match.groups()
|
|
570
|
+
if outer_type == 'Optional':
|
|
571
|
+
return inner_type
|
|
572
|
+
elif outer_type in ['List', 'Set']:
|
|
573
|
+
return resolve(inner_type)
|
|
574
|
+
elif outer_type == 'Dict':
|
|
575
|
+
_, value_type = inner_type.split(',', 1)
|
|
576
|
+
return resolve(value_type.strip())
|
|
577
|
+
elif outer_type == 'Union':
|
|
578
|
+
first_type = inner_type.split(',', 1)[0]
|
|
579
|
+
return resolve(first_type.strip())
|
|
580
|
+
return field_type
|
|
581
|
+
|
|
582
|
+
if field_type.startswith('typing.Optional['):
|
|
583
|
+
field_type = resolve(field_type)
|
|
584
|
+
|
|
585
|
+
if field_type.startswith('typing.List[') or field_type.startswith('typing.Set['):
|
|
586
|
+
field_type = resolve(field_type)
|
|
587
|
+
array_range = random.randint(1, 5)
|
|
588
|
+
return f"[{', '.join([generate_value(field_type) for _ in range(array_range)])}]"
|
|
589
|
+
elif field_type.startswith('typing.Dict['):
|
|
590
|
+
field_type = resolve(field_type)
|
|
591
|
+
dict_range = random.randint(1, 5)
|
|
592
|
+
dict_data = {}
|
|
593
|
+
for _ in range(dict_range):
|
|
594
|
+
dict_data[''.join([chr(random.randint(97, 122)) for _ in range(0, 20)])] = generate_value(field_type)
|
|
595
|
+
return f"{{{', '.join([chr(39)+key+chr(39)+f': {value}' for key, value in dict_data.items()])}}}"
|
|
596
|
+
elif field_type.startswith('typing.Union['):
|
|
597
|
+
field_type = resolve(field_type)
|
|
598
|
+
return generate_value(field_type)
|
|
599
|
+
return test_values.get(field_type, 'Test_' + field_type + '.create_instance()')
|
|
600
|
+
|
|
601
|
+
return generate_value(field_type)
|
|
602
|
+
|
|
603
|
+
def generate_test_class(self, package_name: str, class_name: str, fields: List[Dict[str, str]],
|
|
604
|
+
import_types: Set[str]) -> None:
|
|
605
|
+
"""Generates a unit test class for a Python dataclass"""
|
|
606
|
+
test_class_name = f"Test_{class_name}"
|
|
607
|
+
tests_package_name = "test_" + package_name.replace('.', '_').lower()
|
|
608
|
+
test_class_definition = process_template(
|
|
609
|
+
"structuretopython/test_class.jinja",
|
|
610
|
+
package_name=package_name,
|
|
611
|
+
class_name=class_name,
|
|
612
|
+
test_class_name=test_class_name,
|
|
613
|
+
fields=fields,
|
|
614
|
+
import_types=import_types,
|
|
615
|
+
avro_annotation=self.avro_annotation,
|
|
616
|
+
dataclasses_json_annotation=self.dataclasses_json_annotation
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
base_dir = os.path.join(self.output_dir, "tests")
|
|
620
|
+
test_file_path = os.path.join(base_dir, f"{tests_package_name.replace('.', '_').lower()}.py")
|
|
621
|
+
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
622
|
+
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
623
|
+
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
624
|
+
file.write(test_class_definition)
|
|
625
|
+
|
|
626
|
+
def generate_test_enum(self, package_name: str, class_name: str, symbols: List[str]) -> None:
|
|
627
|
+
"""Generates a unit test class for a Python enum"""
|
|
628
|
+
test_class_name = f"Test_{class_name}"
|
|
629
|
+
tests_package_name = "test_" + package_name.replace('.', '_').lower()
|
|
630
|
+
test_class_definition = process_template(
|
|
631
|
+
"structuretopython/test_enum.jinja",
|
|
632
|
+
package_name=package_name,
|
|
633
|
+
class_name=class_name,
|
|
634
|
+
test_class_name=test_class_name,
|
|
635
|
+
symbols=symbols
|
|
636
|
+
)
|
|
637
|
+
base_dir = os.path.join(self.output_dir, "tests")
|
|
638
|
+
test_file_path = os.path.join(base_dir, f"{tests_package_name.replace('.', '_').lower()}.py")
|
|
639
|
+
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
640
|
+
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
641
|
+
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
642
|
+
file.write(test_class_definition)
|
|
643
|
+
|
|
644
|
+
def write_to_file(self, package: str, class_name: str, python_code: str):
|
|
645
|
+
"""Writes a Python class to a file"""
|
|
646
|
+
# Add 'struct' module to the package path
|
|
647
|
+
full_package = f"{package}.struct"
|
|
648
|
+
parent_package_name = '.'.join(full_package.split('.')[:-1])
|
|
649
|
+
parent_package_path = os.sep.join(parent_package_name.split('.')).lower()
|
|
650
|
+
directory_path = os.path.join(self.output_dir, "src", parent_package_path)
|
|
651
|
+
if not os.path.exists(directory_path):
|
|
652
|
+
os.makedirs(directory_path, exist_ok=True)
|
|
653
|
+
file_path = os.path.join(directory_path, f"{class_name.lower()}.py")
|
|
654
|
+
|
|
655
|
+
with open(file_path, 'w', encoding='utf-8') as file:
|
|
656
|
+
file.write(python_code)
|
|
657
|
+
|
|
658
|
+
def write_init_files(self):
|
|
659
|
+
"""Writes __init__.py files to the output directories"""
|
|
660
|
+
def organize_generated_types():
|
|
661
|
+
generated_types_tree = {}
|
|
662
|
+
for generated_type, _ in self.generated_types.items():
|
|
663
|
+
parts = generated_type.split('.')
|
|
664
|
+
if len(parts) < 2:
|
|
665
|
+
continue
|
|
666
|
+
class_name = parts[-1]
|
|
667
|
+
module_name = parts[-2]
|
|
668
|
+
package_parts = parts[:-2]
|
|
669
|
+
current_node = generated_types_tree
|
|
670
|
+
for part in package_parts:
|
|
671
|
+
if part not in current_node:
|
|
672
|
+
current_node[part] = {}
|
|
673
|
+
current_node = current_node[part]
|
|
674
|
+
current_node[module_name] = class_name
|
|
675
|
+
return generated_types_tree
|
|
676
|
+
|
|
677
|
+
def collect_class_names(node):
|
|
678
|
+
class_names = []
|
|
679
|
+
for key, value in node.items():
|
|
680
|
+
if isinstance(value, dict):
|
|
681
|
+
class_names.extend(collect_class_names(value))
|
|
682
|
+
else:
|
|
683
|
+
class_names.append(value)
|
|
684
|
+
return class_names
|
|
685
|
+
|
|
686
|
+
def write_init_files_recursive(generated_types_tree, current_package: str):
|
|
687
|
+
import_statements = []
|
|
688
|
+
all_statement = []
|
|
689
|
+
for package_or_module_name, content in generated_types_tree.items():
|
|
690
|
+
if isinstance(content, dict):
|
|
691
|
+
class_names = collect_class_names(content)
|
|
692
|
+
if class_names:
|
|
693
|
+
import_statements.append(f"from .{package_or_module_name} import {', '.join(class_names)}")
|
|
694
|
+
all_statement.extend([f'"{name}"' for name in class_names])
|
|
695
|
+
write_init_files_recursive(content, current_package + ('.' if current_package else '') + package_or_module_name)
|
|
696
|
+
else:
|
|
697
|
+
class_name = content
|
|
698
|
+
import_statements.append(f"from .{package_or_module_name} import {class_name}")
|
|
699
|
+
all_statement.append(f'"{class_name}"')
|
|
700
|
+
if current_package and (import_statements or all_statement):
|
|
701
|
+
package_path = os.path.join(self.output_dir, 'src', current_package.replace('.', os.sep).lower())
|
|
702
|
+
init_file_path = os.path.join(package_path, '__init__.py')
|
|
703
|
+
if not os.path.exists(package_path):
|
|
704
|
+
os.makedirs(package_path, exist_ok=True)
|
|
705
|
+
with open(init_file_path, 'w', encoding='utf-8') as file:
|
|
706
|
+
file.write('\n'.join(import_statements) + '\n\n__all__ = [' + ', '.join(all_statement) + ']\n')
|
|
707
|
+
|
|
708
|
+
write_init_files_recursive(organize_generated_types(), '')
|
|
709
|
+
|
|
710
|
+
def write_pyproject_toml(self):
|
|
711
|
+
"""Writes pyproject.toml file to the output directory"""
|
|
712
|
+
pyproject_content = process_template(
|
|
713
|
+
"structuretopython/pyproject_toml.jinja",
|
|
714
|
+
package_name=self.base_package.replace('_', '-'),
|
|
715
|
+
dataclasses_json_annotation=self.dataclasses_json_annotation,
|
|
716
|
+
avro_annotation=self.avro_annotation
|
|
717
|
+
)
|
|
718
|
+
with open(os.path.join(self.output_dir, 'pyproject.toml'), 'w', encoding='utf-8') as file:
|
|
719
|
+
file.write(pyproject_content)
|
|
720
|
+
|
|
721
|
+
def convert_schemas(self, structure_schemas: List, output_dir: str):
|
|
722
|
+
""" Converts JSON Structure schemas to Python dataclasses"""
|
|
723
|
+
self.output_dir = output_dir
|
|
724
|
+
if not os.path.exists(self.output_dir):
|
|
725
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
726
|
+
|
|
727
|
+
# Register all schema IDs first
|
|
728
|
+
for structure_schema in structure_schemas:
|
|
729
|
+
self.register_schema_ids(structure_schema)
|
|
730
|
+
|
|
731
|
+
for structure_schema in structure_schemas:
|
|
732
|
+
self.schema_doc = structure_schema
|
|
733
|
+
if 'definitions' in structure_schema:
|
|
734
|
+
self.definitions = structure_schema['definitions']
|
|
735
|
+
|
|
736
|
+
if 'enum' in structure_schema:
|
|
737
|
+
self.generate_enum(structure_schema, structure_schema.get('name', 'Enum'),
|
|
738
|
+
structure_schema.get('namespace', ''), write_file=True)
|
|
739
|
+
elif structure_schema.get('type') == 'object':
|
|
740
|
+
self.generate_class(structure_schema, structure_schema.get('namespace', ''), write_file=True)
|
|
741
|
+
elif structure_schema.get('type') == 'choice':
|
|
742
|
+
self.generate_choice(structure_schema, structure_schema.get('namespace', ''), write_file=True)
|
|
743
|
+
elif structure_schema.get('type') == 'map':
|
|
744
|
+
self.generate_map_alias(structure_schema, structure_schema.get('namespace', ''), write_file=True)
|
|
745
|
+
|
|
746
|
+
self.write_init_files()
|
|
747
|
+
self.write_pyproject_toml()
|
|
748
|
+
|
|
749
|
+
def convert(self, structure_schema_path: str, output_dir: str):
|
|
750
|
+
"""Converts JSON Structure schema to Python dataclasses"""
|
|
751
|
+
with open(structure_schema_path, 'r', encoding='utf-8') as file:
|
|
752
|
+
schema = json.load(file)
|
|
753
|
+
if isinstance(schema, dict):
|
|
754
|
+
schema = [schema]
|
|
755
|
+
return self.convert_schemas(schema, output_dir)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def convert_structure_to_python(structure_schema_path, py_file_path, package_name='', dataclasses_json_annotation=False, avro_annotation=False):
|
|
759
|
+
"""Converts JSON Structure schema to Python dataclasses"""
|
|
760
|
+
if not package_name:
|
|
761
|
+
package_name = os.path.splitext(os.path.basename(structure_schema_path))[0].lower().replace('-', '_')
|
|
762
|
+
|
|
763
|
+
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
764
|
+
structure_to_python.convert(structure_schema_path, py_file_path)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def convert_structure_schema_to_python(structure_schema, py_file_path, package_name='', dataclasses_json_annotation=False):
|
|
768
|
+
"""Converts JSON Structure schema to Python dataclasses"""
|
|
769
|
+
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation)
|
|
770
|
+
if isinstance(structure_schema, dict):
|
|
771
|
+
structure_schema = [structure_schema]
|
|
772
|
+
structure_to_python.convert_schemas(structure_schema, py_file_path)
|