structurize 2.21.0__py3-none-any.whl → 2.22.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avrotize/_version.py +3 -3
- avrotize/avrotogo.py +21 -8
- avrotize/avrotojava.py +67 -5
- avrotize/avrotopython.py +48 -2
- avrotize/avrotorust.py +64 -26
- avrotize/structuretocsharp.py +42 -11
- avrotize/structuretodb.py +21 -0
- avrotize/structuretogo.py +38 -10
- avrotize/structuretojava.py +114 -8
- avrotize/structuretopython.py +100 -19
- avrotize/structuretots.py +8 -5
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/METADATA +1 -1
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/RECORD +17 -17
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/WHEEL +0 -0
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/entry_points.txt +0 -0
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/licenses/LICENSE +0 -0
- {structurize-2.21.0.dist-info → structurize-2.22.0.dist-info}/top_level.txt +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.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 22, 0)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g5d3e04df0'
|
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)
|
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/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':
|
|
@@ -327,7 +369,8 @@ class AvroToPython:
|
|
|
327
369
|
def generate_test_class(self, package_name: str, class_name: str, fields: List[Dict[str, str]], import_types: Set[str]) -> None:
|
|
328
370
|
"""Generates a unit test class for a Python data class"""
|
|
329
371
|
test_class_name = f"Test_{class_name}"
|
|
330
|
-
|
|
372
|
+
flat_package = package_name.replace('.', '_').lower()
|
|
373
|
+
tests_package_name = flat_package if flat_package.startswith('test_') else f"test_{flat_package}"
|
|
331
374
|
test_class_definition = process_template(
|
|
332
375
|
"avrotopython/test_class.jinja",
|
|
333
376
|
package_name=package_name,
|
|
@@ -348,7 +391,8 @@ class AvroToPython:
|
|
|
348
391
|
def generate_test_enum(self, package_name: str, class_name: str, symbols: List[str]) -> None:
|
|
349
392
|
"""Generates a unit test class for a Python enum"""
|
|
350
393
|
test_class_name = f"Test_{class_name}"
|
|
351
|
-
|
|
394
|
+
flat_package = package_name.replace('.', '_').lower()
|
|
395
|
+
tests_package_name = flat_package if flat_package.startswith('test_') else f"test_{flat_package}"
|
|
352
396
|
test_class_definition = process_template(
|
|
353
397
|
"avrotopython/test_enum.jinja",
|
|
354
398
|
package_name=package_name,
|
|
@@ -609,6 +653,7 @@ def convert_avro_to_python(avro_schema_path, py_file_path, package_name='', data
|
|
|
609
653
|
if not package_name:
|
|
610
654
|
package_name = os.path.splitext(os.path.basename(avro_schema_path))[
|
|
611
655
|
0].lower().replace('-', '_')
|
|
656
|
+
package_name = safe_package_name(package_name)
|
|
612
657
|
|
|
613
658
|
avro_to_python = AvroToPython(
|
|
614
659
|
package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
@@ -617,6 +662,7 @@ def convert_avro_to_python(avro_schema_path, py_file_path, package_name='', data
|
|
|
617
662
|
|
|
618
663
|
def convert_avro_schema_to_python(avro_schema, py_file_path, package_name='', dataclasses_json_annotation=False, avro_annotation=False):
|
|
619
664
|
"""Converts Avro schema to Python data classes"""
|
|
665
|
+
package_name = safe_package_name(package_name) if package_name else package_name
|
|
620
666
|
avro_to_python = AvroToPython(
|
|
621
667
|
package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
622
668
|
if isinstance(avro_schema, dict):
|
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)
|
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"""
|
|
@@ -384,7 +405,12 @@ class StructureToJava:
|
|
|
384
405
|
field_count=len(field_names)
|
|
385
406
|
)
|
|
386
407
|
|
|
387
|
-
|
|
408
|
+
# Generate createTestInstance() method for testing (only for non-abstract classes)
|
|
409
|
+
create_test_instance = ''
|
|
410
|
+
if not is_abstract:
|
|
411
|
+
create_test_instance = self.generate_create_test_instance_method(class_name, fields, schema_namespace)
|
|
412
|
+
|
|
413
|
+
class_definition = class_definition.rstrip() + create_test_instance + equals_hashcode + "\n}\n"
|
|
388
414
|
|
|
389
415
|
if write_file:
|
|
390
416
|
self.write_to_file(package, class_name, class_definition)
|
|
@@ -455,6 +481,86 @@ class StructureToJava:
|
|
|
455
481
|
return str(value)
|
|
456
482
|
return f"/* unsupported const value */"
|
|
457
483
|
|
|
484
|
+
def get_test_value(self, java_type: str) -> str:
|
|
485
|
+
""" Get a test value for a Java type """
|
|
486
|
+
# Handle arrays/lists
|
|
487
|
+
if java_type.startswith("List<") or java_type.startswith("ArrayList<"):
|
|
488
|
+
return "new java.util.ArrayList<>()"
|
|
489
|
+
if java_type.startswith("Map<"):
|
|
490
|
+
return "new java.util.HashMap<>()"
|
|
491
|
+
if java_type.endswith("[]"):
|
|
492
|
+
return f"new {java_type[:-2]}[0]"
|
|
493
|
+
|
|
494
|
+
# Primitive test values
|
|
495
|
+
test_values = {
|
|
496
|
+
'String': '"test-string"',
|
|
497
|
+
'string': '"test-string"',
|
|
498
|
+
'int': '42',
|
|
499
|
+
'Integer': '42',
|
|
500
|
+
'long': '42L',
|
|
501
|
+
'Long': '42L',
|
|
502
|
+
'double': '3.14',
|
|
503
|
+
'Double': '3.14',
|
|
504
|
+
'float': '3.14f',
|
|
505
|
+
'Float': '3.14f',
|
|
506
|
+
'boolean': 'true',
|
|
507
|
+
'Boolean': 'true',
|
|
508
|
+
'byte': '(byte)0',
|
|
509
|
+
'Byte': '(byte)0',
|
|
510
|
+
'short': '(short)0',
|
|
511
|
+
'Short': '(short)0',
|
|
512
|
+
'BigInteger': 'java.math.BigInteger.ZERO',
|
|
513
|
+
'BigDecimal': 'java.math.BigDecimal.ZERO',
|
|
514
|
+
'byte[]': 'new byte[0]',
|
|
515
|
+
'LocalDate': 'java.time.LocalDate.now()',
|
|
516
|
+
'LocalTime': 'java.time.LocalTime.now()',
|
|
517
|
+
'Instant': 'java.time.Instant.now()',
|
|
518
|
+
'Duration': 'java.time.Duration.ZERO',
|
|
519
|
+
'UUID': 'java.util.UUID.randomUUID()',
|
|
520
|
+
'URI': 'java.net.URI.create("http://example.com")',
|
|
521
|
+
'Object': 'new Object()',
|
|
522
|
+
'void': 'null',
|
|
523
|
+
'Void': 'null',
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if java_type in test_values:
|
|
527
|
+
return test_values[java_type]
|
|
528
|
+
|
|
529
|
+
# Check if it's a generated type (enum or class)
|
|
530
|
+
if java_type in self.generated_types_java_package:
|
|
531
|
+
type_kind = self.generated_types_java_package[java_type]
|
|
532
|
+
if type_kind == "enum":
|
|
533
|
+
return f'{java_type}.values()[0]'
|
|
534
|
+
elif type_kind == "class":
|
|
535
|
+
return f'{java_type}.createTestInstance()'
|
|
536
|
+
|
|
537
|
+
# Default: try to instantiate
|
|
538
|
+
return f'new {java_type}()'
|
|
539
|
+
|
|
540
|
+
def generate_create_test_instance_method(self, class_name: str, fields: List[Dict], parent_package: str) -> str:
|
|
541
|
+
""" Generates a static createTestInstance method that creates a fully initialized instance """
|
|
542
|
+
method = f"\n{INDENT}/**\n{INDENT} * Creates a test instance with all required fields populated\n{INDENT} * @return a fully initialized test instance\n{INDENT} */\n"
|
|
543
|
+
method += f"{INDENT}public static {class_name} createTestInstance() {{\n"
|
|
544
|
+
method += f"{INDENT*2}{class_name} instance = new {class_name}();\n"
|
|
545
|
+
|
|
546
|
+
for field in fields:
|
|
547
|
+
# Skip const fields
|
|
548
|
+
if field.get('is_const', False):
|
|
549
|
+
continue
|
|
550
|
+
|
|
551
|
+
field_name = field['name']
|
|
552
|
+
field_type = field['type']
|
|
553
|
+
|
|
554
|
+
# Get a test value for this field
|
|
555
|
+
test_value = self.get_test_value(field_type)
|
|
556
|
+
|
|
557
|
+
# Setter name: set{Pascal(field_name)}
|
|
558
|
+
method += f"{INDENT*2}instance.set{pascal(field_name)}({test_value});\n"
|
|
559
|
+
|
|
560
|
+
method += f"{INDENT*2}return instance;\n"
|
|
561
|
+
method += f"{INDENT}}}\n"
|
|
562
|
+
return method
|
|
563
|
+
|
|
458
564
|
def generate_enum(self, structure_schema: Dict, field_name: str, parent_package: str, write_file: bool) -> JavaType:
|
|
459
565
|
""" Generates a Java enum from JSON Structure enum schema """
|
|
460
566
|
|
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"""
|
|
@@ -286,6 +352,7 @@ class StructureToPython:
|
|
|
286
352
|
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
|
|
287
353
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
288
354
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
355
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
289
356
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
290
357
|
|
|
291
358
|
if python_qualified_name in self.generated_types:
|
|
@@ -323,7 +390,7 @@ class StructureToPython:
|
|
|
323
390
|
# Generate field docstrings
|
|
324
391
|
field_docstrings = [{
|
|
325
392
|
'name': self.safe_name(field['name']),
|
|
326
|
-
'original_name': field['name'],
|
|
393
|
+
'original_name': field.get('json_name') or field['name'],
|
|
327
394
|
'type': field['type'],
|
|
328
395
|
'is_primitive': field['is_primitive'],
|
|
329
396
|
'is_enum': field['is_enum'],
|
|
@@ -358,8 +425,8 @@ class StructureToPython:
|
|
|
358
425
|
)
|
|
359
426
|
|
|
360
427
|
if write_file:
|
|
361
|
-
self.write_to_file(
|
|
362
|
-
self.generate_test_class(
|
|
428
|
+
self.write_to_file(package_name, class_name, class_definition)
|
|
429
|
+
self.generate_test_class(package_name, class_name, field_docstrings, import_types)
|
|
363
430
|
|
|
364
431
|
self.generated_types[python_qualified_name] = 'class'
|
|
365
432
|
self.generated_structure_types[python_qualified_name] = structure_schema
|
|
@@ -368,7 +435,10 @@ class StructureToPython:
|
|
|
368
435
|
def generate_field(self, prop_name: str, prop_schema: Dict, class_name: str,
|
|
369
436
|
parent_namespace: str, required_props: List, import_types: Set[str]) -> Dict:
|
|
370
437
|
""" Generates a field for a Python dataclass """
|
|
371
|
-
|
|
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
|
|
372
442
|
|
|
373
443
|
# Check if this is a const field
|
|
374
444
|
if 'const' in prop_schema:
|
|
@@ -377,6 +447,7 @@ class StructureToPython:
|
|
|
377
447
|
class_name, field_name, prop_schema, parent_namespace, import_types)
|
|
378
448
|
return {
|
|
379
449
|
'name': field_name,
|
|
450
|
+
'json_name': prop_name if needs_field_name_annotation else None,
|
|
380
451
|
'type': prop_type,
|
|
381
452
|
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
382
453
|
'is_enum': False,
|
|
@@ -403,6 +474,7 @@ class StructureToPython:
|
|
|
403
474
|
|
|
404
475
|
return {
|
|
405
476
|
'name': field_name,
|
|
477
|
+
'json_name': prop_name if needs_field_name_annotation else None,
|
|
406
478
|
'type': prop_type,
|
|
407
479
|
'is_primitive': self.is_python_primitive(prop_type) or self.is_python_typing_struct(prop_type),
|
|
408
480
|
'is_enum': prop_type in self.generated_types and self.generated_types[prop_type] == 'enum',
|
|
@@ -424,6 +496,7 @@ class StructureToPython:
|
|
|
424
496
|
class_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
425
497
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
426
498
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
499
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
427
500
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
428
501
|
|
|
429
502
|
if python_qualified_name in self.generated_types:
|
|
@@ -442,8 +515,8 @@ class StructureToPython:
|
|
|
442
515
|
)
|
|
443
516
|
|
|
444
517
|
if write_file:
|
|
445
|
-
self.write_to_file(
|
|
446
|
-
self.generate_test_enum(
|
|
518
|
+
self.write_to_file(package_name, class_name, enum_definition)
|
|
519
|
+
self.generate_test_enum(package_name, class_name, symbols)
|
|
447
520
|
|
|
448
521
|
self.generated_types[python_qualified_name] = 'enum'
|
|
449
522
|
self.generated_enum_symbols[python_qualified_name] = symbols
|
|
@@ -513,6 +586,7 @@ class StructureToPython:
|
|
|
513
586
|
class_name = pascal(structure_schema.get('name', 'UnnamedMap'))
|
|
514
587
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
515
588
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
589
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
516
590
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
517
591
|
|
|
518
592
|
if python_qualified_name in self.generated_types:
|
|
@@ -537,7 +611,7 @@ class StructureToPython:
|
|
|
537
611
|
)
|
|
538
612
|
|
|
539
613
|
if write_file:
|
|
540
|
-
self.write_to_file(
|
|
614
|
+
self.write_to_file(package_name, class_name, map_definition)
|
|
541
615
|
|
|
542
616
|
self.generated_types[python_qualified_name] = 'map'
|
|
543
617
|
return python_qualified_name
|
|
@@ -623,7 +697,8 @@ class StructureToPython:
|
|
|
623
697
|
import_types: Set[str]) -> None:
|
|
624
698
|
"""Generates a unit test class for a Python dataclass"""
|
|
625
699
|
test_class_name = f"Test_{class_name}"
|
|
626
|
-
|
|
700
|
+
# Use a simpler file naming scheme based on class name only
|
|
701
|
+
test_file_name = f"test_{class_name.lower()}"
|
|
627
702
|
test_class_definition = process_template(
|
|
628
703
|
"structuretopython/test_class.jinja",
|
|
629
704
|
package_name=package_name,
|
|
@@ -636,7 +711,7 @@ class StructureToPython:
|
|
|
636
711
|
)
|
|
637
712
|
|
|
638
713
|
base_dir = os.path.join(self.output_dir, "tests")
|
|
639
|
-
test_file_path = os.path.join(base_dir, f"{
|
|
714
|
+
test_file_path = os.path.join(base_dir, f"{test_file_name}.py")
|
|
640
715
|
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
641
716
|
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
642
717
|
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
@@ -645,7 +720,8 @@ class StructureToPython:
|
|
|
645
720
|
def generate_test_enum(self, package_name: str, class_name: str, symbols: List[str]) -> None:
|
|
646
721
|
"""Generates a unit test class for a Python enum"""
|
|
647
722
|
test_class_name = f"Test_{class_name}"
|
|
648
|
-
|
|
723
|
+
# Use a simpler file naming scheme based on class name only
|
|
724
|
+
test_file_name = f"test_{class_name.lower()}"
|
|
649
725
|
test_class_definition = process_template(
|
|
650
726
|
"structuretopython/test_enum.jinja",
|
|
651
727
|
package_name=package_name,
|
|
@@ -654,7 +730,7 @@ class StructureToPython:
|
|
|
654
730
|
symbols=symbols
|
|
655
731
|
)
|
|
656
732
|
base_dir = os.path.join(self.output_dir, "tests")
|
|
657
|
-
test_file_path = os.path.join(base_dir, f"{
|
|
733
|
+
test_file_path = os.path.join(base_dir, f"{test_file_name}.py")
|
|
658
734
|
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
659
735
|
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
660
736
|
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
@@ -662,9 +738,8 @@ class StructureToPython:
|
|
|
662
738
|
|
|
663
739
|
def write_to_file(self, package: str, class_name: str, python_code: str):
|
|
664
740
|
"""Writes a Python class to a file"""
|
|
665
|
-
#
|
|
666
|
-
|
|
667
|
-
parent_package_name = '.'.join(full_package.split('.')[:-1])
|
|
741
|
+
# The containing directory is the parent package (matches avrotopython.py)
|
|
742
|
+
parent_package_name = '.'.join(package.split('.')[:-1])
|
|
668
743
|
parent_package_path = os.sep.join(parent_package_name.split('.')).lower()
|
|
669
744
|
directory_path = os.path.join(self.output_dir, "src", parent_package_path)
|
|
670
745
|
if not os.path.exists(directory_path):
|
|
@@ -777,7 +852,12 @@ class StructureToPython:
|
|
|
777
852
|
def convert_structure_to_python(structure_schema_path, py_file_path, package_name='', dataclasses_json_annotation=False, avro_annotation=False):
|
|
778
853
|
"""Converts JSON Structure schema to Python dataclasses"""
|
|
779
854
|
if not package_name:
|
|
780
|
-
|
|
855
|
+
# Strip .json extension, then also strip .struct suffix if present (*.struct.json naming convention)
|
|
856
|
+
base_name = os.path.splitext(os.path.basename(structure_schema_path))[0]
|
|
857
|
+
if base_name.endswith('.struct'):
|
|
858
|
+
base_name = base_name[:-7] # Remove '.struct' suffix
|
|
859
|
+
package_name = base_name.lower().replace('-', '_')
|
|
860
|
+
package_name = safe_package_name(package_name)
|
|
781
861
|
|
|
782
862
|
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
783
863
|
structure_to_python.convert(structure_schema_path, py_file_path)
|
|
@@ -785,6 +865,7 @@ def convert_structure_to_python(structure_schema_path, py_file_path, package_nam
|
|
|
785
865
|
|
|
786
866
|
def convert_structure_schema_to_python(structure_schema, py_file_path, package_name='', dataclasses_json_annotation=False):
|
|
787
867
|
"""Converts JSON Structure schema to Python dataclasses"""
|
|
868
|
+
package_name = safe_package_name(package_name) if package_name else package_name
|
|
788
869
|
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation)
|
|
789
870
|
if isinstance(structure_schema, dict):
|
|
790
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)
|
|
@@ -400,8 +400,10 @@ class StructureToTypeScript:
|
|
|
400
400
|
write_file: bool = True) -> str:
|
|
401
401
|
""" Generates a TypeScript enum from JSON Structure enum """
|
|
402
402
|
enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
403
|
-
|
|
404
|
-
|
|
403
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
404
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
405
|
+
# Use schema_namespace (not parent_namespace) to match the file location
|
|
406
|
+
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(schema_namespace, enum_name)
|
|
405
407
|
|
|
406
408
|
if typescript_qualified_name in self.generated_types:
|
|
407
409
|
return typescript_qualified_name
|
|
@@ -477,6 +479,7 @@ class StructureToTypeScript:
|
|
|
477
479
|
""" Generates a TypeScript tuple type from JSON Structure tuple type """
|
|
478
480
|
tuple_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'Tuple'))
|
|
479
481
|
namespace = self.concat_namespace(self.base_package, structure_schema.get('namespace', parent_namespace)).lower()
|
|
482
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
480
483
|
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(parent_namespace, tuple_name)
|
|
481
484
|
|
|
482
485
|
if typescript_qualified_name in self.generated_types:
|
|
@@ -487,7 +490,7 @@ class StructureToTypeScript:
|
|
|
487
490
|
item_types = []
|
|
488
491
|
for idx, item in enumerate(tuple_items):
|
|
489
492
|
item_type = self.convert_structure_type_to_typescript(
|
|
490
|
-
tuple_name, f'item{idx}', item,
|
|
493
|
+
tuple_name, f'item{idx}', item, schema_namespace, import_types)
|
|
491
494
|
item_types.append(item_type)
|
|
492
495
|
|
|
493
496
|
# TypeScript tuples are just arrays with fixed length and types
|
|
@@ -735,4 +738,4 @@ def convert_structure_schema_to_typescript(structure_schema: JsonNode, output_di
|
|
|
735
738
|
avro_annotation: Whether to include Avro annotations
|
|
736
739
|
"""
|
|
737
740
|
converter = StructureToTypeScript(package_name, typedjson_annotation, avro_annotation)
|
|
738
|
-
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
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -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=RizwlnbP9gT9Cli8S3AsqYTckMr4Y8rczsBdG4bV938,714
|
|
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,10 +8,10 @@ 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
16
|
avrotize/avrotojsons.py,sha256=WXWniQqwcl8eU35VibDv7qJJwbiLV_yoWZ4JxiZ8mHA,21588
|
|
17
17
|
avrotize/avrotojstruct.py,sha256=-Hs4Ta958bRKmOfSTzRFENABCZ6lQPSPbIBEXvOQD1M,14660
|
|
@@ -20,8 +20,8 @@ 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,29 +44,29 @@ 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/dependencies/cpp/vcpkg/vcpkg.json,sha256=se5qnUVQ1Q6wN_DqgIioqKg_y7ouh9oly2iBAJJXkgw,414
|
|
66
66
|
avrotize/dependencies/typescript/node22/package.json,sha256=oAW_2X-b715kV7aajwuONZEMkyQUJGviG3GwnoUa7hU,387
|
|
67
|
-
structurize-2.
|
|
68
|
-
structurize-2.
|
|
69
|
-
structurize-2.
|
|
70
|
-
structurize-2.
|
|
71
|
-
structurize-2.
|
|
72
|
-
structurize-2.
|
|
67
|
+
structurize-2.22.0.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
|
|
68
|
+
structurize-2.22.0.dist-info/METADATA,sha256=nWSfeORdr2Q5aH2WjWQxw5-HcdXVAbGl7xmKrewxZNs,3670
|
|
69
|
+
structurize-2.22.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
70
|
+
structurize-2.22.0.dist-info/entry_points.txt,sha256=biIH7jA5auhVqfbwHVk2gmD_gvrPYKgjpCAn0JWZ-Rs,55
|
|
71
|
+
structurize-2.22.0.dist-info/top_level.txt,sha256=yn-yQ0Cm1O9fbF8KJgv4IIvX4YRGelKgPqZF1wS5P50,9
|
|
72
|
+
structurize-2.22.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|