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 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.21.1'
32
- __version_tuple__ = version_tuple = (2, 21, 1)
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
- reserved_words = [
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
- {%- set simple_type = base_type.split('.')[-1] if '.' in base_type else base_type %}
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
- {%- set simple_field_type = field.field_type.split('.')[-1] if '.' in field.field_type else field.field_type %}
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
- if '.' in type_to_check:
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
- class_import = f"import {java_qualified_name};"
1814
- if class_import not in imports:
1815
- imports.append(class_import)
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
- return self.parse_avro_schema(non_null_types[0])
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
- {%- endif %}
67
- {%- if avro_annotation %}
68
- let avro_bytes = instance.to_byte_array("avro/binary").unwrap();
69
- let deserialized_avro_instance: {{ enum_name }} = {{ enum_name }}::from_data(&avro_bytes, "avro/binary").unwrap();
70
- assert_eq!(instance, deserialized_avro_instance);
71
- {%- endif %}
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 uuid %}
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
- {%- endif %}
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
- {%- if avro_annotation %}
99
- let avro_bytes = instance.to_byte_array("avro/binary").unwrap();
100
- let deserialized_avro_instance: {{ union_enum_name }} = {{ union_enum_name }}::from_data(&avro_bytes, "avro/binary").unwrap();
101
- assert_eq!(instance, deserialized_avro_instance);
102
- {%- endif %}
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
- if field_type == 'String' or field_type == 'Option<String>':
191
- return f"{ref}.is_string()"
192
- elif field_type == 'bool' or field_type == 'Option<bool>':
193
- return f"{ref}.is_boolean()"
194
- elif field_type == 'i32' or field_type == 'Option<i32>':
195
- return f"{ref}.is_i64()"
196
- elif field_type == 'i64' or field_type == 'Option<i64>':
197
- return f"{ref}.is_i64()"
198
- elif field_type == 'f32' or field_type == 'Option<f32>':
199
- return f"{ref}.is_f64()"
200
- elif field_type == 'f64' or field_type == 'Option<f64>':
201
- return f"{ref}.is_f64()"
202
- elif field_type == 'Vec<u8>' or field_type == 'Option<Vec<u8>>':
203
- return f"{ref}.is_array()"
204
- elif field_type == 'serde_json::Value' or field_type == 'std::collections::HashMap<String, String>':
205
- return f"{ref}.is_object()"
206
- elif field_type.startswith('std::collections::HashMap<String, '):
207
- return f"{ref}.is_object()"
208
- elif field_type.startswith('Vec<'):
209
- return f"{ref}.is_array()"
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
- return f"{field_type}::is_json_match(&{ref})"
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
- union_fields = [
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': self.get_is_json_match_clause(field_name, t, for_union=True),
260
- } for i, t in enumerate(union_types)]
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]
@@ -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
- if field_name_cs == class_name:
428
- field_name_cs += "_"
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 field_name != field_name_cs:
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 field_name != field_name_cs:
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 field_name != field_name_cs:
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 field_name != field_name_cs:
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 safe_identifier(self, name: str) -> str:
35
- """Converts a name to a safe Go identifier"""
36
- reserved_words = [
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
- self.base_package = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_').lower()
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>
@@ -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
- if is_java_reserved_word(name):
77
- return f"_{name}"
78
- if class_name and name == class_name:
79
- return f"{name}_"
80
- return name
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
@@ -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
- if is_python_reserved_word(name):
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
- field_name = prop_name
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, namespace, import_types)
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, namespace, import_types)
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
  Metadata-Version: 2.4
2
2
  Name: avrotize
3
- Version: 2.21.1
3
+ Version: 2.22.1
4
4
  Summary: Tools to convert from and to Avro Schema from various other schema languages.
5
5
  Author-email: Clemens Vasters <clemensv@microsoft.com>
6
6
  Requires-Python: >=3.10
@@ -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=w0GJbqvrBbKA0xDzzXqfmy-zKEDei_ytc0ogfb890d8,706
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=RnycgAuGejq00hDdsUGdMHiJX6nr0VAqNArbCkTzUMg,21880
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=_G_67xi1H0Ctj9KagiCnVNETvyPicOYO8ASvz6e1XYE,131861
14
+ avrotize/avrotojava.py,sha256=NZZ7mUFVzvp7HBsU0XPiCwl4GVXE1RtS86pfNFJsIq8,135427
15
15
  avrotize/avrotojs.py,sha256=QjB6XjFnDrpZBZrrWqS0TN8fQfRXBfhHabfG73FOIo8,12249
16
- avrotize/avrotojsons.py,sha256=WXWniQqwcl8eU35VibDv7qJJwbiLV_yoWZ4JxiZ8mHA,21588
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=K1r6STp9OJJfQOwVMVpKUKRdYuo89JHmp_wLIO4d0WE,31331
24
- avrotize/avrotorust.py,sha256=QMIBNkFpDlH44kuQo24k5D-f0lmdhoA5b7hEbhKsnMw,22214
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=qYYAV6M8I2xv-VUzOobPT1_FYAoBBe0VRTMe9eY8r1g,122229
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=3QE_TCdNklGH5ymzGsEnX1sI4OhvX2AYKPH7xtR5tHk,43926
51
- avrotize/structuretogo.py,sha256=FNL5et5xxAoS0YSPJWGliWAFZPH_p8Nmu9pSEAT4J0U,32876
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=nlXRMfLcAEzGg3Q379UdQ6i9W7eqPjMFXFrN5d09r1E,46955
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=2dCa33nZBXgh996p3zA9uF3S1LHbOcpMSuoDXQPjAnw,39627
60
+ avrotize/structuretopython.py,sha256=d9EZVDHq7r-x0ZYZIRYfCP6kub7MkEROuvzjTJfNVv0,43958
61
61
  avrotize/structuretorust.py,sha256=ChRmO7uzU-pMdDdS0Vtg-MVUaOaNhNUPwH-ZKKOHglU,35134
62
- avrotize/structuretots.py,sha256=W552IJbTetZuTKtFE5A8B75neyRJXP7Q3ioWZCunLtE,35182
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=LvSZLumEBDZWHlXCbaJNlSb-ALILS2s9byIdGc7WHDM,9443
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=eoKjpaFCspI-uqT6TZwzOh7UopbUd4kbGlJLJCKo_64,10336
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=-1-73XnG1k1eONMq7_aENVpoHOsMnj55CyjU3PT8M7U,2232
94
- avrotize/avrotorust/dataclass_struct.rs.jinja,sha256=AXFqCCW8fVdJXnSYTfpwKQeGG9JH8Goc3g1ILKEU-mE,7853
95
- avrotize/avrotorust/dataclass_union.rs.jinja,sha256=HuDKlM03hOkVx9Lm5p93IzQfUCokHz8wOu9iyEkwkUk,3801
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=0Je9fnd0Gb1RSPoD_chu3fafIu8SnRPuCKCw0mPXOTc,1089
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=Lw29JKHOTrLJNe8tr3RO16skBZF8vtC5edpOYsFjsck,15758
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.21.1.dist-info/entry_points.txt,sha256=m8J2TWiqbZh7SBQezc1CNrM_GVPWf01zOFcAKhzCC0U,51
168
- avrotize-2.21.1.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
169
- avrotize-2.21.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
170
- avrotize-2.21.1.dist-info/METADATA,sha256=dtpNJWaoSiOH1ewSwtHKL4IHMhoQ5bWVHLSAmjiJj2U,80208
171
- avrotize-2.21.1.dist-info/RECORD,,
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,,