structurize 2.16.2__py3-none-any.whl → 2.16.5__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 +63 -63
- avrotize/__main__.py +5 -5
- avrotize/_version.py +34 -34
- avrotize/asn1toavro.py +160 -160
- avrotize/avrotize.py +152 -152
- avrotize/avrotocpp.py +483 -483
- avrotize/avrotocsharp.py +992 -992
- avrotize/avrotocsv.py +121 -121
- avrotize/avrotodatapackage.py +173 -173
- avrotize/avrotodb.py +1383 -1383
- avrotize/avrotogo.py +476 -476
- avrotize/avrotographql.py +197 -197
- avrotize/avrotoiceberg.py +210 -210
- avrotize/avrotojava.py +1023 -1023
- avrotize/avrotojs.py +250 -250
- avrotize/avrotojsons.py +481 -481
- avrotize/avrotojstruct.py +345 -345
- avrotize/avrotokusto.py +363 -363
- avrotize/avrotomd.py +137 -137
- avrotize/avrotools.py +168 -168
- avrotize/avrotoparquet.py +208 -208
- avrotize/avrotoproto.py +358 -358
- avrotize/avrotopython.py +622 -622
- avrotize/avrotorust.py +435 -435
- avrotize/avrotots.py +598 -598
- avrotize/avrotoxsd.py +344 -344
- avrotize/commands.json +2493 -2433
- avrotize/common.py +828 -828
- avrotize/constants.py +4 -4
- avrotize/csvtoavro.py +131 -131
- avrotize/datapackagetoavro.py +76 -76
- avrotize/dependency_resolver.py +348 -348
- avrotize/jsonstoavro.py +1698 -1698
- avrotize/jsonstostructure.py +2642 -2642
- avrotize/jstructtoavro.py +878 -878
- avrotize/kstructtoavro.py +93 -93
- avrotize/kustotoavro.py +455 -455
- avrotize/parquettoavro.py +157 -157
- avrotize/proto2parser.py +497 -497
- avrotize/proto3parser.py +402 -402
- avrotize/prototoavro.py +382 -382
- avrotize/structuretocsharp.py +2005 -2005
- avrotize/structuretojsons.py +498 -498
- avrotize/structuretopython.py +772 -772
- avrotize/structuretots.py +653 -0
- avrotize/xsdtoavro.py +413 -413
- {structurize-2.16.2.dist-info → structurize-2.16.5.dist-info}/METADATA +848 -805
- structurize-2.16.5.dist-info/RECORD +52 -0
- {structurize-2.16.2.dist-info → structurize-2.16.5.dist-info}/licenses/LICENSE +200 -200
- structurize-2.16.2.dist-info/RECORD +0 -51
- {structurize-2.16.2.dist-info → structurize-2.16.5.dist-info}/WHEEL +0 -0
- {structurize-2.16.2.dist-info → structurize-2.16.5.dist-info}/entry_points.txt +0 -0
- {structurize-2.16.2.dist-info → structurize-2.16.5.dist-info}/top_level.txt +0 -0
avrotize/avrotojs.py
CHANGED
|
@@ -1,250 +1,250 @@
|
|
|
1
|
-
""" Convert Avro schema to TypeScript classes """
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
from typing import Any, Dict, List, Set, Union
|
|
6
|
-
|
|
7
|
-
from avrotize.common import pascal
|
|
8
|
-
|
|
9
|
-
INDENT = ' ' * 4
|
|
10
|
-
|
|
11
|
-
def is_javascript_reserved_word(word: str) -> bool:
|
|
12
|
-
""" Check if word is a TypeScript reserved word """
|
|
13
|
-
reserved_words = [
|
|
14
|
-
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
15
|
-
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
|
|
16
|
-
'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return',
|
|
17
|
-
'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
|
|
18
|
-
'while', 'with', 'yield'
|
|
19
|
-
]
|
|
20
|
-
return word in reserved_words
|
|
21
|
-
|
|
22
|
-
def is_javascript_primitive(word: str) -> bool:
|
|
23
|
-
""" Check if word is a TypeScript primitive """
|
|
24
|
-
primitives = ['null', 'boolean', 'number', 'string', 'Date']
|
|
25
|
-
return word in primitives
|
|
26
|
-
|
|
27
|
-
class AvroToJavaScript:
|
|
28
|
-
""" Convert Avro schema to TypeScript classes """
|
|
29
|
-
|
|
30
|
-
def __init__(self, base_package: str = '', avro_annotation=False) -> None:
|
|
31
|
-
self.base_package = base_package
|
|
32
|
-
self.avro_annotation = avro_annotation
|
|
33
|
-
self.output_dir = os.getcwd()
|
|
34
|
-
|
|
35
|
-
def map_primitive_to_javascript(self, avro_type: str) -> str:
|
|
36
|
-
""" Map Avro primitive type to TypeScript type """
|
|
37
|
-
mapping = {
|
|
38
|
-
'null': 'null',
|
|
39
|
-
'boolean': 'boolean',
|
|
40
|
-
'int': 'number',
|
|
41
|
-
'long': 'number',
|
|
42
|
-
'float': 'number',
|
|
43
|
-
'double': 'number',
|
|
44
|
-
'bytes': 'string',
|
|
45
|
-
'string': 'string',
|
|
46
|
-
}
|
|
47
|
-
return mapping.get(avro_type, avro_type)
|
|
48
|
-
|
|
49
|
-
def convert_logical_type_to_javascript(self, avro_type: Dict) -> str:
|
|
50
|
-
""" Convert Avro logical type to TypeScript type """
|
|
51
|
-
if 'logicalType' in avro_type:
|
|
52
|
-
if avro_type['logicalType'] in ['decimal', 'uuid']:
|
|
53
|
-
return 'string'
|
|
54
|
-
if avro_type['logicalType'] in ['date', 'time-millis', 'time-micros', 'timestamp-millis', 'timestamp-micros']:
|
|
55
|
-
return 'Date'
|
|
56
|
-
if avro_type['logicalType'] == 'duration':
|
|
57
|
-
return 'string'
|
|
58
|
-
return 'any'
|
|
59
|
-
|
|
60
|
-
def is_javascript_primitive(self, avro_type: str) -> bool:
|
|
61
|
-
""" Check if Avro type is a TypeScript primitive """
|
|
62
|
-
return avro_type in ['null', 'boolean', 'number', 'string', 'Date']
|
|
63
|
-
|
|
64
|
-
def convert_avro_type_to_javascript(self, avro_type: Union[str, Dict, List], parent_namespace: str, import_types: set) -> str | List[Any]:
|
|
65
|
-
""" Convert Avro type to TypeScript type """
|
|
66
|
-
if isinstance(avro_type, str):
|
|
67
|
-
mapped_type = self.map_primitive_to_javascript(avro_type)
|
|
68
|
-
if mapped_type == avro_type and not self.is_javascript_primitive(mapped_type):
|
|
69
|
-
import_types.add(mapped_type)
|
|
70
|
-
return pascal(mapped_type.split('.')[-1])
|
|
71
|
-
return mapped_type
|
|
72
|
-
elif isinstance(avro_type, list):
|
|
73
|
-
return [self.convert_avro_type_to_javascript(t, parent_namespace, import_types) for t in avro_type]
|
|
74
|
-
elif isinstance(avro_type, dict):
|
|
75
|
-
if 'type' in avro_type and avro_type['type'] == 'record':
|
|
76
|
-
class_ref = self.generate_class(avro_type, parent_namespace)
|
|
77
|
-
import_types.add(class_ref)
|
|
78
|
-
return class_ref.split('.')[-1]
|
|
79
|
-
elif 'type' in avro_type and avro_type['type'] == 'enum':
|
|
80
|
-
enum_ref = self.generate_enum(avro_type, parent_namespace)
|
|
81
|
-
import_types.add(enum_ref)
|
|
82
|
-
return enum_ref.split('.')[-1]
|
|
83
|
-
elif 'type' in avro_type and avro_type['type'] == 'array':
|
|
84
|
-
return [self.convert_avro_type_to_javascript(avro_type["items"], parent_namespace, import_types)]
|
|
85
|
-
if 'type' in avro_type and avro_type['type'] == 'map':
|
|
86
|
-
return [self.convert_avro_type_to_javascript(avro_type["values"], parent_namespace, import_types)]
|
|
87
|
-
if 'logicalType' in avro_type:
|
|
88
|
-
return self.convert_logical_type_to_javascript(avro_type)
|
|
89
|
-
return self.convert_avro_type_to_javascript(avro_type['type'], parent_namespace, import_types)
|
|
90
|
-
return 'any'
|
|
91
|
-
|
|
92
|
-
def generate_class(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
93
|
-
""" Generate TypeScript class from Avro record """
|
|
94
|
-
|
|
95
|
-
def add_check(arr_check, ft):
|
|
96
|
-
if isinstance(ft, list):
|
|
97
|
-
for ft2 in ft:
|
|
98
|
-
add_check(arr_check, ft2)
|
|
99
|
-
elif ft == 'null':
|
|
100
|
-
arr_check.append('v !== null')
|
|
101
|
-
elif is_javascript_primitive(ft):
|
|
102
|
-
arr_check.append(f'typeof v !== "{ft}"')
|
|
103
|
-
else:
|
|
104
|
-
arr_check.append(f'!(v instanceof {ft})')
|
|
105
|
-
|
|
106
|
-
import_types: Set[str] = set()
|
|
107
|
-
class_name = pascal(avro_schema['name'])
|
|
108
|
-
namespace = avro_schema.get('namespace', '')
|
|
109
|
-
if not namespace and parent_namespace:
|
|
110
|
-
namespace = parent_namespace
|
|
111
|
-
if self.base_package:
|
|
112
|
-
namespace = f'{self.base_package}.{namespace}'
|
|
113
|
-
fields = avro_schema.get('fields', [])
|
|
114
|
-
doc = avro_schema.get('doc', '')
|
|
115
|
-
|
|
116
|
-
constructor_body = ''
|
|
117
|
-
class_body = ''
|
|
118
|
-
if self.avro_annotation:
|
|
119
|
-
avro_schema_json = json.dumps(avro_schema)
|
|
120
|
-
avro_schema_json = avro_schema_json.replace('"', '§')
|
|
121
|
-
avro_schema_json = f"\"+\n{' '*8}\"".join([avro_schema_json[i:i+80] for i in range(0, len(avro_schema_json), 80)])
|
|
122
|
-
avro_schema_json = avro_schema_json.replace('§', '\\"')
|
|
123
|
-
class_body += f'{class_name}.AvroType = avro.parse("{avro_schema_json}");\n'
|
|
124
|
-
for field in fields:
|
|
125
|
-
field_name = field['name']
|
|
126
|
-
field_doc = field.get('doc', '')
|
|
127
|
-
field_avro_type = field['type']
|
|
128
|
-
if is_javascript_reserved_word(field_name):
|
|
129
|
-
field_name += '_'
|
|
130
|
-
field_type = self.convert_avro_type_to_javascript(field['type'], namespace, import_types)
|
|
131
|
-
if field_doc:
|
|
132
|
-
constructor_body += f'{INDENT}/** {field_doc} */\n'
|
|
133
|
-
constructor_body += f'{INDENT}_{field_name} = null;\n'
|
|
134
|
-
class_body += f'Object.defineProperty({class_name}.prototype, "{field_name}", {{\n'
|
|
135
|
-
class_body += f'{INDENT}get: function() {{'+'\n'
|
|
136
|
-
class_body += f'{INDENT}{INDENT}return this._{field_name};\n'
|
|
137
|
-
class_body += f'{INDENT}}},\n'
|
|
138
|
-
class_body += f'{INDENT}set: function(value) {{'+'\n'
|
|
139
|
-
type_check = []
|
|
140
|
-
if field_avro_type == 'array':
|
|
141
|
-
arr = '!Array.isArray(value) || value.some(v => '
|
|
142
|
-
arr_check: List[str] = []
|
|
143
|
-
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
144
|
-
add_check(arr_check, ft)
|
|
145
|
-
arr += ' || '.join(arr_check) + ')'
|
|
146
|
-
type_check.append(arr)
|
|
147
|
-
elif field_avro_type == 'map':
|
|
148
|
-
map = '!Object.entries(value).every(([k, v]) => typeof k === "string" && '
|
|
149
|
-
map_check: List[str] = []
|
|
150
|
-
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
151
|
-
add_check(map_check, ft)
|
|
152
|
-
map += ' && '.join(map_check) + ')'
|
|
153
|
-
type_check.append(map)
|
|
154
|
-
else:
|
|
155
|
-
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
156
|
-
add_check(type_check, ft)
|
|
157
|
-
class_body += f'{INDENT}{INDENT}if ( {" && ".join(type_check)} ) throw new Error(`Invalid type for {field_name}. Expected {field_type}, got ${{value}}`);\n'
|
|
158
|
-
class_body += f'{INDENT}{INDENT}this._{field_name} = value;\n'
|
|
159
|
-
class_body += f'{INDENT}}}'+'\n'
|
|
160
|
-
class_body += '});\n\n'
|
|
161
|
-
|
|
162
|
-
imports = ''
|
|
163
|
-
if self.avro_annotation:
|
|
164
|
-
imports += "var avro = require('avro-js');\n"
|
|
165
|
-
for import_type in import_types:
|
|
166
|
-
import_type_package = import_type.rsplit('.',1)[0]
|
|
167
|
-
import_type_type = pascal(import_type.split('.')[-1])
|
|
168
|
-
import_type_package = import_type_package.replace('.', '/')
|
|
169
|
-
namespace_path = namespace.replace('.', '/')
|
|
170
|
-
|
|
171
|
-
if import_type_package:# get the relative path from the namespace to the import_type_package
|
|
172
|
-
import_type_package = os.path.relpath(import_type_package, namespace_path).replace(os.sep, '/')
|
|
173
|
-
if not import_type_package.startswith('.'):
|
|
174
|
-
import_type_package = f'./{import_type_package}'
|
|
175
|
-
imports += f"var {import_type_type} = require('{import_type_package}/{import_type_type}').{import_type_type};\n"
|
|
176
|
-
else:
|
|
177
|
-
imports += f"var {import_type_type} = require('{import_type_type}'){import_type_type};\n"
|
|
178
|
-
|
|
179
|
-
class_definition = imports + '\n'
|
|
180
|
-
if doc:
|
|
181
|
-
class_definition += f'/** {doc} */\n'
|
|
182
|
-
class_definition += f"function {class_name}() {{\n{constructor_body}}}\n\n{class_body}\nmodule.exports = {class_name};\n"
|
|
183
|
-
self.write_to_file(namespace, class_name, class_definition)
|
|
184
|
-
return f'{namespace}.{class_name}'
|
|
185
|
-
|
|
186
|
-
def generate_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
187
|
-
""" Generate TypeScript enum from Avro enum """
|
|
188
|
-
enum_name = pascal(avro_schema['name'])
|
|
189
|
-
namespace = avro_schema.get('namespace', '')
|
|
190
|
-
if not namespace and parent_namespace:
|
|
191
|
-
namespace = parent_namespace
|
|
192
|
-
if self.base_package:
|
|
193
|
-
namespace = f'{self.base_package}.{namespace}'
|
|
194
|
-
symbols = avro_schema.get('symbols', [])
|
|
195
|
-
|
|
196
|
-
enum_body = ''
|
|
197
|
-
for symbol in symbols:
|
|
198
|
-
if is_javascript_reserved_word(symbol):
|
|
199
|
-
symbol += '_'
|
|
200
|
-
enum_body += f'{INDENT}{symbol}: "{symbol}",\n'
|
|
201
|
-
|
|
202
|
-
enum_definition = ''
|
|
203
|
-
if 'doc' in avro_schema:
|
|
204
|
-
enum_definition += f"/** {avro_schema['doc']} */\n"
|
|
205
|
-
enum_definition += f"const {enum_name} = Object.freeze({{\n{enum_body}}});\n\n"
|
|
206
|
-
enum_definition += f"module.exports = {enum_name};\n"
|
|
207
|
-
self.write_to_file(namespace, enum_name, enum_definition)
|
|
208
|
-
return f'{namespace}.{enum_name}'
|
|
209
|
-
|
|
210
|
-
def write_to_file(self, namespace: str, name: str, content: str):
|
|
211
|
-
""" Write TypeScript class to file """
|
|
212
|
-
directory_path = os.path.join(self.output_dir, namespace.replace('.', os.sep))
|
|
213
|
-
if not os.path.exists(directory_path):
|
|
214
|
-
os.makedirs(directory_path, exist_ok=True)
|
|
215
|
-
|
|
216
|
-
file_path = os.path.join(directory_path, f"{name}.js")
|
|
217
|
-
with open(file_path, 'w', encoding='utf-8') as file:
|
|
218
|
-
file.write(content)
|
|
219
|
-
|
|
220
|
-
def convert_schema(self, schema: List|Dict, output_dir: str):
|
|
221
|
-
""" Convert Avro schema to TypeScript classes """
|
|
222
|
-
self.output_dir = output_dir
|
|
223
|
-
if isinstance(schema, dict):
|
|
224
|
-
schema = [schema]
|
|
225
|
-
|
|
226
|
-
for avro_schema in schema:
|
|
227
|
-
if avro_schema['type'] == 'record':
|
|
228
|
-
self.generate_class(avro_schema, self.base_package)
|
|
229
|
-
elif avro_schema['type'] == 'enum':
|
|
230
|
-
self.generate_enum(avro_schema, self.base_package)
|
|
231
|
-
|
|
232
|
-
def convert(self, avro_schema_path: str, output_dir: str):
|
|
233
|
-
""" Convert Avro schema to TypeScript classes """
|
|
234
|
-
with open(avro_schema_path, 'r', encoding='utf-8') as file:
|
|
235
|
-
schema = json.load(file)
|
|
236
|
-
self.convert_schema(schema, output_dir)
|
|
237
|
-
|
|
238
|
-
def convert_avro_to_javascript(avro_schema_path, js_dir_path, package_name='', avro_annotation=False):
|
|
239
|
-
""" Convert Avro schema to TypeScript classes """
|
|
240
|
-
|
|
241
|
-
if not package_name:
|
|
242
|
-
package_name = os.path.splitext(os.path.basename(avro_schema_path))[0].replace('-', '_')
|
|
243
|
-
|
|
244
|
-
converter = AvroToJavaScript(package_name, avro_annotation=avro_annotation)
|
|
245
|
-
converter.convert(avro_schema_path, js_dir_path)
|
|
246
|
-
|
|
247
|
-
def convert_avro_schema_to_javascript(avro_schema, js_dir_path, package_name='', avro_annotation=False):
|
|
248
|
-
""" Convert Avro schema to TypeScript classes """
|
|
249
|
-
converter = AvroToJavaScript(package_name, avro_annotation=avro_annotation)
|
|
250
|
-
converter.convert_schema(avro_schema, js_dir_path)
|
|
1
|
+
""" Convert Avro schema to TypeScript classes """
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, List, Set, Union
|
|
6
|
+
|
|
7
|
+
from avrotize.common import pascal
|
|
8
|
+
|
|
9
|
+
INDENT = ' ' * 4
|
|
10
|
+
|
|
11
|
+
def is_javascript_reserved_word(word: str) -> bool:
|
|
12
|
+
""" Check if word is a TypeScript reserved word """
|
|
13
|
+
reserved_words = [
|
|
14
|
+
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
15
|
+
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
|
|
16
|
+
'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return',
|
|
17
|
+
'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
|
|
18
|
+
'while', 'with', 'yield'
|
|
19
|
+
]
|
|
20
|
+
return word in reserved_words
|
|
21
|
+
|
|
22
|
+
def is_javascript_primitive(word: str) -> bool:
|
|
23
|
+
""" Check if word is a TypeScript primitive """
|
|
24
|
+
primitives = ['null', 'boolean', 'number', 'string', 'Date']
|
|
25
|
+
return word in primitives
|
|
26
|
+
|
|
27
|
+
class AvroToJavaScript:
|
|
28
|
+
""" Convert Avro schema to TypeScript classes """
|
|
29
|
+
|
|
30
|
+
def __init__(self, base_package: str = '', avro_annotation=False) -> None:
|
|
31
|
+
self.base_package = base_package
|
|
32
|
+
self.avro_annotation = avro_annotation
|
|
33
|
+
self.output_dir = os.getcwd()
|
|
34
|
+
|
|
35
|
+
def map_primitive_to_javascript(self, avro_type: str) -> str:
|
|
36
|
+
""" Map Avro primitive type to TypeScript type """
|
|
37
|
+
mapping = {
|
|
38
|
+
'null': 'null',
|
|
39
|
+
'boolean': 'boolean',
|
|
40
|
+
'int': 'number',
|
|
41
|
+
'long': 'number',
|
|
42
|
+
'float': 'number',
|
|
43
|
+
'double': 'number',
|
|
44
|
+
'bytes': 'string',
|
|
45
|
+
'string': 'string',
|
|
46
|
+
}
|
|
47
|
+
return mapping.get(avro_type, avro_type)
|
|
48
|
+
|
|
49
|
+
def convert_logical_type_to_javascript(self, avro_type: Dict) -> str:
|
|
50
|
+
""" Convert Avro logical type to TypeScript type """
|
|
51
|
+
if 'logicalType' in avro_type:
|
|
52
|
+
if avro_type['logicalType'] in ['decimal', 'uuid']:
|
|
53
|
+
return 'string'
|
|
54
|
+
if avro_type['logicalType'] in ['date', 'time-millis', 'time-micros', 'timestamp-millis', 'timestamp-micros']:
|
|
55
|
+
return 'Date'
|
|
56
|
+
if avro_type['logicalType'] == 'duration':
|
|
57
|
+
return 'string'
|
|
58
|
+
return 'any'
|
|
59
|
+
|
|
60
|
+
def is_javascript_primitive(self, avro_type: str) -> bool:
|
|
61
|
+
""" Check if Avro type is a TypeScript primitive """
|
|
62
|
+
return avro_type in ['null', 'boolean', 'number', 'string', 'Date']
|
|
63
|
+
|
|
64
|
+
def convert_avro_type_to_javascript(self, avro_type: Union[str, Dict, List], parent_namespace: str, import_types: set) -> str | List[Any]:
|
|
65
|
+
""" Convert Avro type to TypeScript type """
|
|
66
|
+
if isinstance(avro_type, str):
|
|
67
|
+
mapped_type = self.map_primitive_to_javascript(avro_type)
|
|
68
|
+
if mapped_type == avro_type and not self.is_javascript_primitive(mapped_type):
|
|
69
|
+
import_types.add(mapped_type)
|
|
70
|
+
return pascal(mapped_type.split('.')[-1])
|
|
71
|
+
return mapped_type
|
|
72
|
+
elif isinstance(avro_type, list):
|
|
73
|
+
return [self.convert_avro_type_to_javascript(t, parent_namespace, import_types) for t in avro_type]
|
|
74
|
+
elif isinstance(avro_type, dict):
|
|
75
|
+
if 'type' in avro_type and avro_type['type'] == 'record':
|
|
76
|
+
class_ref = self.generate_class(avro_type, parent_namespace)
|
|
77
|
+
import_types.add(class_ref)
|
|
78
|
+
return class_ref.split('.')[-1]
|
|
79
|
+
elif 'type' in avro_type and avro_type['type'] == 'enum':
|
|
80
|
+
enum_ref = self.generate_enum(avro_type, parent_namespace)
|
|
81
|
+
import_types.add(enum_ref)
|
|
82
|
+
return enum_ref.split('.')[-1]
|
|
83
|
+
elif 'type' in avro_type and avro_type['type'] == 'array':
|
|
84
|
+
return [self.convert_avro_type_to_javascript(avro_type["items"], parent_namespace, import_types)]
|
|
85
|
+
if 'type' in avro_type and avro_type['type'] == 'map':
|
|
86
|
+
return [self.convert_avro_type_to_javascript(avro_type["values"], parent_namespace, import_types)]
|
|
87
|
+
if 'logicalType' in avro_type:
|
|
88
|
+
return self.convert_logical_type_to_javascript(avro_type)
|
|
89
|
+
return self.convert_avro_type_to_javascript(avro_type['type'], parent_namespace, import_types)
|
|
90
|
+
return 'any'
|
|
91
|
+
|
|
92
|
+
def generate_class(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
93
|
+
""" Generate TypeScript class from Avro record """
|
|
94
|
+
|
|
95
|
+
def add_check(arr_check, ft):
|
|
96
|
+
if isinstance(ft, list):
|
|
97
|
+
for ft2 in ft:
|
|
98
|
+
add_check(arr_check, ft2)
|
|
99
|
+
elif ft == 'null':
|
|
100
|
+
arr_check.append('v !== null')
|
|
101
|
+
elif is_javascript_primitive(ft):
|
|
102
|
+
arr_check.append(f'typeof v !== "{ft}"')
|
|
103
|
+
else:
|
|
104
|
+
arr_check.append(f'!(v instanceof {ft})')
|
|
105
|
+
|
|
106
|
+
import_types: Set[str] = set()
|
|
107
|
+
class_name = pascal(avro_schema['name'])
|
|
108
|
+
namespace = avro_schema.get('namespace', '')
|
|
109
|
+
if not namespace and parent_namespace:
|
|
110
|
+
namespace = parent_namespace
|
|
111
|
+
if self.base_package:
|
|
112
|
+
namespace = f'{self.base_package}.{namespace}'
|
|
113
|
+
fields = avro_schema.get('fields', [])
|
|
114
|
+
doc = avro_schema.get('doc', '')
|
|
115
|
+
|
|
116
|
+
constructor_body = ''
|
|
117
|
+
class_body = ''
|
|
118
|
+
if self.avro_annotation:
|
|
119
|
+
avro_schema_json = json.dumps(avro_schema)
|
|
120
|
+
avro_schema_json = avro_schema_json.replace('"', '§')
|
|
121
|
+
avro_schema_json = f"\"+\n{' '*8}\"".join([avro_schema_json[i:i+80] for i in range(0, len(avro_schema_json), 80)])
|
|
122
|
+
avro_schema_json = avro_schema_json.replace('§', '\\"')
|
|
123
|
+
class_body += f'{class_name}.AvroType = avro.parse("{avro_schema_json}");\n'
|
|
124
|
+
for field in fields:
|
|
125
|
+
field_name = field['name']
|
|
126
|
+
field_doc = field.get('doc', '')
|
|
127
|
+
field_avro_type = field['type']
|
|
128
|
+
if is_javascript_reserved_word(field_name):
|
|
129
|
+
field_name += '_'
|
|
130
|
+
field_type = self.convert_avro_type_to_javascript(field['type'], namespace, import_types)
|
|
131
|
+
if field_doc:
|
|
132
|
+
constructor_body += f'{INDENT}/** {field_doc} */\n'
|
|
133
|
+
constructor_body += f'{INDENT}_{field_name} = null;\n'
|
|
134
|
+
class_body += f'Object.defineProperty({class_name}.prototype, "{field_name}", {{\n'
|
|
135
|
+
class_body += f'{INDENT}get: function() {{'+'\n'
|
|
136
|
+
class_body += f'{INDENT}{INDENT}return this._{field_name};\n'
|
|
137
|
+
class_body += f'{INDENT}}},\n'
|
|
138
|
+
class_body += f'{INDENT}set: function(value) {{'+'\n'
|
|
139
|
+
type_check = []
|
|
140
|
+
if field_avro_type == 'array':
|
|
141
|
+
arr = '!Array.isArray(value) || value.some(v => '
|
|
142
|
+
arr_check: List[str] = []
|
|
143
|
+
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
144
|
+
add_check(arr_check, ft)
|
|
145
|
+
arr += ' || '.join(arr_check) + ')'
|
|
146
|
+
type_check.append(arr)
|
|
147
|
+
elif field_avro_type == 'map':
|
|
148
|
+
map = '!Object.entries(value).every(([k, v]) => typeof k === "string" && '
|
|
149
|
+
map_check: List[str] = []
|
|
150
|
+
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
151
|
+
add_check(map_check, ft)
|
|
152
|
+
map += ' && '.join(map_check) + ')'
|
|
153
|
+
type_check.append(map)
|
|
154
|
+
else:
|
|
155
|
+
for ft in field_type if isinstance(field_type, list) else [field_type]:
|
|
156
|
+
add_check(type_check, ft)
|
|
157
|
+
class_body += f'{INDENT}{INDENT}if ( {" && ".join(type_check)} ) throw new Error(`Invalid type for {field_name}. Expected {field_type}, got ${{value}}`);\n'
|
|
158
|
+
class_body += f'{INDENT}{INDENT}this._{field_name} = value;\n'
|
|
159
|
+
class_body += f'{INDENT}}}'+'\n'
|
|
160
|
+
class_body += '});\n\n'
|
|
161
|
+
|
|
162
|
+
imports = ''
|
|
163
|
+
if self.avro_annotation:
|
|
164
|
+
imports += "var avro = require('avro-js');\n"
|
|
165
|
+
for import_type in import_types:
|
|
166
|
+
import_type_package = import_type.rsplit('.',1)[0]
|
|
167
|
+
import_type_type = pascal(import_type.split('.')[-1])
|
|
168
|
+
import_type_package = import_type_package.replace('.', '/')
|
|
169
|
+
namespace_path = namespace.replace('.', '/')
|
|
170
|
+
|
|
171
|
+
if import_type_package:# get the relative path from the namespace to the import_type_package
|
|
172
|
+
import_type_package = os.path.relpath(import_type_package, namespace_path).replace(os.sep, '/')
|
|
173
|
+
if not import_type_package.startswith('.'):
|
|
174
|
+
import_type_package = f'./{import_type_package}'
|
|
175
|
+
imports += f"var {import_type_type} = require('{import_type_package}/{import_type_type}').{import_type_type};\n"
|
|
176
|
+
else:
|
|
177
|
+
imports += f"var {import_type_type} = require('{import_type_type}'){import_type_type};\n"
|
|
178
|
+
|
|
179
|
+
class_definition = imports + '\n'
|
|
180
|
+
if doc:
|
|
181
|
+
class_definition += f'/** {doc} */\n'
|
|
182
|
+
class_definition += f"function {class_name}() {{\n{constructor_body}}}\n\n{class_body}\nmodule.exports = {class_name};\n"
|
|
183
|
+
self.write_to_file(namespace, class_name, class_definition)
|
|
184
|
+
return f'{namespace}.{class_name}'
|
|
185
|
+
|
|
186
|
+
def generate_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
187
|
+
""" Generate TypeScript enum from Avro enum """
|
|
188
|
+
enum_name = pascal(avro_schema['name'])
|
|
189
|
+
namespace = avro_schema.get('namespace', '')
|
|
190
|
+
if not namespace and parent_namespace:
|
|
191
|
+
namespace = parent_namespace
|
|
192
|
+
if self.base_package:
|
|
193
|
+
namespace = f'{self.base_package}.{namespace}'
|
|
194
|
+
symbols = avro_schema.get('symbols', [])
|
|
195
|
+
|
|
196
|
+
enum_body = ''
|
|
197
|
+
for symbol in symbols:
|
|
198
|
+
if is_javascript_reserved_word(symbol):
|
|
199
|
+
symbol += '_'
|
|
200
|
+
enum_body += f'{INDENT}{symbol}: "{symbol}",\n'
|
|
201
|
+
|
|
202
|
+
enum_definition = ''
|
|
203
|
+
if 'doc' in avro_schema:
|
|
204
|
+
enum_definition += f"/** {avro_schema['doc']} */\n"
|
|
205
|
+
enum_definition += f"const {enum_name} = Object.freeze({{\n{enum_body}}});\n\n"
|
|
206
|
+
enum_definition += f"module.exports = {enum_name};\n"
|
|
207
|
+
self.write_to_file(namespace, enum_name, enum_definition)
|
|
208
|
+
return f'{namespace}.{enum_name}'
|
|
209
|
+
|
|
210
|
+
def write_to_file(self, namespace: str, name: str, content: str):
|
|
211
|
+
""" Write TypeScript class to file """
|
|
212
|
+
directory_path = os.path.join(self.output_dir, namespace.replace('.', os.sep))
|
|
213
|
+
if not os.path.exists(directory_path):
|
|
214
|
+
os.makedirs(directory_path, exist_ok=True)
|
|
215
|
+
|
|
216
|
+
file_path = os.path.join(directory_path, f"{name}.js")
|
|
217
|
+
with open(file_path, 'w', encoding='utf-8') as file:
|
|
218
|
+
file.write(content)
|
|
219
|
+
|
|
220
|
+
def convert_schema(self, schema: List|Dict, output_dir: str):
|
|
221
|
+
""" Convert Avro schema to TypeScript classes """
|
|
222
|
+
self.output_dir = output_dir
|
|
223
|
+
if isinstance(schema, dict):
|
|
224
|
+
schema = [schema]
|
|
225
|
+
|
|
226
|
+
for avro_schema in schema:
|
|
227
|
+
if avro_schema['type'] == 'record':
|
|
228
|
+
self.generate_class(avro_schema, self.base_package)
|
|
229
|
+
elif avro_schema['type'] == 'enum':
|
|
230
|
+
self.generate_enum(avro_schema, self.base_package)
|
|
231
|
+
|
|
232
|
+
def convert(self, avro_schema_path: str, output_dir: str):
|
|
233
|
+
""" Convert Avro schema to TypeScript classes """
|
|
234
|
+
with open(avro_schema_path, 'r', encoding='utf-8') as file:
|
|
235
|
+
schema = json.load(file)
|
|
236
|
+
self.convert_schema(schema, output_dir)
|
|
237
|
+
|
|
238
|
+
def convert_avro_to_javascript(avro_schema_path, js_dir_path, package_name='', avro_annotation=False):
|
|
239
|
+
""" Convert Avro schema to TypeScript classes """
|
|
240
|
+
|
|
241
|
+
if not package_name:
|
|
242
|
+
package_name = os.path.splitext(os.path.basename(avro_schema_path))[0].replace('-', '_')
|
|
243
|
+
|
|
244
|
+
converter = AvroToJavaScript(package_name, avro_annotation=avro_annotation)
|
|
245
|
+
converter.convert(avro_schema_path, js_dir_path)
|
|
246
|
+
|
|
247
|
+
def convert_avro_schema_to_javascript(avro_schema, js_dir_path, package_name='', avro_annotation=False):
|
|
248
|
+
""" Convert Avro schema to TypeScript classes """
|
|
249
|
+
converter = AvroToJavaScript(package_name, avro_annotation=avro_annotation)
|
|
250
|
+
converter.convert_schema(avro_schema, js_dir_path)
|