avrotize 2.21.1__py3-none-any.whl → 2.22.1__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/_version.py +2 -2
- avrotize/avrotogo.py +21 -8
- avrotize/avrotojava/class_test.java.jinja +2 -4
- avrotize/avrotojava.py +67 -5
- avrotize/avrotojsons.py +25 -3
- avrotize/avrotopython/dataclass_core.jinja +11 -1
- avrotize/avrotopython.py +44 -0
- avrotize/avrotorust/dataclass_enum.rs.jinja +9 -7
- avrotize/avrotorust/dataclass_struct.rs.jinja +19 -11
- avrotize/avrotorust/dataclass_union.rs.jinja +25 -7
- avrotize/avrotorust.py +64 -26
- avrotize/structuretocsharp.py +42 -11
- avrotize/structuretodb.py +21 -0
- avrotize/structuretogo.py +38 -10
- avrotize/structuretojava/pom.xml.jinja +5 -0
- avrotize/structuretojava.py +28 -7
- avrotize/structuretopython/dataclass_core.jinja +1 -0
- avrotize/structuretopython.py +79 -6
- avrotize/structuretots.py +4 -3
- {avrotize-2.21.1.dist-info → avrotize-2.22.1.dist-info}/METADATA +1 -1
- {avrotize-2.21.1.dist-info → avrotize-2.22.1.dist-info}/RECORD +24 -24
- {avrotize-2.21.1.dist-info → avrotize-2.22.1.dist-info}/WHEEL +0 -0
- {avrotize-2.21.1.dist-info → avrotize-2.22.1.dist-info}/entry_points.txt +0 -0
- {avrotize-2.21.1.dist-info → avrotize-2.22.1.dist-info}/licenses/LICENSE +0 -0
avrotize/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.
|
|
32
|
-
__version_tuple__ = version_tuple = (2,
|
|
31
|
+
__version__ = version = '2.22.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 22, 1)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
avrotize/avrotogo.py
CHANGED
|
@@ -10,8 +10,15 @@ JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
|
10
10
|
class AvroToGo:
|
|
11
11
|
"""Converts Avro schema to Go structs, including JSON and Avro marshalling methods"""
|
|
12
12
|
|
|
13
|
+
# Go reserved keywords that cannot be used as package names
|
|
14
|
+
GO_RESERVED_WORDS = [
|
|
15
|
+
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
|
|
16
|
+
'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
|
|
17
|
+
'import', 'return', 'var',
|
|
18
|
+
]
|
|
19
|
+
|
|
13
20
|
def __init__(self, base_package: str = '') -> None:
|
|
14
|
-
self.base_package = base_package
|
|
21
|
+
self.base_package = self._safe_package_name(base_package) if base_package else base_package
|
|
15
22
|
self.output_dir = os.getcwd()
|
|
16
23
|
self.generated_types_avro_namespace: Dict[str, str] = {}
|
|
17
24
|
self.generated_types_go_package: Dict[str, str] = {}
|
|
@@ -25,14 +32,15 @@ class AvroToGo:
|
|
|
25
32
|
self.structs = []
|
|
26
33
|
self.enums = []
|
|
27
34
|
|
|
35
|
+
def _safe_package_name(self, name: str) -> str:
|
|
36
|
+
"""Converts a name to a safe Go package name"""
|
|
37
|
+
if name in self.GO_RESERVED_WORDS:
|
|
38
|
+
return f"{name}_"
|
|
39
|
+
return name
|
|
40
|
+
|
|
28
41
|
def safe_identifier(self, name: str) -> str:
|
|
29
42
|
"""Converts a name to a safe Go identifier"""
|
|
30
|
-
|
|
31
|
-
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
|
|
32
|
-
'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
|
|
33
|
-
'import', 'return', 'var',
|
|
34
|
-
]
|
|
35
|
-
if name in reserved_words:
|
|
43
|
+
if name in self.GO_RESERVED_WORDS:
|
|
36
44
|
return f"{name}_"
|
|
37
45
|
return name
|
|
38
46
|
|
|
@@ -157,6 +165,10 @@ class AvroToGo:
|
|
|
157
165
|
'original_name': field['name']
|
|
158
166
|
} for field in avro_schema.get('fields', [])]
|
|
159
167
|
|
|
168
|
+
# Collect imports from field types
|
|
169
|
+
go_types = [f['type'] for f in fields]
|
|
170
|
+
imports = self.get_imports_for_definition(go_types)
|
|
171
|
+
|
|
160
172
|
context = {
|
|
161
173
|
'doc': avro_schema.get('doc', ''),
|
|
162
174
|
'struct_name': go_struct_name,
|
|
@@ -166,6 +178,7 @@ class AvroToGo:
|
|
|
166
178
|
'avro_annotation': self.avro_annotation,
|
|
167
179
|
'json_match_predicates': [self.get_is_json_match_clause(f['name'], f['type']) for f in fields],
|
|
168
180
|
'base_package': self.base_package,
|
|
181
|
+
'imports': imports,
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
pkg_dir = os.path.join(self.output_dir, 'pkg', self.base_package)
|
|
@@ -430,7 +443,7 @@ class AvroToGo:
|
|
|
430
443
|
def convert(self, avro_schema_path: str, output_dir: str):
|
|
431
444
|
"""Converts Avro schema to Go"""
|
|
432
445
|
if not self.base_package:
|
|
433
|
-
self.base_package = os.path.splitext(os.path.basename(avro_schema_path))[0]
|
|
446
|
+
self.base_package = self._safe_package_name(os.path.splitext(os.path.basename(avro_schema_path))[0])
|
|
434
447
|
|
|
435
448
|
with open(avro_schema_path, 'r', encoding='utf-8') as file:
|
|
436
449
|
schema = json.load(file)
|
|
@@ -113,8 +113,7 @@ public class {{ test_class_name }} {
|
|
|
113
113
|
"Instances with different {{ field.field_name }} should not be equal");
|
|
114
114
|
{%- elif field.is_enum %}
|
|
115
115
|
// Enum - try to get a different enum value if more than one value exists
|
|
116
|
-
{
|
|
117
|
-
{{ simple_type }}[] enumValues = {{ simple_type }}.values();
|
|
116
|
+
{{ base_type }}[] enumValues = {{ base_type }}.values();
|
|
118
117
|
if (enumValues.length > 1) {
|
|
119
118
|
instance2.set{{ field.field_name | pascal }}(enumValues[(instance2.get{{ field.field_name | pascal }}().ordinal() + 1) % enumValues.length]);
|
|
120
119
|
assertNotEquals(instance1, instance2,
|
|
@@ -150,8 +149,7 @@ public class {{ test_class_name }} {
|
|
|
150
149
|
{%- if '<' in field.field_type %}
|
|
151
150
|
{{ field.field_type }} testValue = {{ field.test_value }};
|
|
152
151
|
{%- else %}
|
|
153
|
-
{
|
|
154
|
-
{{ simple_field_type }} testValue = {{ field.test_value }};
|
|
152
|
+
{{ field.field_type }} testValue = {{ field.test_value }};
|
|
155
153
|
{%- endif %}
|
|
156
154
|
{%- if not field.is_const %}
|
|
157
155
|
instance.set{{ field.field_name | pascal }}(testValue);
|
avrotize/avrotojava.py
CHANGED
|
@@ -1721,6 +1721,51 @@ class AvroToJava:
|
|
|
1721
1721
|
def get_test_imports(self, fields: List) -> List[str]:
|
|
1722
1722
|
""" Gets the necessary imports for the test class """
|
|
1723
1723
|
imports = []
|
|
1724
|
+
|
|
1725
|
+
# Track simple names to detect conflicts
|
|
1726
|
+
# Map: simple_name -> list of FQNs that have that simple name
|
|
1727
|
+
simple_name_to_fqns: Dict[str, List[str]] = {}
|
|
1728
|
+
|
|
1729
|
+
# First pass: collect all custom type FQNs and their simple names
|
|
1730
|
+
for field in fields:
|
|
1731
|
+
inner_types = []
|
|
1732
|
+
if field.field_type.startswith("List<"):
|
|
1733
|
+
inner_type = field.field_type[5:-1]
|
|
1734
|
+
if inner_type.startswith("Map<"):
|
|
1735
|
+
start = inner_type.index('<') + 1
|
|
1736
|
+
end = inner_type.rindex('>')
|
|
1737
|
+
map_types = inner_type[start:end].split(',')
|
|
1738
|
+
if len(map_types) > 1:
|
|
1739
|
+
inner_types.append(map_types[1].strip())
|
|
1740
|
+
else:
|
|
1741
|
+
inner_types.append(inner_type)
|
|
1742
|
+
elif field.field_type.startswith("Map<"):
|
|
1743
|
+
start = field.field_type.index('<') + 1
|
|
1744
|
+
end = field.field_type.rindex('>')
|
|
1745
|
+
map_types = field.field_type[start:end].split(',')
|
|
1746
|
+
if len(map_types) > 1:
|
|
1747
|
+
inner_types.append(map_types[1].strip())
|
|
1748
|
+
if not field.field_type.startswith(("List<", "Map<")):
|
|
1749
|
+
inner_types.append(field.field_type)
|
|
1750
|
+
if hasattr(field, 'java_type_obj') and field.java_type_obj and field.java_type_obj.union_types:
|
|
1751
|
+
for union_member_type in field.java_type_obj.union_types:
|
|
1752
|
+
inner_types.append(union_member_type.type_name)
|
|
1753
|
+
|
|
1754
|
+
for type_to_check in inner_types:
|
|
1755
|
+
if type_to_check in self.generated_types_java_package and '.' in type_to_check:
|
|
1756
|
+
simple_name = type_to_check.split('.')[-1]
|
|
1757
|
+
if simple_name not in simple_name_to_fqns:
|
|
1758
|
+
simple_name_to_fqns[simple_name] = []
|
|
1759
|
+
if type_to_check not in simple_name_to_fqns[simple_name]:
|
|
1760
|
+
simple_name_to_fqns[simple_name].append(type_to_check)
|
|
1761
|
+
|
|
1762
|
+
# Find conflicting simple names (same simple name, different FQNs)
|
|
1763
|
+
conflicting_fqns: set = set()
|
|
1764
|
+
for simple_name, fqns in simple_name_to_fqns.items():
|
|
1765
|
+
if len(fqns) > 1:
|
|
1766
|
+
# This simple name has conflicts - mark all FQNs as conflicting
|
|
1767
|
+
conflicting_fqns.update(fqns)
|
|
1768
|
+
|
|
1724
1769
|
for field in fields:
|
|
1725
1770
|
# Extract inner types from generic collections
|
|
1726
1771
|
inner_types = []
|
|
@@ -1772,7 +1817,8 @@ class AvroToJava:
|
|
|
1772
1817
|
if type_to_check in self.generated_types_java_package:
|
|
1773
1818
|
type_kind = self.generated_types_java_package[type_to_check]
|
|
1774
1819
|
# Only import if it's a fully qualified name with a package
|
|
1775
|
-
|
|
1820
|
+
# Skip imports for types with conflicting simple names - they'll use FQN
|
|
1821
|
+
if '.' in type_to_check and type_to_check not in conflicting_fqns:
|
|
1776
1822
|
import_stmt = f"import {type_to_check};"
|
|
1777
1823
|
if import_stmt not in imports:
|
|
1778
1824
|
imports.append(import_stmt)
|
|
@@ -1809,10 +1855,11 @@ class AvroToJava:
|
|
|
1809
1855
|
if java_qualified_name:
|
|
1810
1856
|
if java_qualified_name in self.generated_types_java_package or java_qualified_name.split('.')[-1] in self.generated_types_java_package:
|
|
1811
1857
|
member_type_kind = self.generated_types_java_package.get(java_qualified_name, self.generated_types_java_package.get(java_qualified_name.split('.')[-1], None))
|
|
1812
|
-
# Import the class/enum
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
imports
|
|
1858
|
+
# Import the class/enum only if not conflicting
|
|
1859
|
+
if java_qualified_name not in conflicting_fqns:
|
|
1860
|
+
class_import = f"import {java_qualified_name};"
|
|
1861
|
+
if class_import not in imports:
|
|
1862
|
+
imports.append(class_import)
|
|
1816
1863
|
# No longer import test classes - we instantiate classes directly
|
|
1817
1864
|
return imports
|
|
1818
1865
|
|
|
@@ -1920,6 +1967,21 @@ class AvroToJava:
|
|
|
1920
1967
|
'Double': 'Double.valueOf(3.14)',
|
|
1921
1968
|
'byte[]': 'new byte[] { 0x01, 0x02, 0x03 }',
|
|
1922
1969
|
'Object': 'null', # Use null for Object types (Avro unions) to avoid reference equality issues
|
|
1970
|
+
# Java time types - use factory methods, not constructors
|
|
1971
|
+
'Instant': 'java.time.Instant.now()',
|
|
1972
|
+
'java.time.Instant': 'java.time.Instant.now()',
|
|
1973
|
+
'LocalDate': 'java.time.LocalDate.now()',
|
|
1974
|
+
'java.time.LocalDate': 'java.time.LocalDate.now()',
|
|
1975
|
+
'LocalTime': 'java.time.LocalTime.now()',
|
|
1976
|
+
'java.time.LocalTime': 'java.time.LocalTime.now()',
|
|
1977
|
+
'LocalDateTime': 'java.time.LocalDateTime.now()',
|
|
1978
|
+
'java.time.LocalDateTime': 'java.time.LocalDateTime.now()',
|
|
1979
|
+
'Duration': 'java.time.Duration.ofSeconds(42)',
|
|
1980
|
+
'java.time.Duration': 'java.time.Duration.ofSeconds(42)',
|
|
1981
|
+
'UUID': 'java.util.UUID.randomUUID()',
|
|
1982
|
+
'java.util.UUID': 'java.util.UUID.randomUUID()',
|
|
1983
|
+
'BigDecimal': 'new java.math.BigDecimal("42.00")',
|
|
1984
|
+
'java.math.BigDecimal': 'new java.math.BigDecimal("42.00")',
|
|
1923
1985
|
}
|
|
1924
1986
|
|
|
1925
1987
|
# Handle generic types
|
avrotize/avrotojsons.py
CHANGED
|
@@ -184,16 +184,38 @@ class AvroToJsonSchemaConverter:
|
|
|
184
184
|
def handle_type_union(self, types: List[Union[str, Dict[str, Any]]]) -> Dict[str, Any] | List[Dict[str, Any]| str] | str:
|
|
185
185
|
"""
|
|
186
186
|
Handle Avro type unions, returning a JSON schema that validates against any of the types.
|
|
187
|
+
Preserves nullability from Avro unions containing 'null'.
|
|
187
188
|
"""
|
|
189
|
+
is_nullable = 'null' in types
|
|
188
190
|
non_null_types = [t for t in types if t != 'null']
|
|
191
|
+
|
|
189
192
|
if len(non_null_types) == 1:
|
|
190
|
-
# Single non-null type
|
|
191
|
-
|
|
193
|
+
# Single non-null type - check if it's a primitive that can use type array
|
|
194
|
+
single_type = non_null_types[0]
|
|
195
|
+
json_schema = self.parse_avro_schema(single_type)
|
|
196
|
+
|
|
197
|
+
if is_nullable and isinstance(json_schema, dict):
|
|
198
|
+
# For primitives with a simple 'type' field, we can use type array notation
|
|
199
|
+
if 'type' in json_schema and isinstance(json_schema['type'], str) and '$ref' not in json_schema:
|
|
200
|
+
# Primitive type - add null to type array
|
|
201
|
+
json_schema = copy.deepcopy(json_schema)
|
|
202
|
+
json_schema['type'] = [json_schema['type'], 'null']
|
|
203
|
+
elif '$ref' in json_schema:
|
|
204
|
+
# Reference type - use oneOf with null
|
|
205
|
+
json_schema = {
|
|
206
|
+
'oneOf': [
|
|
207
|
+
{'type': 'null'},
|
|
208
|
+
json_schema
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
return json_schema
|
|
192
212
|
else:
|
|
193
213
|
# Multiple non-null types
|
|
194
|
-
union_types = [self.convert_reference(t) if isinstance(t,str) and t in self.defined_types else self.avro_primitive_to_json_type(t)
|
|
214
|
+
union_types = [self.convert_reference(t) if isinstance(t, str) and t in self.defined_types else self.avro_primitive_to_json_type(t)
|
|
195
215
|
if isinstance(t, str) else self.parse_avro_schema(t)
|
|
196
216
|
for t in non_null_types]
|
|
217
|
+
if is_nullable:
|
|
218
|
+
union_types.insert(0, {'type': 'null'})
|
|
197
219
|
return {
|
|
198
220
|
'oneOf': union_types
|
|
199
221
|
}
|
|
@@ -25,7 +25,7 @@ import avro.schema
|
|
|
25
25
|
import avro.name
|
|
26
26
|
import avro.io
|
|
27
27
|
{%- endif %}
|
|
28
|
-
{%- for import_type in import_types if import_type not in ['datetime.datetime', 'datetime.date', 'datetime.time', 'datetime.timedelta', 'decimal.Decimal'] %}
|
|
28
|
+
{%- for import_type in import_types if import_type not in ['datetime.datetime', 'datetime.date', 'datetime.time', 'datetime.timedelta', 'decimal.Decimal', 'uuid.UUID'] %}
|
|
29
29
|
from {{ '.'.join(import_type.split('.')[:-1]) | lower }} import {{ import_type.split('.')[-1] }}
|
|
30
30
|
{%- endfor %}
|
|
31
31
|
{%- for import_type in import_types if import_type in ['datetime.datetime', 'datetime.date', 'datetime.time', 'datetime.timedelta'] %}
|
|
@@ -33,6 +33,16 @@ from {{ '.'.join(import_type.split('.')[:-1]) | lower }} import {{ import_type.s
|
|
|
33
33
|
import datetime
|
|
34
34
|
{%- endif %}
|
|
35
35
|
{%- endfor %}
|
|
36
|
+
{%- for import_type in import_types if import_type == 'decimal.Decimal' %}
|
|
37
|
+
{%- if loop.first %}
|
|
38
|
+
import decimal
|
|
39
|
+
{%- endif %}
|
|
40
|
+
{%- endfor %}
|
|
41
|
+
{%- for import_type in import_types if import_type == 'uuid.UUID' %}
|
|
42
|
+
{%- if loop.first %}
|
|
43
|
+
import uuid
|
|
44
|
+
{%- endif %}
|
|
45
|
+
{%- endfor %}
|
|
36
46
|
|
|
37
47
|
{% if dataclasses_json_annotation %}
|
|
38
48
|
@dataclass_json(undefined=Undefined.EXCLUDE)
|
avrotize/avrotopython.py
CHANGED
|
@@ -12,6 +12,38 @@ from avrotize.common import fullname, get_typing_args_from_string, is_generic_av
|
|
|
12
12
|
|
|
13
13
|
INDENT = ' '
|
|
14
14
|
|
|
15
|
+
# Python standard library modules that should not be shadowed by package names
|
|
16
|
+
PYTHON_STDLIB_MODULES = {
|
|
17
|
+
'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio', 'asyncore',
|
|
18
|
+
'atexit', 'audioop', 'base64', 'bdb', 'binascii', 'binhex', 'bisect', 'builtins',
|
|
19
|
+
'bz2', 'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs',
|
|
20
|
+
'codeop', 'collections', 'colorsys', 'compileall', 'concurrent', 'configparser',
|
|
21
|
+
'contextlib', 'contextvars', 'copy', 'copyreg', 'cProfile', 'crypt', 'csv',
|
|
22
|
+
'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal', 'difflib',
|
|
23
|
+
'dis', 'distutils', 'doctest', 'email', 'encodings', 'enum', 'errno', 'faulthandler',
|
|
24
|
+
'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'fractions', 'ftplib', 'functools',
|
|
25
|
+
'gc', 'getopt', 'getpass', 'gettext', 'glob', 'graphlib', 'grp', 'gzip',
|
|
26
|
+
'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr', 'imp',
|
|
27
|
+
'importlib', 'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword',
|
|
28
|
+
'lib2to3', 'linecache', 'locale', 'logging', 'lzma', 'mailbox', 'mailcap',
|
|
29
|
+
'marshal', 'math', 'mimetypes', 'mmap', 'modulefinder', 'multiprocessing',
|
|
30
|
+
'netrc', 'nis', 'nntplib', 'numbers', 'operator', 'optparse', 'os', 'ossaudiodev',
|
|
31
|
+
'pathlib', 'pdb', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform',
|
|
32
|
+
'plistlib', 'poplib', 'posix', 'posixpath', 'pprint', 'profile', 'pstats',
|
|
33
|
+
'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc', 'queue', 'quopri', 'random',
|
|
34
|
+
're', 'readline', 'reprlib', 'resource', 'rlcompleter', 'runpy', 'sched',
|
|
35
|
+
'secrets', 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
|
|
36
|
+
'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd',
|
|
37
|
+
'sqlite3', 'ssl', 'stat', 'statistics', 'string', 'stringprep', 'struct',
|
|
38
|
+
'subprocess', 'sunau', 'symtable', 'sys', 'sysconfig', 'syslog', 'tabnanny',
|
|
39
|
+
'tarfile', 'telnetlib', 'tempfile', 'termios', 'test', 'textwrap', 'threading',
|
|
40
|
+
'time', 'timeit', 'tkinter', 'token', 'tokenize', 'trace', 'traceback',
|
|
41
|
+
'tracemalloc', 'tty', 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata',
|
|
42
|
+
'unittest', 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
|
|
43
|
+
'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml', 'xmlrpc',
|
|
44
|
+
'zipapp', 'zipfile', 'zipimport', 'zlib', 'zoneinfo',
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
|
|
16
48
|
def is_python_reserved_word(word: str) -> bool:
|
|
17
49
|
"""Checks if a word is a Python reserved word"""
|
|
@@ -25,6 +57,13 @@ def is_python_reserved_word(word: str) -> bool:
|
|
|
25
57
|
return word in reserved_words
|
|
26
58
|
|
|
27
59
|
|
|
60
|
+
def safe_package_name(name: str) -> str:
|
|
61
|
+
"""Converts a name to a safe Python package name that won't shadow stdlib"""
|
|
62
|
+
if name.lower() in PYTHON_STDLIB_MODULES:
|
|
63
|
+
return f"{name}_types"
|
|
64
|
+
return name
|
|
65
|
+
|
|
66
|
+
|
|
28
67
|
class AvroToPython:
|
|
29
68
|
"""Converts Avro schema to Python data classes"""
|
|
30
69
|
|
|
@@ -167,6 +206,9 @@ class AvroToPython:
|
|
|
167
206
|
enum_ref = self.generate_enum(avro_type, parent_package, write_file=True)
|
|
168
207
|
import_types.add(enum_ref)
|
|
169
208
|
return self.strip_package_from_fully_qualified_name(enum_ref)
|
|
209
|
+
elif avro_type['type'] == 'fixed':
|
|
210
|
+
# Fixed types are represented as bytes in Python
|
|
211
|
+
return 'bytes'
|
|
170
212
|
elif avro_type['type'] == 'array':
|
|
171
213
|
return f"typing.List[{self.convert_avro_type_to_python(avro_type['items'], parent_package, import_types)}]"
|
|
172
214
|
elif avro_type['type'] == 'map':
|
|
@@ -611,6 +653,7 @@ def convert_avro_to_python(avro_schema_path, py_file_path, package_name='', data
|
|
|
611
653
|
if not package_name:
|
|
612
654
|
package_name = os.path.splitext(os.path.basename(avro_schema_path))[
|
|
613
655
|
0].lower().replace('-', '_')
|
|
656
|
+
package_name = safe_package_name(package_name)
|
|
614
657
|
|
|
615
658
|
avro_to_python = AvroToPython(
|
|
616
659
|
package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
@@ -619,6 +662,7 @@ def convert_avro_to_python(avro_schema_path, py_file_path, package_name='', data
|
|
|
619
662
|
|
|
620
663
|
def convert_avro_schema_to_python(avro_schema, py_file_path, package_name='', dataclasses_json_annotation=False, avro_annotation=False):
|
|
621
664
|
"""Converts Avro schema to Python data classes"""
|
|
665
|
+
package_name = safe_package_name(package_name) if package_name else package_name
|
|
622
666
|
avro_to_python = AvroToPython(
|
|
623
667
|
package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
624
668
|
if isinstance(avro_schema, dict):
|
|
@@ -55,20 +55,22 @@ mod tests {
|
|
|
55
55
|
use super::*;
|
|
56
56
|
use rand::Rng;
|
|
57
57
|
|
|
58
|
+
{%- if serde_annotation %}
|
|
58
59
|
#[test]
|
|
59
60
|
fn test_serialize_deserialize_{{ enum_name.lower() }}() {
|
|
60
61
|
{%- for symbol in symbols %}
|
|
61
62
|
let instance = {{ enum_name }}::{{ symbol }};
|
|
62
|
-
{%- if serde_annotation %}
|
|
63
63
|
let json_bytes = serde_json::to_vec(&instance).unwrap();
|
|
64
64
|
let deserialized_instance: {{ enum_name }} = serde_json::from_slice(&json_bytes).unwrap();
|
|
65
65
|
assert_eq!(instance, deserialized_instance);
|
|
66
|
-
{%-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
{%- endfor %}
|
|
67
|
+
}
|
|
68
|
+
{%- endif %}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn test_enum_variants_{{ enum_name.lower() }}() {
|
|
72
|
+
{%- for symbol in symbols %}
|
|
73
|
+
let _instance = {{ enum_name }}::{{ symbol }};
|
|
72
74
|
{%- endfor %}
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -4,28 +4,28 @@ use apache_avro::Schema;
|
|
|
4
4
|
{%- endif %}
|
|
5
5
|
{%- if serde_annotation or avro_annotation %}
|
|
6
6
|
use serde::{Serialize, Deserialize};
|
|
7
|
-
{%- endif %}
|
|
8
7
|
use std::io::Write;
|
|
9
8
|
use flate2::write::GzEncoder;
|
|
10
9
|
use flate2::read::GzDecoder;
|
|
10
|
+
{%- endif %}
|
|
11
11
|
{%- set uses_chrono = false %}
|
|
12
12
|
{%- set uses_uuid = false %}
|
|
13
13
|
{%- set uses_hashmap = false%}
|
|
14
14
|
{%- for field in fields %}
|
|
15
|
-
{%- if field.type == "NaiveDate" or field.type == "NaiveTime" or field.type == "NaiveDateTime" %}
|
|
15
|
+
{%- if field.type == "NaiveDate" or field.type == "NaiveTime" or field.type == "NaiveDateTime" or "chrono::" in field.type %}
|
|
16
16
|
{%- set uses_chrono = true %}
|
|
17
17
|
{%- endif %}
|
|
18
|
-
{%- if field.type == "Uuid" %}
|
|
18
|
+
{%- if field.type == "Uuid" or "uuid::" in field.type %}
|
|
19
19
|
{%- set uses_uuid = true %}
|
|
20
20
|
{%- endif %}
|
|
21
|
-
{%- if field.type.startswith("HashMap<") %}
|
|
21
|
+
{%- if field.type.startswith("HashMap<") or "HashMap<" in field.type %}
|
|
22
22
|
{%- set uses_hashmap = true %}
|
|
23
23
|
{%- endif %}
|
|
24
24
|
{%- endfor %}
|
|
25
25
|
{%- if uses_chrono %}
|
|
26
26
|
use chrono::{NaiveDate, NaiveTime, NaiveDateTime};
|
|
27
27
|
{%- endif %}
|
|
28
|
-
{%- if
|
|
28
|
+
{%- if uses_uuid %}
|
|
29
29
|
use uuid::Uuid;
|
|
30
30
|
{%- endif %}
|
|
31
31
|
{%- if uses_hashmap %}
|
|
@@ -39,7 +39,7 @@ use std::collections::HashMap;
|
|
|
39
39
|
#[derive(Debug{%- if serde_annotation or avro_annotation %}, Serialize, Deserialize{%- endif %}, PartialEq, Clone, Default)]
|
|
40
40
|
pub struct {{ struct_name }} {
|
|
41
41
|
{%- for field in fields %}
|
|
42
|
-
{%- if field.serde_rename %}
|
|
42
|
+
{%- if field.serde_rename and (serde_annotation or avro_annotation) %}
|
|
43
43
|
#[serde(rename = "{{ field.original_name }}")]
|
|
44
44
|
{%- endif %}
|
|
45
45
|
pub {{ field.name }}: {{ field.type }},
|
|
@@ -55,6 +55,7 @@ lazy_static! {
|
|
|
55
55
|
{%- endif %}
|
|
56
56
|
|
|
57
57
|
impl {{ struct_name }} {
|
|
58
|
+
{%- if serde_annotation or avro_annotation %}
|
|
58
59
|
/// Serializes the struct to a byte array based on the provided content type
|
|
59
60
|
pub fn to_byte_array(&self, content_type: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
|
60
61
|
let result: Vec<u8>;
|
|
@@ -70,14 +71,10 @@ impl {{ struct_name }} {
|
|
|
70
71
|
let value = apache_avro::to_value(self).unwrap();
|
|
71
72
|
result = apache_avro::to_avro_datum(&SCHEMA, value).unwrap();
|
|
72
73
|
}
|
|
73
|
-
{
|
|
74
|
-
{%- if avro_annotation or serde_annotation %}
|
|
74
|
+
else {% endif -%}
|
|
75
75
|
{
|
|
76
|
-
{%- endif %}
|
|
77
76
|
return Err(format!("unsupported media type: {}", media_type).into())
|
|
78
|
-
{%- if avro_annotation or serde_annotation %}
|
|
79
77
|
}
|
|
80
|
-
{%- endif %}
|
|
81
78
|
if media_type.ends_with("+gzip") {
|
|
82
79
|
let mut encoder = GzEncoder::new(Vec::new(), flate2::Compression::default());
|
|
83
80
|
encoder.write_all(&result)?;
|
|
@@ -87,7 +84,9 @@ impl {{ struct_name }} {
|
|
|
87
84
|
return Ok(result)
|
|
88
85
|
}
|
|
89
86
|
}
|
|
87
|
+
{%- endif %}
|
|
90
88
|
|
|
89
|
+
{%- if serde_annotation or avro_annotation %}
|
|
91
90
|
/// Deserializes the struct from a byte array based on the provided content type
|
|
92
91
|
pub fn from_data(data: impl AsRef<[u8]>, content_type: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
|
93
92
|
let media_type = content_type.split(';').next().unwrap_or("");
|
|
@@ -114,7 +113,9 @@ impl {{ struct_name }} {
|
|
|
114
113
|
{%- endif %}
|
|
115
114
|
Err(format!("unsupported media type: {}", media_type).into())
|
|
116
115
|
}
|
|
116
|
+
{%- endif %}
|
|
117
117
|
|
|
118
|
+
{%- if serde_annotation or avro_annotation %}
|
|
118
119
|
/// Checks if the given JSON value matches the schema of the struct
|
|
119
120
|
pub fn is_json_match(node: &serde_json::Value) -> bool {
|
|
120
121
|
{%- for predicate in json_match_predicates %}
|
|
@@ -122,6 +123,7 @@ impl {{ struct_name }} {
|
|
|
122
123
|
{%- endfor %}
|
|
123
124
|
true
|
|
124
125
|
}
|
|
126
|
+
{%- endif %}
|
|
125
127
|
|
|
126
128
|
/// Returns the struct instance itself
|
|
127
129
|
pub fn to_object(&self) -> &Self {
|
|
@@ -160,6 +162,12 @@ mod tests {
|
|
|
160
162
|
assert!(instance.{{ field.name }} != {{ field_type }}::default()); // Check that {{ field.name }} is not default
|
|
161
163
|
{%- elif field.type.startswith("std::collections::HashMap<")%}
|
|
162
164
|
assert!(instance.{{ field.name }}.len() >= 0); // Check that {{ field.name }} is not empty
|
|
165
|
+
{%- elif field.is_generated_type %}
|
|
166
|
+
// Skip default comparison for generated types (enums/unions) as random values may match default
|
|
167
|
+
let _ = &instance.{{ field.name }};
|
|
168
|
+
{%- elif field.type in ["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", "isize", "usize"] %}
|
|
169
|
+
// Skip default comparison for numeric types as random values can legitimately be 0
|
|
170
|
+
let _ = instance.{{ field.name }};
|
|
163
171
|
{%- elif field.type != "bool" %}
|
|
164
172
|
assert!(instance.{{ field.name }} != {{ field.type }}::default()); // Check that {{ field.name }} is not default
|
|
165
173
|
{%- endif %}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{%- if serde_annotation %}
|
|
1
|
+
{%- if serde_annotation or avro_annotation %}
|
|
2
2
|
use serde::{self, Serialize, Deserialize};
|
|
3
3
|
{%- endif %}
|
|
4
4
|
|
|
@@ -70,14 +70,25 @@ impl {{ union_enum_name }} {
|
|
|
70
70
|
|
|
71
71
|
#[cfg(test)]
|
|
72
72
|
impl {{ union_enum_name }} {
|
|
73
|
+
{%- set unique_fields = union_fields | selectattr('is_first_with_predicate') | list %}
|
|
73
74
|
pub fn generate_random_instance() -> {{ union_enum_name }} {
|
|
74
75
|
let mut rng = rand::thread_rng();
|
|
76
|
+
{%- if serde_annotation and unique_fields | length < union_fields | length %}
|
|
77
|
+
// Only pick from variants with unique JSON structures to ensure round-trip works
|
|
78
|
+
match rand::Rng::gen_range(&mut rng, 0..{{ unique_fields | length }}) {
|
|
79
|
+
{%- for union_field in unique_fields %}
|
|
80
|
+
{{ loop.index0 }} => {{ union_enum_name }}::{{ union_field.name }}({{ union_field.random_value }}),
|
|
81
|
+
{%- endfor %}
|
|
82
|
+
_ => panic!("Invalid random index generated"),
|
|
83
|
+
}
|
|
84
|
+
{%- else %}
|
|
75
85
|
match rand::Rng::gen_range(&mut rng, 0..{{ union_fields | length }}) {
|
|
76
86
|
{%- for union_field in union_fields %}
|
|
77
87
|
{{ loop.index0 }} => {{ union_enum_name }}::{{ union_field.name }}({{ union_field.random_value }}),
|
|
78
88
|
{%- endfor %}
|
|
79
89
|
_ => panic!("Invalid random index generated"),
|
|
80
90
|
}
|
|
91
|
+
{%- endif %}
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -85,21 +96,28 @@ impl {{ union_enum_name }} {
|
|
|
85
96
|
mod tests {
|
|
86
97
|
use super::*;
|
|
87
98
|
|
|
99
|
+
{%- if serde_annotation %}
|
|
88
100
|
#[test]
|
|
89
101
|
fn test_serialize_deserialize_{{ union_enum_name.lower() }}() {
|
|
90
102
|
let mut rng = rand::thread_rng();
|
|
91
103
|
{%- for union_field in union_fields %}
|
|
104
|
+
{%- if union_field.is_first_with_predicate %}
|
|
92
105
|
let instance = {{ union_enum_name }}::{{ union_field.name }}({{ union_field.random_value }});
|
|
93
|
-
{%- if serde_annotation %}
|
|
94
106
|
let json_bytes = serde_json::to_vec(&instance).unwrap();
|
|
95
107
|
let deserialized_instance: {{ union_enum_name }} = serde_json::from_slice(&json_bytes).unwrap();
|
|
96
108
|
assert_eq!(instance, deserialized_instance);
|
|
109
|
+
{%- else %}
|
|
110
|
+
// Skip {{ union_field.name }} - structurally identical to earlier variant, would deserialize as first match
|
|
97
111
|
{%- endif %}
|
|
98
|
-
{%-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
{%- endfor %}
|
|
113
|
+
}
|
|
114
|
+
{%- endif %}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn test_union_variants_{{ union_enum_name.lower() }}() {
|
|
118
|
+
let mut rng = rand::thread_rng();
|
|
119
|
+
{%- for union_field in union_fields %}
|
|
120
|
+
let _instance = {{ union_enum_name }}::{{ union_field.name }}({{ union_field.random_value }});
|
|
103
121
|
{%- endfor %}
|
|
104
122
|
}
|
|
105
123
|
}
|
avrotize/avrotorust.py
CHANGED
|
@@ -144,12 +144,15 @@ class AvroToRust:
|
|
|
144
144
|
field_name = self.safe_identifier(snake(original_field_name))
|
|
145
145
|
field_type = self.convert_avro_type_to_rust(field_name, field['type'], parent_namespace)
|
|
146
146
|
serde_rename = field_name != original_field_name
|
|
147
|
+
# Check if this is a generated type (enum, union, or record) where random values may match default
|
|
148
|
+
is_generated_type = field_type in self.generated_types_rust_package or '::' in field_type
|
|
147
149
|
fields.append({
|
|
148
150
|
'original_name': original_field_name,
|
|
149
151
|
'name': field_name,
|
|
150
152
|
'type': field_type,
|
|
151
153
|
'serde_rename': serde_rename,
|
|
152
|
-
'random_value': self.generate_random_value(field_type)
|
|
154
|
+
'random_value': self.generate_random_value(field_type),
|
|
155
|
+
'is_generated_type': is_generated_type
|
|
153
156
|
})
|
|
154
157
|
|
|
155
158
|
struct_name = self.safe_identifier(pascal(avro_schema['name']))
|
|
@@ -187,28 +190,51 @@ class AvroToRust:
|
|
|
187
190
|
def get_is_json_match_clause(self, field_name: str, field_type: str, for_union=False) -> str:
|
|
188
191
|
"""Generates the is_json_match clause for a field"""
|
|
189
192
|
ref = f'node[\"{field_name}\"]' if not for_union else 'node'
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return f"{ref}.
|
|
202
|
-
elif
|
|
203
|
-
return f"{ref}.
|
|
204
|
-
elif
|
|
205
|
-
return f"{ref}.
|
|
206
|
-
elif
|
|
207
|
-
return f"{ref}.
|
|
208
|
-
elif
|
|
209
|
-
return f"{ref}.
|
|
193
|
+
|
|
194
|
+
# Check if type is optional - if so, we need to allow null values
|
|
195
|
+
is_optional = field_type.startswith('Option<')
|
|
196
|
+
base_type = field_type[7:-1] if is_optional else field_type
|
|
197
|
+
null_check = f" || {ref}.is_null()" if is_optional else ""
|
|
198
|
+
|
|
199
|
+
# serde_json::Value can be any JSON type, so always return true
|
|
200
|
+
if base_type == 'serde_json::Value':
|
|
201
|
+
return "true"
|
|
202
|
+
|
|
203
|
+
if base_type == 'String':
|
|
204
|
+
return f"({ref}.is_string(){null_check})"
|
|
205
|
+
elif base_type == 'bool':
|
|
206
|
+
return f"({ref}.is_boolean(){null_check})"
|
|
207
|
+
elif base_type == 'i32':
|
|
208
|
+
return f"({ref}.is_i64(){null_check})"
|
|
209
|
+
elif base_type == 'i64':
|
|
210
|
+
return f"({ref}.is_i64(){null_check})"
|
|
211
|
+
elif base_type == 'f32':
|
|
212
|
+
return f"({ref}.is_f64(){null_check})"
|
|
213
|
+
elif base_type == 'f64':
|
|
214
|
+
return f"({ref}.is_f64(){null_check})"
|
|
215
|
+
elif base_type == 'Vec<u8>':
|
|
216
|
+
return f"({ref}.is_array(){null_check})"
|
|
217
|
+
elif base_type == 'std::collections::HashMap<String, String>':
|
|
218
|
+
return f"({ref}.is_object(){null_check})"
|
|
219
|
+
elif base_type.startswith('std::collections::HashMap<String, '):
|
|
220
|
+
return f"({ref}.is_object(){null_check})"
|
|
221
|
+
elif base_type.startswith('Vec<'):
|
|
222
|
+
return f"({ref}.is_array(){null_check})"
|
|
223
|
+
# chrono types - check for string (ISO 8601 format) or number (timestamp)
|
|
224
|
+
elif 'chrono::NaiveDateTime' in base_type or 'NaiveDateTime' in base_type:
|
|
225
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
226
|
+
elif 'chrono::NaiveDate' in base_type or 'NaiveDate' in base_type:
|
|
227
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
228
|
+
elif 'chrono::NaiveTime' in base_type or 'NaiveTime' in base_type:
|
|
229
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
230
|
+
# uuid type - check for string
|
|
231
|
+
elif 'uuid::Uuid' in base_type or 'Uuid' in base_type:
|
|
232
|
+
return f"({ref}.is_string(){null_check})"
|
|
210
233
|
else:
|
|
211
|
-
|
|
234
|
+
# Custom types - call their is_json_match method
|
|
235
|
+
if is_optional:
|
|
236
|
+
return f"({base_type}::is_json_match(&{ref}) || {ref}.is_null())"
|
|
237
|
+
return f"{base_type}::is_json_match(&{ref})"
|
|
212
238
|
|
|
213
239
|
|
|
214
240
|
def generate_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
@@ -250,17 +276,29 @@ class AvroToRust:
|
|
|
250
276
|
ns = namespace.replace('.', '::').lower()
|
|
251
277
|
union_enum_name = pascal(field_name) + 'Union'
|
|
252
278
|
union_types = [self.convert_avro_type_to_rust(field_name + "Option" + str(i), t, namespace) for i, t in enumerate(avro_type) if t != 'null']
|
|
253
|
-
|
|
254
|
-
|
|
279
|
+
|
|
280
|
+
# Track seen predicates to identify structurally identical variants
|
|
281
|
+
seen_predicates: set = set()
|
|
282
|
+
union_fields = []
|
|
283
|
+
for i, t in enumerate(union_types):
|
|
284
|
+
predicate = self.get_is_json_match_clause(field_name, t, for_union=True)
|
|
285
|
+
# Mark if this is the first variant with this predicate structure
|
|
286
|
+
# Subsequent variants with same predicate can't be distinguished during JSON deserialization
|
|
287
|
+
is_first_with_predicate = predicate not in seen_predicates
|
|
288
|
+
seen_predicates.add(predicate)
|
|
289
|
+
union_fields.append({
|
|
255
290
|
'name': pascal(t.rsplit('::',1)[-1]),
|
|
256
291
|
'type': t,
|
|
257
292
|
'random_value': self.generate_random_value(t),
|
|
258
293
|
'default_value': 'Default::default()',
|
|
259
|
-
'json_match_predicate':
|
|
260
|
-
|
|
294
|
+
'json_match_predicate': predicate,
|
|
295
|
+
'is_first_with_predicate': is_first_with_predicate,
|
|
296
|
+
})
|
|
297
|
+
|
|
261
298
|
qualified_union_enum_name = self.safe_package(self.concat_package(ns, union_enum_name))
|
|
262
299
|
context = {
|
|
263
300
|
'serde_annotation': self.serde_annotation,
|
|
301
|
+
'avro_annotation': self.avro_annotation,
|
|
264
302
|
'union_enum_name': union_enum_name,
|
|
265
303
|
'union_fields': union_fields,
|
|
266
304
|
'json_match_predicates': [self.get_is_json_match_clause(f['name'], f['type'], for_union=True) for f in union_fields]
|
avrotize/structuretocsharp.py
CHANGED
|
@@ -143,6 +143,35 @@ class StructureToCSharp:
|
|
|
143
143
|
]
|
|
144
144
|
return word in reserved_words
|
|
145
145
|
|
|
146
|
+
def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field') -> str:
|
|
147
|
+
"""Converts a name to a safe C# identifier.
|
|
148
|
+
|
|
149
|
+
Handles:
|
|
150
|
+
- Reserved words (prepend @)
|
|
151
|
+
- Numeric prefixes (prepend _)
|
|
152
|
+
- Special characters (replace with _)
|
|
153
|
+
- All-special-char names (use fallback_prefix)
|
|
154
|
+
- Class name collision (append _)
|
|
155
|
+
"""
|
|
156
|
+
import re
|
|
157
|
+
# Replace invalid characters with underscores
|
|
158
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
159
|
+
# Remove leading/trailing underscores from sanitization
|
|
160
|
+
safe = safe.strip('_') if safe != name else safe
|
|
161
|
+
# If nothing left after removing special chars, use fallback
|
|
162
|
+
if not safe or not re.match(r'^[a-zA-Z_@]', safe):
|
|
163
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
164
|
+
safe = '_' + safe # Numeric prefix
|
|
165
|
+
else:
|
|
166
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
167
|
+
# Handle reserved words with @ prefix
|
|
168
|
+
if self.is_csharp_reserved_word(safe):
|
|
169
|
+
safe = '@' + safe
|
|
170
|
+
# Handle class name collision
|
|
171
|
+
if class_name and safe == class_name:
|
|
172
|
+
safe = safe + '_'
|
|
173
|
+
return safe
|
|
174
|
+
|
|
146
175
|
def is_csharp_primitive_type(self, csharp_type: str) -> bool:
|
|
147
176
|
""" Checks if a type is a C# primitive type """
|
|
148
177
|
if csharp_type.endswith('?'):
|
|
@@ -416,16 +445,18 @@ class StructureToCSharp:
|
|
|
416
445
|
""" Generates a property for a class """
|
|
417
446
|
property_definition = ''
|
|
418
447
|
|
|
419
|
-
# Resolve property name
|
|
420
|
-
field_name = prop_name
|
|
421
|
-
if self.is_csharp_reserved_word(field_name):
|
|
422
|
-
field_name = f"@{field_name}"
|
|
448
|
+
# Resolve property name using safe_identifier for special chars, numeric prefixes, etc.
|
|
449
|
+
field_name = self.safe_identifier(prop_name, class_name)
|
|
423
450
|
if self.pascal_properties:
|
|
424
|
-
field_name_cs = pascal(field_name)
|
|
451
|
+
field_name_cs = pascal(field_name.lstrip('@'))
|
|
452
|
+
# Re-check for class name collision after pascal casing
|
|
453
|
+
if field_name_cs == class_name:
|
|
454
|
+
field_name_cs += "_"
|
|
425
455
|
else:
|
|
426
456
|
field_name_cs = field_name
|
|
427
|
-
|
|
428
|
-
|
|
457
|
+
|
|
458
|
+
# Track if field name differs from original for JSON annotation
|
|
459
|
+
needs_json_annotation = field_name_cs != prop_name
|
|
429
460
|
|
|
430
461
|
# Check if this is a const field
|
|
431
462
|
if 'const' in prop_schema:
|
|
@@ -442,9 +473,9 @@ class StructureToCSharp:
|
|
|
442
473
|
|
|
443
474
|
# Add JSON property name annotation when property name differs from schema name
|
|
444
475
|
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
445
|
-
if
|
|
476
|
+
if needs_json_annotation:
|
|
446
477
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
447
|
-
if self.newtonsoft_json_annotation and
|
|
478
|
+
if self.newtonsoft_json_annotation and needs_json_annotation:
|
|
448
479
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
449
480
|
|
|
450
481
|
# Add XML element annotation if enabled
|
|
@@ -473,9 +504,9 @@ class StructureToCSharp:
|
|
|
473
504
|
|
|
474
505
|
# Add JSON property name annotation when property name differs from schema name
|
|
475
506
|
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
476
|
-
if
|
|
507
|
+
if needs_json_annotation:
|
|
477
508
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
478
|
-
if self.newtonsoft_json_annotation and
|
|
509
|
+
if self.newtonsoft_json_annotation and needs_json_annotation:
|
|
479
510
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
480
511
|
|
|
481
512
|
# Add XML element annotation if enabled
|
avrotize/structuretodb.py
CHANGED
|
@@ -443,6 +443,27 @@ def structure_type_to_sql_type(structure_type: Any, dialect: str) -> str:
|
|
|
443
443
|
struct_type = structure_type.get("type", "string")
|
|
444
444
|
if struct_type in ["array", "set", "map", "object", "choice", "tuple"]:
|
|
445
445
|
return type_map[dialect][struct_type]
|
|
446
|
+
|
|
447
|
+
# Handle string type with maxLength annotation
|
|
448
|
+
if struct_type == "string" and "maxLength" in structure_type:
|
|
449
|
+
max_length = structure_type["maxLength"]
|
|
450
|
+
if dialect == "sqlserver" or dialect == "sqlanywhere":
|
|
451
|
+
return f"NVARCHAR({max_length})"
|
|
452
|
+
elif dialect in ["postgres", "redshift", "db2"]:
|
|
453
|
+
return f"VARCHAR({max_length})"
|
|
454
|
+
elif dialect in ["mysql", "mariadb"]:
|
|
455
|
+
return f"VARCHAR({max_length})"
|
|
456
|
+
elif dialect == "sqlite":
|
|
457
|
+
return f"VARCHAR({max_length})"
|
|
458
|
+
elif dialect == "oracle":
|
|
459
|
+
return f"VARCHAR2({max_length})"
|
|
460
|
+
elif dialect == "bigquery":
|
|
461
|
+
return f"STRING({max_length})"
|
|
462
|
+
elif dialect == "snowflake":
|
|
463
|
+
return f"VARCHAR({max_length})"
|
|
464
|
+
else:
|
|
465
|
+
return f"VARCHAR({max_length})"
|
|
466
|
+
|
|
446
467
|
return structure_type_to_sql_type(struct_type, dialect)
|
|
447
468
|
|
|
448
469
|
return type_map.get(dialect, type_map["postgres"])["string"]
|
avrotize/structuretogo.py
CHANGED
|
@@ -16,8 +16,15 @@ INDENT = ' '
|
|
|
16
16
|
class StructureToGo:
|
|
17
17
|
""" Converts JSON Structure schema to Go structs """
|
|
18
18
|
|
|
19
|
+
# Go reserved keywords that cannot be used as package names
|
|
20
|
+
GO_RESERVED_WORDS = [
|
|
21
|
+
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
|
|
22
|
+
'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
|
|
23
|
+
'import', 'return', 'var',
|
|
24
|
+
]
|
|
25
|
+
|
|
19
26
|
def __init__(self, base_package: str = '') -> None:
|
|
20
|
-
self.base_package = base_package
|
|
27
|
+
self.base_package = self._safe_package_name(base_package) if base_package else base_package
|
|
21
28
|
self.output_dir = os.getcwd()
|
|
22
29
|
self.json_annotation = False
|
|
23
30
|
self.avro_annotation = False
|
|
@@ -31,17 +38,37 @@ class StructureToGo:
|
|
|
31
38
|
self.structs: List[Dict] = []
|
|
32
39
|
self.enums: List[Dict] = []
|
|
33
40
|
|
|
34
|
-
def
|
|
35
|
-
"""Converts a name to a safe Go
|
|
36
|
-
|
|
37
|
-
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
|
|
38
|
-
'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
|
|
39
|
-
'import', 'return', 'var',
|
|
40
|
-
]
|
|
41
|
-
if name in reserved_words:
|
|
41
|
+
def _safe_package_name(self, name: str) -> str:
|
|
42
|
+
"""Converts a name to a safe Go package name"""
|
|
43
|
+
if name in self.GO_RESERVED_WORDS:
|
|
42
44
|
return f"{name}_"
|
|
43
45
|
return name
|
|
44
46
|
|
|
47
|
+
def safe_identifier(self, name: str, fallback_prefix: str = 'field') -> str:
|
|
48
|
+
"""Converts a name to a safe Go identifier.
|
|
49
|
+
|
|
50
|
+
Handles:
|
|
51
|
+
- Reserved words (append _)
|
|
52
|
+
- Numeric prefixes (prepend _)
|
|
53
|
+
- Special characters (replace with _)
|
|
54
|
+
- All-special-char names (use fallback_prefix)
|
|
55
|
+
"""
|
|
56
|
+
import re
|
|
57
|
+
# Replace invalid characters with underscores
|
|
58
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
59
|
+
# Remove leading/trailing underscores from sanitization
|
|
60
|
+
safe = safe.strip('_') if safe != name else safe
|
|
61
|
+
# If nothing left after removing special chars, use fallback
|
|
62
|
+
if not safe or not re.match(r'^[a-zA-Z_]', safe):
|
|
63
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
64
|
+
safe = '_' + safe # Numeric prefix
|
|
65
|
+
else:
|
|
66
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
67
|
+
# Handle reserved words
|
|
68
|
+
if safe in self.GO_RESERVED_WORDS:
|
|
69
|
+
safe = safe + '_'
|
|
70
|
+
return safe
|
|
71
|
+
|
|
45
72
|
def go_type_name(self, name: str, namespace: str = '') -> str:
|
|
46
73
|
"""Returns a qualified name for a Go struct or enum"""
|
|
47
74
|
if namespace:
|
|
@@ -675,7 +702,8 @@ class StructureToGo:
|
|
|
675
702
|
def convert(self, structure_schema_path: str, output_dir: str):
|
|
676
703
|
"""Converts JSON Structure schema to Go"""
|
|
677
704
|
if not self.base_package:
|
|
678
|
-
|
|
705
|
+
pkg_name = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_').lower()
|
|
706
|
+
self.base_package = self._safe_package_name(pkg_name)
|
|
679
707
|
|
|
680
708
|
with open(structure_schema_path, 'r', encoding='utf-8') as file:
|
|
681
709
|
schema = json.load(file)
|
|
@@ -22,5 +22,10 @@
|
|
|
22
22
|
<artifactId>jackson-annotations</artifactId>
|
|
23
23
|
<version>${jackson.version}</version>
|
|
24
24
|
</dependency>
|
|
25
|
+
<dependency>
|
|
26
|
+
<groupId>com.fasterxml.jackson.datatype</groupId>
|
|
27
|
+
<artifactId>jackson-datatype-jsr310</artifactId>
|
|
28
|
+
<version>${jackson.version}</version>
|
|
29
|
+
</dependency>
|
|
25
30
|
</dependencies>
|
|
26
31
|
</project>
|
avrotize/structuretojava.py
CHANGED
|
@@ -71,13 +71,34 @@ class StructureToJava:
|
|
|
71
71
|
self.is_class = is_class
|
|
72
72
|
self.is_enum = is_enum
|
|
73
73
|
|
|
74
|
-
def safe_identifier(self, name: str, class_name: str = '') -> str:
|
|
75
|
-
"""Converts a name to a safe Java identifier
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field') -> str:
|
|
75
|
+
"""Converts a name to a safe Java identifier.
|
|
76
|
+
|
|
77
|
+
Handles:
|
|
78
|
+
- Reserved words (prepend _)
|
|
79
|
+
- Numeric prefixes (prepend _)
|
|
80
|
+
- Special characters (replace with _)
|
|
81
|
+
- All-special-char names (use fallback_prefix)
|
|
82
|
+
- Class name collision (append _)
|
|
83
|
+
"""
|
|
84
|
+
import re
|
|
85
|
+
# Replace invalid characters with underscores
|
|
86
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
87
|
+
# Remove leading/trailing underscores from sanitization
|
|
88
|
+
safe = safe.strip('_') if safe != name else safe
|
|
89
|
+
# If nothing left after removing special chars, use fallback
|
|
90
|
+
if not safe or not re.match(r'^[a-zA-Z_]', safe):
|
|
91
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
92
|
+
safe = '_' + safe # Numeric prefix
|
|
93
|
+
else:
|
|
94
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
95
|
+
# Handle reserved words
|
|
96
|
+
if is_java_reserved_word(safe):
|
|
97
|
+
safe = '_' + safe
|
|
98
|
+
# Handle class name collision
|
|
99
|
+
if class_name and safe == class_name:
|
|
100
|
+
safe = safe + '_'
|
|
101
|
+
return safe
|
|
81
102
|
|
|
82
103
|
def safe_package(self, packageName: str) -> str:
|
|
83
104
|
"""Converts a name to a safe Java identifier by checking each path segment"""
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
""" {{ class_name }} dataclass. """
|
|
2
2
|
|
|
3
3
|
# pylint: disable=too-many-lines, too-many-locals, too-many-branches, too-many-statements, too-many-arguments, line-too-long, wildcard-import
|
|
4
|
+
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
{%- if avro_annotation or dataclasses_json_annotation %}
|
|
6
7
|
import io
|
avrotize/structuretopython.py
CHANGED
|
@@ -15,6 +15,38 @@ JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
|
15
15
|
|
|
16
16
|
INDENT = ' '
|
|
17
17
|
|
|
18
|
+
# Python standard library modules that should not be shadowed by package names
|
|
19
|
+
PYTHON_STDLIB_MODULES = {
|
|
20
|
+
'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio', 'asyncore',
|
|
21
|
+
'atexit', 'audioop', 'base64', 'bdb', 'binascii', 'binhex', 'bisect', 'builtins',
|
|
22
|
+
'bz2', 'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs',
|
|
23
|
+
'codeop', 'collections', 'colorsys', 'compileall', 'concurrent', 'configparser',
|
|
24
|
+
'contextlib', 'contextvars', 'copy', 'copyreg', 'cProfile', 'crypt', 'csv',
|
|
25
|
+
'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal', 'difflib',
|
|
26
|
+
'dis', 'distutils', 'doctest', 'email', 'encodings', 'enum', 'errno', 'faulthandler',
|
|
27
|
+
'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'fractions', 'ftplib', 'functools',
|
|
28
|
+
'gc', 'getopt', 'getpass', 'gettext', 'glob', 'graphlib', 'grp', 'gzip',
|
|
29
|
+
'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr', 'imp',
|
|
30
|
+
'importlib', 'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword',
|
|
31
|
+
'lib2to3', 'linecache', 'locale', 'logging', 'lzma', 'mailbox', 'mailcap',
|
|
32
|
+
'marshal', 'math', 'mimetypes', 'mmap', 'modulefinder', 'multiprocessing',
|
|
33
|
+
'netrc', 'nis', 'nntplib', 'numbers', 'operator', 'optparse', 'os', 'ossaudiodev',
|
|
34
|
+
'pathlib', 'pdb', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform',
|
|
35
|
+
'plistlib', 'poplib', 'posix', 'posixpath', 'pprint', 'profile', 'pstats',
|
|
36
|
+
'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc', 'queue', 'quopri', 'random',
|
|
37
|
+
're', 'readline', 'reprlib', 'resource', 'rlcompleter', 'runpy', 'sched',
|
|
38
|
+
'secrets', 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
|
|
39
|
+
'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd',
|
|
40
|
+
'sqlite3', 'ssl', 'stat', 'statistics', 'string', 'stringprep', 'struct',
|
|
41
|
+
'subprocess', 'sunau', 'symtable', 'sys', 'sysconfig', 'syslog', 'tabnanny',
|
|
42
|
+
'tarfile', 'telnetlib', 'tempfile', 'termios', 'test', 'textwrap', 'threading',
|
|
43
|
+
'time', 'timeit', 'tkinter', 'token', 'tokenize', 'trace', 'traceback',
|
|
44
|
+
'tracemalloc', 'tty', 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata',
|
|
45
|
+
'unittest', 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
|
|
46
|
+
'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml', 'xmlrpc',
|
|
47
|
+
'zipapp', 'zipfile', 'zipimport', 'zlib', 'zoneinfo',
|
|
48
|
+
}
|
|
49
|
+
|
|
18
50
|
|
|
19
51
|
def is_python_reserved_word(word: str) -> bool:
|
|
20
52
|
"""Checks if a word is a Python reserved word"""
|
|
@@ -28,6 +60,13 @@ def is_python_reserved_word(word: str) -> bool:
|
|
|
28
60
|
return word in reserved_words
|
|
29
61
|
|
|
30
62
|
|
|
63
|
+
def safe_package_name(name: str) -> str:
|
|
64
|
+
"""Converts a name to a safe Python package name that won't shadow stdlib"""
|
|
65
|
+
if name.lower() in PYTHON_STDLIB_MODULES:
|
|
66
|
+
return f"{name}_types"
|
|
67
|
+
return name
|
|
68
|
+
|
|
69
|
+
|
|
31
70
|
class StructureToPython:
|
|
32
71
|
""" Converts JSON Structure schema to Python classes """
|
|
33
72
|
|
|
@@ -110,11 +149,38 @@ class StructureToPython:
|
|
|
110
149
|
type_name.startswith('typing.Optional[') or type_name.startswith('typing.Union[') or \
|
|
111
150
|
type_name == 'typing.Any'
|
|
112
151
|
|
|
152
|
+
def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field') -> str:
|
|
153
|
+
"""Converts a name to a safe Python identifier.
|
|
154
|
+
|
|
155
|
+
Handles:
|
|
156
|
+
- Reserved words (append _)
|
|
157
|
+
- Numeric prefixes (prepend _)
|
|
158
|
+
- Special characters (replace with _)
|
|
159
|
+
- All-special-char names (use fallback_prefix)
|
|
160
|
+
- Class name collision (append _)
|
|
161
|
+
"""
|
|
162
|
+
import re
|
|
163
|
+
# Replace invalid characters with underscores
|
|
164
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
165
|
+
# Remove leading/trailing underscores from sanitization, but keep intentional ones
|
|
166
|
+
safe = safe.strip('_') if safe != name else safe
|
|
167
|
+
# If nothing left after removing special chars, use fallback
|
|
168
|
+
if not safe or not re.match(r'^[a-zA-Z_]', safe):
|
|
169
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
170
|
+
safe = '_' + safe # Numeric prefix
|
|
171
|
+
else:
|
|
172
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
173
|
+
# Handle reserved words
|
|
174
|
+
if is_python_reserved_word(safe):
|
|
175
|
+
safe = safe + '_'
|
|
176
|
+
# Handle class name collision
|
|
177
|
+
if class_name and safe == class_name:
|
|
178
|
+
safe = safe + '_'
|
|
179
|
+
return safe
|
|
180
|
+
|
|
113
181
|
def safe_name(self, name: str) -> str:
|
|
114
|
-
"""Converts a name to a safe Python name"""
|
|
115
|
-
|
|
116
|
-
return name + "_"
|
|
117
|
-
return name
|
|
182
|
+
"""Converts a name to a safe Python name (legacy wrapper)"""
|
|
183
|
+
return self.safe_identifier(name)
|
|
118
184
|
|
|
119
185
|
def pascal_type_name(self, ref: str) -> str:
|
|
120
186
|
"""Converts a reference to a type name"""
|
|
@@ -324,7 +390,7 @@ class StructureToPython:
|
|
|
324
390
|
# Generate field docstrings
|
|
325
391
|
field_docstrings = [{
|
|
326
392
|
'name': self.safe_name(field['name']),
|
|
327
|
-
'original_name': field['name'],
|
|
393
|
+
'original_name': field.get('json_name') or field['name'],
|
|
328
394
|
'type': field['type'],
|
|
329
395
|
'is_primitive': field['is_primitive'],
|
|
330
396
|
'is_enum': field['is_enum'],
|
|
@@ -369,7 +435,10 @@ class StructureToPython:
|
|
|
369
435
|
def generate_field(self, prop_name: str, prop_schema: Dict, class_name: str,
|
|
370
436
|
parent_namespace: str, required_props: List, import_types: Set[str]) -> Dict:
|
|
371
437
|
""" Generates a field for a Python dataclass """
|
|
372
|
-
|
|
438
|
+
# Sanitize field name for Python identifier validity
|
|
439
|
+
field_name = self.safe_identifier(prop_name, class_name)
|
|
440
|
+
# Track if we need a field_name annotation for JSON serialization
|
|
441
|
+
needs_field_name_annotation = field_name != prop_name
|
|
373
442
|
|
|
374
443
|
# Check if this is a const field
|
|
375
444
|
if 'const' in prop_schema:
|
|
@@ -378,6 +447,7 @@ class StructureToPython:
|
|
|
378
447
|
class_name, field_name, prop_schema, parent_namespace, import_types)
|
|
379
448
|
return {
|
|
380
449
|
'name': field_name,
|
|
450
|
+
'json_name': prop_name if needs_field_name_annotation else None,
|
|
381
451
|
'type': prop_type,
|
|
382
452
|
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
383
453
|
'is_enum': False,
|
|
@@ -404,6 +474,7 @@ class StructureToPython:
|
|
|
404
474
|
|
|
405
475
|
return {
|
|
406
476
|
'name': field_name,
|
|
477
|
+
'json_name': prop_name if needs_field_name_annotation else None,
|
|
407
478
|
'type': prop_type,
|
|
408
479
|
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
409
480
|
'is_enum': prop_type in self.generated_types and self.generated_types[prop_type] == 'enum',
|
|
@@ -786,6 +857,7 @@ def convert_structure_to_python(structure_schema_path, py_file_path, package_nam
|
|
|
786
857
|
if base_name.endswith('.struct'):
|
|
787
858
|
base_name = base_name[:-7] # Remove '.struct' suffix
|
|
788
859
|
package_name = base_name.lower().replace('-', '_')
|
|
860
|
+
package_name = safe_package_name(package_name)
|
|
789
861
|
|
|
790
862
|
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
791
863
|
structure_to_python.convert(structure_schema_path, py_file_path)
|
|
@@ -793,6 +865,7 @@ def convert_structure_to_python(structure_schema_path, py_file_path, package_nam
|
|
|
793
865
|
|
|
794
866
|
def convert_structure_schema_to_python(structure_schema, py_file_path, package_name='', dataclasses_json_annotation=False):
|
|
795
867
|
"""Converts JSON Structure schema to Python dataclasses"""
|
|
868
|
+
package_name = safe_package_name(package_name) if package_name else package_name
|
|
796
869
|
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation)
|
|
797
870
|
if isinstance(structure_schema, dict):
|
|
798
871
|
structure_schema = [structure_schema]
|
avrotize/structuretots.py
CHANGED
|
@@ -329,7 +329,7 @@ class StructureToTypeScript:
|
|
|
329
329
|
fields = []
|
|
330
330
|
for prop_name, prop_schema in properties.items():
|
|
331
331
|
field_type = self.convert_structure_type_to_typescript(
|
|
332
|
-
class_name, prop_name, prop_schema,
|
|
332
|
+
class_name, prop_name, prop_schema, schema_namespace, import_types)
|
|
333
333
|
is_required = prop_name in required_props
|
|
334
334
|
is_optional = not is_required
|
|
335
335
|
field_type_no_null = self.strip_nullable(field_type)
|
|
@@ -479,6 +479,7 @@ class StructureToTypeScript:
|
|
|
479
479
|
""" Generates a TypeScript tuple type from JSON Structure tuple type """
|
|
480
480
|
tuple_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'Tuple'))
|
|
481
481
|
namespace = self.concat_namespace(self.base_package, structure_schema.get('namespace', parent_namespace)).lower()
|
|
482
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
482
483
|
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(parent_namespace, tuple_name)
|
|
483
484
|
|
|
484
485
|
if typescript_qualified_name in self.generated_types:
|
|
@@ -489,7 +490,7 @@ class StructureToTypeScript:
|
|
|
489
490
|
item_types = []
|
|
490
491
|
for idx, item in enumerate(tuple_items):
|
|
491
492
|
item_type = self.convert_structure_type_to_typescript(
|
|
492
|
-
tuple_name, f'item{idx}', item,
|
|
493
|
+
tuple_name, f'item{idx}', item, schema_namespace, import_types)
|
|
493
494
|
item_types.append(item_type)
|
|
494
495
|
|
|
495
496
|
# TypeScript tuples are just arrays with fixed length and types
|
|
@@ -737,4 +738,4 @@ def convert_structure_schema_to_typescript(structure_schema: JsonNode, output_di
|
|
|
737
738
|
avro_annotation: Whether to include Avro annotations
|
|
738
739
|
"""
|
|
739
740
|
converter = StructureToTypeScript(package_name, typedjson_annotation, avro_annotation)
|
|
740
|
-
converter.convert_schema(structure_schema, output_dir, package_name)
|
|
741
|
+
converter.convert_schema(structure_schema, output_dir, package_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
avrotize/__init__.py,sha256=t5h5wkHXr6M0mmHAB5rhjZ3Gxy9xutGTGIfojfao9rI,3820
|
|
2
2
|
avrotize/__main__.py,sha256=5pY8dYAURcOnFRvgb6fgaOIa_SOzPLIWbU8-ZTQ0jG4,88
|
|
3
|
-
avrotize/_version.py,sha256=
|
|
3
|
+
avrotize/_version.py,sha256=2Rc4v0v4QLr2kVq6V3tQOneBqZW00NrqxdxCLKrfD00,706
|
|
4
4
|
avrotize/asn1toavro.py,sha256=QDNwfBfXMxSH-k487CA3CaGCGDzOLs4PpVbbENm5uF0,8386
|
|
5
5
|
avrotize/avrotize.py,sha256=VHFpBltMVBpyt0ju3ZWW725BKjQ4Fk-nrAy8udW-X44,5713
|
|
6
6
|
avrotize/avrotocpp.py,sha256=hRZV247_TDD7Sm6_8sFx-UH5SueLLx2Wg6TvAVUX0iE,25693
|
|
@@ -8,20 +8,20 @@ avrotize/avrotocsharp.py,sha256=k-tBex_vT-79yKZNetP9pKF63206NlT7KKBIXies9Cg,7140
|
|
|
8
8
|
avrotize/avrotocsv.py,sha256=PaDEW2aGRFVNLwewWhJ3OwxbKFI3PBg_mTgtT4uLMko,3689
|
|
9
9
|
avrotize/avrotodatapackage.py,sha256=zSCphLvCYiBKRAUCdccsr-4JysH3PyAS6fSgwa65Tss,7259
|
|
10
10
|
avrotize/avrotodb.py,sha256=5fNJgz00VMimyOl7eI0lIxlcaN_JnN0mb2Q9lzCRecw,46989
|
|
11
|
-
avrotize/avrotogo.py,sha256=
|
|
11
|
+
avrotize/avrotogo.py,sha256=H4jxdO7laRWKJuU6mytz2sgUa_20hZUlsBrUGrCe_UU,22405
|
|
12
12
|
avrotize/avrotographql.py,sha256=i6G7xWjH_Lsn_CLiM4BCPb8OyZuCCpsYjXwXNTRMwEE,7394
|
|
13
13
|
avrotize/avrotoiceberg.py,sha256=plVHGWkED1YDLcMDxL7NMdJl2f8G32hwlNWFrBLcsD8,9057
|
|
14
|
-
avrotize/avrotojava.py,sha256=
|
|
14
|
+
avrotize/avrotojava.py,sha256=NZZ7mUFVzvp7HBsU0XPiCwl4GVXE1RtS86pfNFJsIq8,135427
|
|
15
15
|
avrotize/avrotojs.py,sha256=QjB6XjFnDrpZBZrrWqS0TN8fQfRXBfhHabfG73FOIo8,12249
|
|
16
|
-
avrotize/avrotojsons.py,sha256=
|
|
16
|
+
avrotize/avrotojsons.py,sha256=I7EJz_IaCYLNzcycYXDOLAexHRHQfyb4yoWcxJ02vb0,22697
|
|
17
17
|
avrotize/avrotojstruct.py,sha256=-Hs4Ta958bRKmOfSTzRFENABCZ6lQPSPbIBEXvOQD1M,14660
|
|
18
18
|
avrotize/avrotokusto.py,sha256=D6AiRPa5uiZbqo9dqrFvAknsF5oNXgIzk8_08uZTZ2M,17636
|
|
19
19
|
avrotize/avrotomd.py,sha256=WHPHnfmkI3xDNIHKZ3ReYxj6tib1eCny3JOznNSN6r8,5348
|
|
20
20
|
avrotize/avrotools.py,sha256=dTbGgWQyKdSuvCf4yuoymwhYO5gX9ywPu-klIXYwKZM,6052
|
|
21
21
|
avrotize/avrotoparquet.py,sha256=qm5hfia5elW1Yn4KACG8bbudLAqQSwGk3fIkTvdT5Rg,9088
|
|
22
22
|
avrotize/avrotoproto.py,sha256=STqbdGjVrgKrlKXt-6dZlekW_Oq0W0StRx80St1XqIc,22486
|
|
23
|
-
avrotize/avrotopython.py,sha256=
|
|
24
|
-
avrotize/avrotorust.py,sha256=
|
|
23
|
+
avrotize/avrotopython.py,sha256=7s73rKyaQDu5-uLnF4mK7Jat9F4jipQ-lLapPXg6aPI,34168
|
|
24
|
+
avrotize/avrotorust.py,sha256=HEcDirRBCbXQNNs_FmkT-sp1dWQgZ8A23qkQYUxVuXE,24255
|
|
25
25
|
avrotize/avrotots.py,sha256=u_XLjlHN0Gof5QYlpqK4X9WoX9rL30TjQMPg4TiyYnI,33241
|
|
26
26
|
avrotize/avrotoxsd.py,sha256=iGQq_8kC0kfKsqvqS6s_mO-kJ8N5G8vXOwqRI_DZUxc,17744
|
|
27
27
|
avrotize/cddltostructure.py,sha256=MA2c-P3CIEAxEaBX-FF299gR55xcLEV3FrfTr2QfayM,74491
|
|
@@ -44,22 +44,22 @@ avrotize/proto3parser.py,sha256=MfE84c-oAWWuzYmKlEZ5g5LUF7YzZaASFh2trX3UCaw,1560
|
|
|
44
44
|
avrotize/prototoavro.py,sha256=hqXBGRxYojaEbEgoHZxXwMG4R1nWC7UMl_XNLWfqH38,17346
|
|
45
45
|
avrotize/structuretocddl.py,sha256=RK_dTJf0oAo6BIBM48NHRcWC96OtUjlgUC6HzXs5Lkk,21234
|
|
46
46
|
avrotize/structuretocpp.py,sha256=tBWOvyZPYQ1CHN6RgDnWlmzJ1giOyQ9SlHBHWvhPyiw,35898
|
|
47
|
-
avrotize/structuretocsharp.py,sha256=
|
|
47
|
+
avrotize/structuretocsharp.py,sha256=Hi6C2HAIB3DxKZ4s8Q2_rK9QOqI_cLmObLkpwI3iO1o,123681
|
|
48
48
|
avrotize/structuretocsv.py,sha256=w9cwXAnnakKaeTtXsLWWO8KwYnXUxyXvC7a-ZKs-E94,13851
|
|
49
49
|
avrotize/structuretodatapackage.py,sha256=NEHRt30KfVDWH1EQakvuMdRZTtfVXx8fsaYud0ofb2g,29768
|
|
50
|
-
avrotize/structuretodb.py,sha256=
|
|
51
|
-
avrotize/structuretogo.py,sha256=
|
|
50
|
+
avrotize/structuretodb.py,sha256=uk4hKSRNw1JwOsWSZGECjSwvmTFUipRvMgTGlKnviCo,44860
|
|
51
|
+
avrotize/structuretogo.py,sha256=EtU7C5IqKzPrYaxKUQJMDMpk-rlc8eRwFzHe489jHp4,34100
|
|
52
52
|
avrotize/structuretographql.py,sha256=wcGXnrup5v5saRa1BhR6o-X8q8ujsQMVqrFHQTBPjww,20468
|
|
53
53
|
avrotize/structuretoiceberg.py,sha256=itKb33Kj-7-udk4eHTLmTEasIeh1ggpZ3e_bwCxLABM,15344
|
|
54
|
-
avrotize/structuretojava.py,sha256=
|
|
54
|
+
avrotize/structuretojava.py,sha256=r6YfI-_HtdeGqotL5W31WR5sz-TE1VLdDegctpjeiC4,47886
|
|
55
55
|
avrotize/structuretojs.py,sha256=TJuhUGqn0F2omjPVcrPeFPHY2KynRceFg9gMGvEenxA,29608
|
|
56
56
|
avrotize/structuretojsons.py,sha256=PJrQBaf6yQHu5eFkePxbjPBEmL-fYfX2wj6OmH1jsWw,22495
|
|
57
57
|
avrotize/structuretokusto.py,sha256=rOKgYIcm7ZK8RS-VvMFPNzPzwtv7c4dIKU-fKjrJLyM,30618
|
|
58
58
|
avrotize/structuretomd.py,sha256=exfCldYbieVdduhotSoLrxsbphmyJQyeQso9qv4qyUw,13642
|
|
59
59
|
avrotize/structuretoproto.py,sha256=Aq0-fwMXSjjAxgZ5mq1kpo_TauigMRrJK9LNyoN-YGs,42679
|
|
60
|
-
avrotize/structuretopython.py,sha256=
|
|
60
|
+
avrotize/structuretopython.py,sha256=d9EZVDHq7r-x0ZYZIRYfCP6kub7MkEROuvzjTJfNVv0,43958
|
|
61
61
|
avrotize/structuretorust.py,sha256=ChRmO7uzU-pMdDdS0Vtg-MVUaOaNhNUPwH-ZKKOHglU,35134
|
|
62
|
-
avrotize/structuretots.py,sha256=
|
|
62
|
+
avrotize/structuretots.py,sha256=7jKoMOWwSJG_T_uZuEcPu-4d0dMuydAHvUhE86QSEJU,35274
|
|
63
63
|
avrotize/structuretoxsd.py,sha256=01VpasyWSMOx04sILHLP7H-WkhGdXAEGKohUUfgrNf0,32797
|
|
64
64
|
avrotize/xsdtoavro.py,sha256=nQtNH_3pEZBp67oUCPqzhvItEExHTe-8obsIfNRXt8Y,19064
|
|
65
65
|
avrotize/avrotocpp/CMakeLists.txt.jinja,sha256=t2ADvvi3o20xfVrsRBaUvZlpVCDR4h6Szsf0GcuSkH0,3015
|
|
@@ -81,18 +81,18 @@ avrotize/avrotogo/go_helpers.jinja,sha256=WSjLIqkrYLUKdQBejEpOnNiENCaU8HQvm4gshy
|
|
|
81
81
|
avrotize/avrotogo/go_struct.jinja,sha256=vI-xGVC1O7uAFYoY0T0kbT2Be5gFDkgc_UdFUbbpHZk,4272
|
|
82
82
|
avrotize/avrotogo/go_test.jinja,sha256=r1tGQqHP5MiSRj3yQhPSQLNSx1ihfsl6MnUU7xbHAgM,1206
|
|
83
83
|
avrotize/avrotogo/go_union.jinja,sha256=OcRVPWI-1OvYthh7cB-8RMT9nJEOStAQMfbW3b5hpUU,890
|
|
84
|
-
avrotize/avrotojava/class_test.java.jinja,sha256=
|
|
84
|
+
avrotize/avrotojava/class_test.java.jinja,sha256=H950i6RlEoOoJadgUbEWOaeLIR3M9NHEF_hm4wFwbJs,9225
|
|
85
85
|
avrotize/avrotojava/enum_test.java.jinja,sha256=INIKtiKrZwSFXVXgv8ouvzGv8vzjWouRq3DAPqxRs8k,545
|
|
86
86
|
avrotize/avrotojava/testproject.pom.jinja,sha256=qRKGs9cCfex0hNgryyIJLae-pSpP4LMpPYcn8H8bBvk,2072
|
|
87
87
|
avrotize/avrotomd/README.md.jinja,sha256=efG9ly41AjNMuOOnq9U7LXEhGylfHhVTzqe_COM4cRM,999
|
|
88
|
-
avrotize/avrotopython/dataclass_core.jinja,sha256=
|
|
88
|
+
avrotize/avrotopython/dataclass_core.jinja,sha256=hvLtsN67dlPDmTIHScrUE0X1z3KxKMDPmIg80ziKCAk,10616
|
|
89
89
|
avrotize/avrotopython/enum_core.jinja,sha256=QP5FDkMg5Si6xSHCr6L-nMe_94RH2mJdcAmFJH69BiY,2779
|
|
90
90
|
avrotize/avrotopython/pyproject_toml.jinja,sha256=fEtJrk4pTrHCcvwpGf1KYoORA90HeJZdmeusx_WVAeo,462
|
|
91
91
|
avrotize/avrotopython/test_class.jinja,sha256=h_8pyvrTRa9DTRzUP5tjnzXF4bMjbovqjNqDdx1CZYM,3203
|
|
92
92
|
avrotize/avrotopython/test_enum.jinja,sha256=PeVxzC-gV5bizVnRs58RPoUDbXGI696B-VO4g-qB2lc,528
|
|
93
|
-
avrotize/avrotorust/dataclass_enum.rs.jinja,sha256
|
|
94
|
-
avrotize/avrotorust/dataclass_struct.rs.jinja,sha256=
|
|
95
|
-
avrotize/avrotorust/dataclass_union.rs.jinja,sha256=
|
|
93
|
+
avrotize/avrotorust/dataclass_enum.rs.jinja,sha256=g4aYp0a7Yk8jO6aIiTXU8Obm48Qv63zxbzGREsWBBK8,2102
|
|
94
|
+
avrotize/avrotorust/dataclass_struct.rs.jinja,sha256=_FAKZG9VSRhn5enJScH8Y3t5KJ0aXTRqzGbMTjO6-dk,8463
|
|
95
|
+
avrotize/avrotorust/dataclass_union.rs.jinja,sha256=Ye4zVqpHgFNMUuvULCqN1aCWqezbWHppF_N_0hU0W2U,4640
|
|
96
96
|
avrotize/avrotots/class_core.ts.jinja,sha256=KEKSXsjPVTXP_LTOIUVjsTWfT3g1ZVSrGjy0UMkEj0g,5081
|
|
97
97
|
avrotize/avrotots/class_test.ts.jinja,sha256=zm3wXaSJ225y7knLj6I9zjrkPTEROLHYmBr8eDo7pCo,2752
|
|
98
98
|
avrotize/avrotots/enum_core.ts.jinja,sha256=6NN_nF2y9us2saM7Wag8IN73BdIF0vxhPxq83NQEnm8,1522
|
|
@@ -139,7 +139,7 @@ avrotize/structuretojava/choice_core.jinja,sha256=H-VhQzIIwgu9myuw3j9WxFx01maTOl
|
|
|
139
139
|
avrotize/structuretojava/class_core.jinja,sha256=mn3KVnvIxHIUB1Tep_YeISnM7k6ytQdOynQ4sUGrCAc,981
|
|
140
140
|
avrotize/structuretojava/enum_core.jinja,sha256=yXnXQbr3IEudUj5OOFqDUJT7TfGqAJMy6O6K5DQ1lUQ,487
|
|
141
141
|
avrotize/structuretojava/equals_hashcode.jinja,sha256=m_6EBJdRfQWnP8dmEJ2vkosyFaNkVyYDtyb9RKQCZtQ,946
|
|
142
|
-
avrotize/structuretojava/pom.xml.jinja,sha256=
|
|
142
|
+
avrotize/structuretojava/pom.xml.jinja,sha256=DxGdDXa3XgdD5vMp498Fzf-nSEX3Fx4grKG09-g32Bg,1305
|
|
143
143
|
avrotize/structuretojava/tuple_core.jinja,sha256=IZl3s_olYhwasA21uFqSZCyq4tLZ_jtQVD6ZQgUKmkk,1731
|
|
144
144
|
avrotize/structuretojs/class_core.js.jinja,sha256=-DiN6dHxiEAc3bWCV6EAH1TpEdO2Fhs6d3tgYzxdLM4,779
|
|
145
145
|
avrotize/structuretojs/enum_core.js.jinja,sha256=iMDePiuj4d7s7VuUOv3MnkFLKV9XBzReMR4TudZ3MbQ,205
|
|
@@ -148,7 +148,7 @@ avrotize/structuretojs/test_class.js.jinja,sha256=XTQEtuFRgQb0ixrA_E9uK3tM4OJCf3
|
|
|
148
148
|
avrotize/structuretojs/test_enum.js.jinja,sha256=5vGrqUigmSFx_wFQQD-nE7P6o_HAyZEnmWpW0ScAkfw,1393
|
|
149
149
|
avrotize/structuretojs/test_runner.js.jinja,sha256=RH-wqqBTVjiFzp0k5id5vtMGVllHTQbihnRRgsM5C7k,1185
|
|
150
150
|
avrotize/structuretomd/README.md.jinja,sha256=UBBkspRnVI9E3RLRWPbxKSvoU9y4tKi1iqzn2iGJbDQ,4824
|
|
151
|
-
avrotize/structuretopython/dataclass_core.jinja,sha256=
|
|
151
|
+
avrotize/structuretopython/dataclass_core.jinja,sha256=fNcGjBx4rgJZQ1rNqoJ5npWZwr8j75R61W3-kSskJqk,15793
|
|
152
152
|
avrotize/structuretopython/enum_core.jinja,sha256=DDE7Mw9M2hjb0x1ErY9IvwASgAkI1hzFWjKbHsTfDRs,1253
|
|
153
153
|
avrotize/structuretopython/map_alias.jinja,sha256=lq1vh37yrMTlOKQXhcu5xxkZg8kCDnskw1DUqW06aq0,567
|
|
154
154
|
avrotize/structuretopython/pyproject_toml.jinja,sha256=IuMr-XjXVmTx1R91DGLJ6y3LskCBcwt-xxJNi5hR1qA,587
|
|
@@ -164,8 +164,8 @@ avrotize/structuretots/index.ts.jinja,sha256=-R4R_En1N4W_BEN3z3bLts9Xi4KnBTDLrYM
|
|
|
164
164
|
avrotize/structuretots/package.json.jinja,sha256=OfJn4g68VhBP-yvJCdsWm_1RHx1kphsmdWpxu_Fst1E,819
|
|
165
165
|
avrotize/structuretots/test_class.ts.jinja,sha256=7tJ6hPo3A9zToTkwolVyXYhmZ_E4uI_OnnYsUUzUEdQ,1180
|
|
166
166
|
avrotize/structuretots/tsconfig.json.jinja,sha256=8Pl65JW8uOMEexxkteobo0ZEqsJBO31HegNRUrf8XGQ,515
|
|
167
|
-
avrotize-2.
|
|
168
|
-
avrotize-2.
|
|
169
|
-
avrotize-2.
|
|
170
|
-
avrotize-2.
|
|
171
|
-
avrotize-2.
|
|
167
|
+
avrotize-2.22.1.dist-info/entry_points.txt,sha256=m8J2TWiqbZh7SBQezc1CNrM_GVPWf01zOFcAKhzCC0U,51
|
|
168
|
+
avrotize-2.22.1.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
|
|
169
|
+
avrotize-2.22.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
170
|
+
avrotize-2.22.1.dist-info/METADATA,sha256=VlqM6wO1RE7NuEFnJMM2Gcdy3ZCJRVt1jhw6Um3pzig,80208
|
|
171
|
+
avrotize-2.22.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|