structurize 3.7.0__tar.gz → 3.7.1__tar.gz

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.
Files changed (121) hide show
  1. {structurize-3.7.0/structurize.egg-info → structurize-3.7.1}/PKG-INFO +1 -1
  2. {structurize-3.7.0 → structurize-3.7.1}/avrotize/_version.py +3 -3
  3. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotocapnproto.py +57 -33
  4. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotocsharp.py +110 -76
  5. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoflatbuffers.py +33 -9
  6. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotojtd.py +5 -2
  7. {structurize-3.7.0 → structurize-3.7.1}/avrotize/capnprototoavro.py +51 -22
  8. {structurize-3.7.0 → structurize-3.7.1}/avrotize/common.py +18 -0
  9. {structurize-3.7.0 → structurize-3.7.1}/avrotize/cuetoavro.py +49 -13
  10. {structurize-3.7.0 → structurize-3.7.1}/avrotize/dependency_resolver.py +60 -1
  11. {structurize-3.7.0 → structurize-3.7.1}/avrotize/flatbufferstoavro.py +97 -16
  12. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jtdtoavro.py +19 -8
  13. {structurize-3.7.0 → structurize-3.7.1}/avrotize/ramltoavro.py +21 -10
  14. {structurize-3.7.0 → structurize-3.7.1}/avrotize/smithytoavro.py +10 -7
  15. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretocsharp.py +44 -40
  16. {structurize-3.7.0 → structurize-3.7.1}/avrotize/surrealtoavro.py +56 -28
  17. {structurize-3.7.0 → structurize-3.7.1}/avrotize/thrifttoavro.py +16 -1
  18. {structurize-3.7.0 → structurize-3.7.1/structurize.egg-info}/PKG-INFO +1 -1
  19. {structurize-3.7.0 → structurize-3.7.1}/.gitignore +0 -0
  20. {structurize-3.7.0 → structurize-3.7.1}/LICENSE +0 -0
  21. {structurize-3.7.0 → structurize-3.7.1}/MANIFEST.in +0 -0
  22. {structurize-3.7.0 → structurize-3.7.1}/README.md +0 -0
  23. {structurize-3.7.0 → structurize-3.7.1}/avrotize/__init__.py +0 -0
  24. {structurize-3.7.0 → structurize-3.7.1}/avrotize/__main__.py +0 -0
  25. {structurize-3.7.0 → structurize-3.7.1}/avrotize/asn1toavro.py +0 -0
  26. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotize.py +0 -0
  27. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotocpp.py +0 -0
  28. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotocsv.py +0 -0
  29. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotocue.py +0 -0
  30. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotodatapackage.py +0 -0
  31. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotodb.py +0 -0
  32. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotogo.py +0 -0
  33. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotographql.py +0 -0
  34. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoiceberg.py +0 -0
  35. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotojava.py +0 -0
  36. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotojs.py +0 -0
  37. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotojsons.py +0 -0
  38. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotojstruct.py +0 -0
  39. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotokusto.py +0 -0
  40. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotomd.py +0 -0
  41. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotools.py +0 -0
  42. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoparquet.py +0 -0
  43. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoproto.py +0 -0
  44. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotopython.py +0 -0
  45. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoraml.py +0 -0
  46. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotorust.py +0 -0
  47. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotosmithy.py +0 -0
  48. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotosurreal.py +0 -0
  49. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotothrift.py +0 -0
  50. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotots.py +0 -0
  51. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrototsml.py +0 -0
  52. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrotoxsd.py +0 -0
  53. {structurize-3.7.0 → structurize-3.7.1}/avrotize/avrovalidator.py +0 -0
  54. {structurize-3.7.0 → structurize-3.7.1}/avrotize/cddltostructure.py +0 -0
  55. {structurize-3.7.0 → structurize-3.7.1}/avrotize/choice_inference.py +0 -0
  56. {structurize-3.7.0 → structurize-3.7.1}/avrotize/commands.json +0 -0
  57. {structurize-3.7.0 → structurize-3.7.1}/avrotize/constants.py +0 -0
  58. {structurize-3.7.0 → structurize-3.7.1}/avrotize/csvtoavro.py +0 -0
  59. {structurize-3.7.0 → structurize-3.7.1}/avrotize/cuetostructure.py +0 -0
  60. {structurize-3.7.0 → structurize-3.7.1}/avrotize/datapackagetoavro.py +0 -0
  61. {structurize-3.7.0 → structurize-3.7.1}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
  62. {structurize-3.7.0 → structurize-3.7.1}/avrotize/dependencies/typescript/node22/package.json +0 -0
  63. {structurize-3.7.0 → structurize-3.7.1}/avrotize/dependency_version.py +0 -0
  64. {structurize-3.7.0 → structurize-3.7.1}/avrotize/flatbufferstojstruct.py +0 -0
  65. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jsonstoavro.py +0 -0
  66. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jsonstostructure.py +0 -0
  67. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jsontoschema.py +0 -0
  68. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jstructtoavro.py +0 -0
  69. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jstructtoflatbuffers.py +0 -0
  70. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jstructtoraml.py +0 -0
  71. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jstructtosmithy.py +0 -0
  72. {structurize-3.7.0 → structurize-3.7.1}/avrotize/jtdtostructure.py +0 -0
  73. {structurize-3.7.0 → structurize-3.7.1}/avrotize/kstructtoavro.py +0 -0
  74. {structurize-3.7.0 → structurize-3.7.1}/avrotize/kustotoavro.py +0 -0
  75. {structurize-3.7.0 → structurize-3.7.1}/avrotize/kustotojstruct.py +0 -0
  76. {structurize-3.7.0 → structurize-3.7.1}/avrotize/mcp_server.py +0 -0
  77. {structurize-3.7.0 → structurize-3.7.1}/avrotize/openapitostructure.py +0 -0
  78. {structurize-3.7.0 → structurize-3.7.1}/avrotize/parquettoavro.py +0 -0
  79. {structurize-3.7.0 → structurize-3.7.1}/avrotize/proto2parser.py +0 -0
  80. {structurize-3.7.0 → structurize-3.7.1}/avrotize/proto3parser.py +0 -0
  81. {structurize-3.7.0 → structurize-3.7.1}/avrotize/prototoavro.py +0 -0
  82. {structurize-3.7.0 → structurize-3.7.1}/avrotize/ramltojstruct.py +0 -0
  83. {structurize-3.7.0 → structurize-3.7.1}/avrotize/schema_inference.py +0 -0
  84. {structurize-3.7.0 → structurize-3.7.1}/avrotize/smithytojstruct.py +0 -0
  85. {structurize-3.7.0 → structurize-3.7.1}/avrotize/sqltoavro.py +0 -0
  86. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretocddl.py +0 -0
  87. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretocpp.py +0 -0
  88. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretocsv.py +0 -0
  89. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretocue.py +0 -0
  90. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretodatapackage.py +0 -0
  91. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretodb.py +0 -0
  92. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretogo.py +0 -0
  93. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretographql.py +0 -0
  94. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretoiceberg.py +0 -0
  95. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretojava.py +0 -0
  96. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretojs.py +0 -0
  97. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretojsons.py +0 -0
  98. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretojtd.py +0 -0
  99. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretokusto.py +0 -0
  100. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretomd.py +0 -0
  101. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretoparquet.py +0 -0
  102. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretoproto.py +0 -0
  103. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretopython.py +0 -0
  104. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretorust.py +0 -0
  105. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretots.py +0 -0
  106. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretotsml.py +0 -0
  107. {structurize-3.7.0 → structurize-3.7.1}/avrotize/structuretoxsd.py +0 -0
  108. {structurize-3.7.0 → structurize-3.7.1}/avrotize/thriftstructure.py +0 -0
  109. {structurize-3.7.0 → structurize-3.7.1}/avrotize/tmslvalidate.py +0 -0
  110. {structurize-3.7.0 → structurize-3.7.1}/avrotize/validate.py +0 -0
  111. {structurize-3.7.0 → structurize-3.7.1}/avrotize/xmltoschema.py +0 -0
  112. {structurize-3.7.0 → structurize-3.7.1}/avrotize/xsdtoavro.py +0 -0
  113. {structurize-3.7.0 → structurize-3.7.1}/build.ps1 +0 -0
  114. {structurize-3.7.0 → structurize-3.7.1}/build.sh +0 -0
  115. {structurize-3.7.0 → structurize-3.7.1}/pyproject.toml +0 -0
  116. {structurize-3.7.0 → structurize-3.7.1}/setup.cfg +0 -0
  117. {structurize-3.7.0 → structurize-3.7.1}/structurize.egg-info/SOURCES.txt +0 -0
  118. {structurize-3.7.0 → structurize-3.7.1}/structurize.egg-info/dependency_links.txt +0 -0
  119. {structurize-3.7.0 → structurize-3.7.1}/structurize.egg-info/entry_points.txt +0 -0
  120. {structurize-3.7.0 → structurize-3.7.1}/structurize.egg-info/requires.txt +0 -0
  121. {structurize-3.7.0 → structurize-3.7.1}/structurize.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 3.7.0
3
+ Version: 3.7.1
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
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.7.0'
22
- __version_tuple__ = version_tuple = (3, 7, 0)
21
+ __version__ = version = '3.7.1'
22
+ __version_tuple__ = version_tuple = (3, 7, 1)
23
23
 
24
- __commit_id__ = commit_id = 'gd5dcf3fee'
24
+ __commit_id__ = commit_id = 'g2f9460efb'
@@ -8,6 +8,8 @@ import re
8
8
  import tempfile
9
9
  from typing import Any
10
10
 
11
+ from avrotize.common import unique_name
12
+
11
13
  INDENT = " "
12
14
  FILE_ID = "0xd12f8a7e9b0c4a6f"
13
15
 
@@ -41,6 +43,8 @@ class AvroToCapnpConverter:
41
43
  self.namespace = namespace
42
44
  self.named: dict[str, dict[str, Any]] = {}
43
45
  self.emitted: set[str] = set()
46
+ self.type_names: dict[int, str] = {}
47
+ self.top_level_type_names: set[str] = set()
44
48
 
45
49
  def convert(self, avro_schema: Any) -> str:
46
50
  schemas = avro_schema if isinstance(avro_schema, list) else [avro_schema]
@@ -48,6 +52,7 @@ class AvroToCapnpConverter:
48
52
  if isinstance(schema, dict) and schema.get("type") in {"record", "enum"}:
49
53
  self.named[schema["name"]] = schema
50
54
  self.named[_type_name(schema)] = schema
55
+ self._assign_type_name(schema, self.top_level_type_names)
51
56
  lines = [f"@{FILE_ID};", ""]
52
57
  if self.namespace:
53
58
  lines.append(f"# namespace: {self.namespace}")
@@ -57,83 +62,102 @@ class AvroToCapnpConverter:
57
62
  if isinstance(schema, dict) and schema.get("type") in {"enum", "record"}:
58
63
  name = _type_name(schema)
59
64
  if name not in self.emitted:
60
- lines.extend(self._render_named(schema, 0))
65
+ lines.extend(self._render_named(schema, 0, self.top_level_type_names))
61
66
  lines.append("")
62
67
  return "\n".join(lines).rstrip() + "\n"
63
68
 
64
- def _render_named(self, schema: dict[str, Any], level: int) -> list[str]:
69
+ def _assign_type_name(self, schema: dict[str, Any], used: set[str] | None = None) -> str:
70
+ key = id(schema)
71
+ if key not in self.type_names:
72
+ self.type_names[key] = unique_name(_safe_ident(schema["name"]), used if used is not None else self.top_level_type_names)
73
+ return self.type_names[key]
74
+
75
+ def _render_named(self, schema: dict[str, Any], level: int, used_type_names: set[str] | None = None) -> list[str]:
65
76
  if schema.get("type") == "enum":
66
- return self._render_enum(schema, level)
77
+ return self._render_enum(schema, level, used_type_names)
67
78
  if schema.get("type") == "record":
68
- return self._render_struct(schema, level)
79
+ return self._render_struct(schema, level, used_type_names)
69
80
  return []
70
81
 
71
- def _render_enum(self, schema: dict[str, Any], level: int) -> list[str]:
82
+ def _render_enum(self, schema: dict[str, Any], level: int, used_type_names: set[str] | None = None) -> list[str]:
72
83
  self.emitted.add(_type_name(schema))
73
- lines = [f"{INDENT * level}enum {_safe_ident(schema['name'])} {{"]
84
+ lines = [f"{INDENT * level}enum {self._assign_type_name(schema, used_type_names)} {{"]
85
+ used_symbols: set[str] = set()
74
86
  for index, symbol in enumerate(schema.get("symbols", [])):
75
- lines.append(f"{INDENT * (level + 1)}{_safe_ident(symbol)} @{index};")
87
+ lines.append(f"{INDENT * (level + 1)}{unique_name(_safe_ident(symbol), used_symbols)} @{index};")
76
88
  lines.append(f"{INDENT * level}}}")
77
89
  return lines
78
90
 
79
- def _render_struct(self, schema: dict[str, Any], level: int) -> list[str]:
91
+ def _render_struct(self, schema: dict[str, Any], level: int, used_type_names: set[str] | None = None) -> list[str]:
80
92
  self.emitted.add(_type_name(schema))
81
- lines = [f"{INDENT * level}struct {_safe_ident(schema['name'])} {{"]
93
+ lines = [f"{INDENT * level}struct {self._assign_type_name(schema, used_type_names)} {{"]
82
94
  fields = schema.get("fields", [])
83
- grouped: dict[str, list[tuple[int, dict[str, Any], Any]]] = {}
84
- normal: list[tuple[int, dict[str, Any], Any]] = []
95
+ grouped: dict[str, list[tuple[int, dict[str, Any], Any, str]]] = {}
96
+ normal: list[tuple[int, dict[str, Any], Any, str]] = []
97
+ used_fields: set[str] = set()
85
98
  for index, field in enumerate(fields):
86
99
  avro_type = field.get("type")
87
100
  union_name = field.get("capnpUnion")
88
- item = (index, field, avro_type)
101
+ safe_field_name = unique_name(_safe_ident(field.get("name", f"field{index}")), used_fields)
102
+ item = (index, field, avro_type, safe_field_name)
89
103
  if union_name:
90
104
  grouped.setdefault(str(union_name), []).append(item)
91
105
  else:
92
106
  normal.append(item)
93
- for index, field, avro_type in normal:
94
- lines.extend(self._render_field(field, avro_type, index, level + 1))
107
+ nested_type_names: set[str] = set()
108
+ for index, field, avro_type, safe_field_name in normal:
109
+ lines.extend(self._render_field(field, avro_type, index, level + 1, safe_field_name, nested_type_names))
95
110
  for _, items in grouped.items():
96
111
  lines.append(f"{INDENT * (level + 1)}union {{")
97
- for index, field, avro_type in items:
98
- lines.extend(self._render_field(field, avro_type, index, level + 2))
112
+ for index, field, avro_type, safe_field_name in items:
113
+ lines.extend(self._render_field(field, avro_type, index, level + 2, safe_field_name, nested_type_names))
99
114
  lines.append(f"{INDENT * (level + 1)}}}")
100
115
  lines.append(f"{INDENT * level}}}")
101
116
  return lines
102
117
 
103
- def _render_field(self, field: dict[str, Any], avro_type: Any, index: int, level: int) -> list[str]:
104
- capnp_type, nested = self._capnp_type(avro_type, _safe_ident(field.get("name", f"field{index}")))
118
+ def _render_field(
119
+ self,
120
+ field: dict[str, Any],
121
+ avro_type: Any,
122
+ index: int,
123
+ level: int,
124
+ safe_field_name: str,
125
+ used_type_names: set[str],
126
+ ) -> list[str]:
127
+ capnp_type, nested = self._capnp_type(avro_type, safe_field_name, used_type_names)
105
128
  ordinal = index
106
129
  lines: list[str] = []
107
- lines.extend(self._render_inline_nested(nested, level))
108
- lines.append(f"{INDENT * level}{_safe_ident(field.get('name', f'field{index}'))} @{ordinal} :{capnp_type};")
130
+ lines.extend(self._render_inline_nested(nested, level, used_type_names))
131
+ lines.append(f"{INDENT * level}{safe_field_name} @{ordinal} :{capnp_type};")
109
132
  return lines
110
133
 
111
- def _render_inline_nested(self, nested: list[dict[str, Any]], level: int) -> list[str]:
134
+ def _render_inline_nested(self, nested: list[dict[str, Any]], level: int, used_type_names: set[str]) -> list[str]:
112
135
  lines: list[str] = []
113
136
  for schema in nested:
114
- lines.extend(self._render_named(schema, level))
137
+ lines.extend(self._render_named(schema, level, used_type_names))
115
138
  return lines
116
139
 
117
140
  def _non_null_union(self, avro_type: list[Any]) -> list[Any]:
118
141
  return [item for item in avro_type if item != "null" and not (isinstance(item, dict) and item.get("type") == "null")]
119
142
 
120
- def _capnp_type(self, avro_type: Any, field_name: str) -> tuple[str, list[dict[str, Any]]]:
143
+ def _capnp_type(self, avro_type: Any, field_name: str, used_type_names: set[str] | None = None) -> tuple[str, list[dict[str, Any]]]:
144
+ used_type_names = used_type_names if used_type_names is not None else self.top_level_type_names
121
145
  if isinstance(avro_type, list):
122
146
  non_null = self._non_null_union(avro_type)
123
147
  if len(non_null) == 1:
124
- return self._capnp_type(non_null[0], field_name)
148
+ return self._capnp_type(non_null[0], field_name, used_type_names)
125
149
  union_schema = {
126
150
  "type": "record",
127
151
  "name": f"{field_name[:1].upper()}{field_name[1:]}Choice",
128
152
  "fields": [{"name": f"choice{i}", "type": t, "capnpUnion": "union"} for i, t in enumerate(non_null)],
129
153
  }
130
- return union_schema["name"], [union_schema]
154
+ return self._assign_type_name(union_schema, used_type_names), [union_schema]
131
155
  if isinstance(avro_type, dict):
132
156
  avro_kind = avro_type.get("type")
133
157
  if isinstance(avro_kind, list):
134
- return self._capnp_type(avro_kind, field_name)
158
+ return self._capnp_type(avro_kind, field_name, used_type_names)
135
159
  if avro_kind == "array":
136
- item_type, nested = self._capnp_type(avro_type.get("items", "string"), f"{field_name}Item")
160
+ item_type, nested = self._capnp_type(avro_type.get("items", "string"), f"{field_name}Item", used_type_names)
137
161
  return f"List({item_type})", nested
138
162
  if avro_kind == "map":
139
163
  entry = {
@@ -144,19 +168,19 @@ class AvroToCapnpConverter:
144
168
  {"name": "value", "type": avro_type.get("values", "string")},
145
169
  ],
146
170
  }
147
- return f"List({entry['name']})", [entry]
171
+ return f"List({self._assign_type_name(entry, used_type_names)})", [entry]
148
172
  if avro_kind == "record":
149
- return avro_type["name"], [avro_type]
173
+ return self._assign_type_name(avro_type, used_type_names), [avro_type]
150
174
  if avro_kind == "enum":
151
- return avro_type["name"], [avro_type]
175
+ return self._assign_type_name(avro_type, used_type_names), [avro_type]
152
176
  if avro_kind == "fixed":
153
177
  return "Data", []
154
- return self._capnp_type(avro_kind, field_name)
178
+ return self._capnp_type(avro_kind, field_name, used_type_names)
155
179
  if isinstance(avro_type, str):
156
180
  named = self.named.get(avro_type)
157
181
  if named:
158
- return named["name"], []
159
- return self.primitive_map.get(avro_type, avro_type.split(".")[-1]), []
182
+ return self._assign_type_name(named), []
183
+ return self.primitive_map.get(avro_type, _safe_ident(avro_type.split(".")[-1])), []
160
184
  return "Text", []
161
185
 
162
186
 
@@ -8,7 +8,7 @@ import re
8
8
  from typing import Any, Dict, List, Tuple, Union, cast
9
9
  import uuid
10
10
 
11
- from avrotize.common import build_flat_type_dict, inline_avro_references, is_generic_avro_type, pascal, process_template
11
+ from avrotize.common import build_flat_type_dict, inline_avro_references, is_generic_avro_type, pascal, process_template, unique_name
12
12
  from avrotize.constants import (
13
13
  CSHARP_AVRO_VERSION,
14
14
  NEWTONSOFT_JSON_VERSION,
@@ -76,7 +76,7 @@ class AvroToCSharp:
76
76
  else:
77
77
  return name
78
78
 
79
- def map_primitive_to_csharp(self, avro_type: str) -> str:
79
+ def map_primitive_to_csharp(self, avro_type: str, parent_namespace: str = '') -> str:
80
80
  """ Maps Avro primitive types to C# types """
81
81
  mapping = {
82
82
  'null': 'void', # Placeholder, actual handling for nullable types is in the union logic
@@ -92,9 +92,43 @@ class AvroToCSharp:
92
92
  if qualified_class_name in self.generated_avro_types:
93
93
  result = qualified_class_name
94
94
  else:
95
- result = mapping.get(avro_type, 'object')
95
+ result = mapping.get(avro_type, '')
96
+ if not result:
97
+ avro_type_name = avro_type if '.' in avro_type or not parent_namespace else f"{parent_namespace}.{avro_type}"
98
+ if avro_type_name in self.type_dict:
99
+ namespace = ".".join(avro_type_name.split('.')[:-1])
100
+ name = avro_type_name.split('.')[-1]
101
+ result = 'global::'+self.get_qualified_name(pascal(self.concat_namespace(self.base_namespace, namespace)), pascal(name))
102
+ else:
103
+ result = 'object'
96
104
  return result
97
105
 
106
+ def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field', allow_unicode: bool = False) -> str:
107
+ """Converts a source name to a safe C# identifier."""
108
+ safe = re.sub(r'[^\w]', '_', str(name), flags=re.UNICODE) if allow_unicode else re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
109
+ safe = safe.strip('_') if safe != name else safe
110
+ starts_ok = safe and (safe[0] == '_' or safe[0] == '@' or (safe[0].isalpha() if allow_unicode else re.match(r'^[a-zA-Z_@]', safe)))
111
+ if not starts_ok:
112
+ safe = '_' + safe if safe and safe[0].isdigit() else f"{fallback_prefix}_unnamed"
113
+ if self.is_csharp_reserved_word(safe):
114
+ safe = f"@{safe}"
115
+ if class_name and safe == class_name:
116
+ safe += "_"
117
+ return safe
118
+
119
+ def safe_enum_member_name(self, symbol: str, used: set) -> str:
120
+ """Converts an Avro enum symbol to a unique C# enum member name."""
121
+ parts = [part for part in re.split(r'[^\w]+', str(symbol), flags=re.UNICODE) if part]
122
+ if not parts:
123
+ candidate = "Value"
124
+ else:
125
+ candidate = ''.join(part if part.isupper() else part[:1].upper() + part[1:] for part in parts)
126
+ if not candidate or not (candidate[0] == '_' or candidate[0].isalpha()):
127
+ candidate = f"Value{candidate}"
128
+ if self.is_csharp_reserved_word(candidate):
129
+ candidate = f"@{candidate}"
130
+ return unique_name(candidate, used)
131
+
98
132
  def is_csharp_reserved_word(self, word: str) -> bool:
99
133
  """ Checks if a word is a reserved C# keyword """
100
134
  reserved_words = [
@@ -133,7 +167,7 @@ class AvroToCSharp:
133
167
  def convert_avro_type_to_csharp(self, class_name: str, field_name: str, avro_type: JsonNode, parent_namespace: str) -> str:
134
168
  """ Converts Avro type to C# type """
135
169
  if isinstance(avro_type, str):
136
- return self.map_primitive_to_csharp(avro_type)
170
+ return self.map_primitive_to_csharp(avro_type, parent_namespace)
137
171
  elif isinstance(avro_type, list):
138
172
  # Handle nullable types and unions
139
173
  if is_generic_avro_type(avro_type):
@@ -194,7 +228,8 @@ class AvroToCSharp:
194
228
  class_definition += "[MessagePackObject]\n"
195
229
 
196
230
  fields = avro_schema.get('fields', [])
197
- fields_str = [self.generate_property(index + 1, field, class_name, avro_namespace) for index, field in enumerate(fields)]
231
+ property_names = self.get_csharp_property_names(fields, class_name)
232
+ fields_str = [self.generate_property(index + 1, field, class_name, avro_namespace, property_names[index]) for index, field in enumerate(fields)]
198
233
  class_body = "\n".join(fields_str)
199
234
  class_definition += f"public partial class {class_name}"
200
235
  if self.avro_annotation:
@@ -229,14 +264,8 @@ class AvroToCSharp:
229
264
  put_method = f"{INDENT}void global::Avro.Specific.ISpecificRecord.Put(int fieldPos, object fieldValue)\n" + \
230
265
  INDENT+"{"+f"\n{INDENT*2}switch (fieldPos)\n{INDENT*2}"+"{"
231
266
  for pos, field in enumerate(avro_schema.get('fields', [])):
232
- field_name = field['name']
233
- if self.is_csharp_reserved_word(field_name):
234
- field_name = f"@{field_name}"
235
- field_type = self.convert_avro_type_to_csharp(class_name, field_name, field['type'], avro_namespace)
236
- if self.pascal_properties:
237
- field_name = pascal(field_name)
238
- if field_name == class_name:
239
- field_name += "_"
267
+ field_name = property_names[pos]
268
+ field_type = self.convert_avro_type_to_csharp(class_name, field['name'], field['type'], avro_namespace)
240
269
  if field_type in self.generated_types:
241
270
  if self.generated_types[field_type] == "union":
242
271
  get_method += f"\n{INDENT*3}case {pos}: return this.{field_name}?.ToObject();"
@@ -279,7 +308,7 @@ class AvroToCSharp:
279
308
  cbor_annotation=self.cbor_annotation,
280
309
  json_match_clauses=self.create_is_json_match_clauses(avro_schema, avro_namespace, class_name),
281
310
  openapi_generator_compat=self.openapi_generator_compat,
282
- openapi_fields=self.get_openapi_compat_fields(fields, class_name, avro_namespace)
311
+ openapi_fields=self.get_openapi_compat_fields(fields, class_name, avro_namespace, property_names)
283
312
  )
284
313
 
285
314
  # emit Equals and GetHashCode for value equality
@@ -294,26 +323,45 @@ class AvroToCSharp:
294
323
  self.generated_avro_types[ref] = avro_schema
295
324
  return ref
296
325
 
326
+ def get_csharp_property_names(self, fields: List[Dict], class_name: str) -> List[str]:
327
+ """Returns unique C# property names for the fields in a class."""
328
+ used: set[str] = set()
329
+ property_names: List[str] = []
330
+ for field in fields:
331
+ raw_name = str(field['name'])
332
+ if self.openapi_generator_compat:
333
+ safe_name = self.safe_identifier(raw_name, class_name)
334
+ candidate = self.get_openapi_property_name(safe_name, class_name)
335
+ property_names.append(re.sub(r'_(\d+)$', r'\1', unique_name(candidate, used)))
336
+ else:
337
+ field_name = self.safe_identifier(raw_name, class_name, allow_unicode=True)
338
+ if self.pascal_properties:
339
+ field_name = pascal(field_name.lstrip('@'))
340
+ if self.is_csharp_reserved_word(field_name):
341
+ field_name = f"@{field_name}"
342
+ if field_name == class_name:
343
+ field_name += "_"
344
+ property_names.append(unique_name(field_name, used))
345
+ return property_names
346
+
297
347
  def create_is_json_match_clauses(self, avro_schema, parent_namespace, class_name) -> List[str]:
298
348
  """ Generates the IsJsonMatch method for System.Text.Json """
299
349
  clauses: List[str] = []
300
- for field in avro_schema.get('fields', []):
301
- field_name = field['name']
302
- if self.is_csharp_reserved_word(field_name):
303
- field_name = f"@{field_name}"
304
- if field_name == class_name:
305
- field_name += "_"
350
+ fields = avro_schema.get('fields', [])
351
+ used_names: set[str] = set()
352
+ for field in fields:
353
+ field_name = unique_name(self.safe_identifier(field['name'], class_name, allow_unicode=True), used_names)
306
354
  field_type = self.convert_avro_type_to_csharp(
307
- class_name, field_name, field['type'], parent_namespace)
308
- clauses.append(self.get_is_json_match_clause(class_name, field_name, field_type))
355
+ class_name, field['name'], field['type'], parent_namespace)
356
+ clauses.append(self.get_is_json_match_clause(class_name, field_name, field_type, str(field['name'])))
309
357
  if len(clauses) == 0:
310
358
  clauses.append("true")
311
359
  return clauses
312
360
 
313
- def get_is_json_match_clause(self, class_name, field_name, field_type) -> str:
361
+ def get_is_json_match_clause(self, class_name, field_name, field_type, wire_name: str | None = None) -> str:
314
362
  """ Generates the IsJsonMatch clause for a field """
315
363
  class_definition = ''
316
- field_name_js = field_name[1:] if field_name[0] == '@' else field_name
364
+ field_name_js = wire_name if wire_name is not None else (field_name[1:] if field_name[0] == '@' else field_name)
317
365
  is_optional = field_type[-1] == '?'
318
366
  field_type = field_type[:-1] if is_optional else field_type
319
367
  if field_type == 'byte[]':
@@ -399,18 +447,11 @@ class AvroToCSharp:
399
447
 
400
448
  # Build equality comparisons for each field
401
449
  equality_checks = []
402
- for field in fields:
403
- field_name = field['name']
404
- if self.is_csharp_reserved_word(field_name):
405
- field_name = f"@{field_name}"
406
- if self.openapi_generator_compat:
407
- field_name = self.get_openapi_property_name(field_name, class_name)
408
- elif self.pascal_properties:
409
- field_name = pascal(field_name)
410
- if field_name == class_name:
411
- field_name += "_"
450
+ property_names = self.get_csharp_property_names(fields, class_name)
451
+ for index, field in enumerate(fields):
452
+ field_name = property_names[index]
412
453
 
413
- field_type = self.convert_avro_type_to_csharp(class_name, field_name, field['type'], parent_namespace)
454
+ field_type = self.convert_avro_type_to_csharp(class_name, field['name'], field['type'], parent_namespace)
414
455
 
415
456
  # Handle different types of comparisons
416
457
  if field_type == 'byte[]' or field_type == 'byte[]?':
@@ -443,18 +484,10 @@ class AvroToCSharp:
443
484
 
444
485
  # Collect field names for HashCode.Combine
445
486
  hash_fields = []
446
- for field in fields:
447
- field_name = field['name']
448
- if self.is_csharp_reserved_word(field_name):
449
- field_name = f"@{field_name}"
450
- if self.openapi_generator_compat:
451
- field_name = self.get_openapi_property_name(field_name, class_name)
452
- elif self.pascal_properties:
453
- field_name = pascal(field_name)
454
- if field_name == class_name:
455
- field_name += "_"
456
-
457
- field_type = self.convert_avro_type_to_csharp(class_name, field_name, field['type'], parent_namespace)
487
+ for index, field in enumerate(fields):
488
+ field_name = property_names[index]
489
+
490
+ field_type = self.convert_avro_type_to_csharp(class_name, field['name'], field['type'], parent_namespace)
458
491
 
459
492
  # Handle special types that need custom hash code computation
460
493
  if field_type == 'byte[]' or field_type == 'byte[]?':
@@ -502,10 +535,14 @@ class AvroToCSharp:
502
535
  else:
503
536
  enum_definition += f"[XmlType(\"{enum_name}\")]\n"
504
537
 
505
- if self.system_xml_annotation:
506
- symbols_str = [f"{INDENT}/// <summary>\n{INDENT}/// {symbol}\n{INDENT}/// </summary>\n{INDENT}[XmlEnum(Name=\"{symbol}\")]\n{INDENT}{symbol}" for symbol in avro_schema['symbols']]
507
- else:
508
- symbols_str = [f"{INDENT}/// <summary>\n{INDENT}/// {symbol}\n{INDENT}/// </summary>\n{INDENT}{symbol}" for symbol in avro_schema['symbols']]
538
+ used_symbols: set[str] = set()
539
+ symbols_str = []
540
+ for symbol in avro_schema['symbols']:
541
+ member_name = self.safe_enum_member_name(str(symbol), used_symbols)
542
+ if self.system_xml_annotation:
543
+ symbols_str.append(f"{INDENT}/// <summary>\n{INDENT}/// {member_name}\n{INDENT}/// </summary>\n{INDENT}[XmlEnum(Name=\"{symbol}\")]\n{INDENT}{member_name}")
544
+ else:
545
+ symbols_str.append(f"{INDENT}/// <summary>\n{INDENT}/// {member_name}\n{INDENT}/// </summary>\n{INDENT}{member_name}")
509
546
  enum_body = ",\n".join(symbols_str)
510
547
  enum_definition += f"public enum {enum_name}\n{{\n{enum_body}\n}}"
511
548
 
@@ -753,19 +790,14 @@ class AvroToCSharp:
753
790
  return avro_type.get('type') == 'enum'
754
791
  return False
755
792
 
756
- def generate_property(self, field_index: int, field: Dict, class_name: str, parent_namespace: str) -> str:
793
+ def generate_property(self, field_index: int, field: Dict, class_name: str, parent_namespace: str, property_name: str | None = None) -> str:
757
794
  """ Generates a property """
758
795
  is_enum_type = self.is_enum_type(field['type'])
759
796
  field_type = self.convert_avro_type_to_csharp(
760
797
  class_name, field['name'], field['type'], parent_namespace)
761
798
  field_default = field.get('const', field.get('default', None))
762
- annotation_name = field_name = field['name']
763
- if self.is_csharp_reserved_word(field_name):
764
- field_name = f"@{field_name}"
765
- if self.pascal_properties:
766
- field_name = pascal(field_name)
767
- if field_name == class_name:
768
- field_name += "_"
799
+ annotation_name = field['name']
800
+ field_name = property_name if property_name is not None else self.get_csharp_property_names([field], class_name)[0]
769
801
  if self.openapi_generator_compat:
770
802
  return self.generate_openapi_compat_property(field, field_type, field_name, annotation_name, class_name)
771
803
  prop = ''
@@ -809,7 +841,8 @@ class AvroToCSharp:
809
841
  # For enum types, use qualified enum value (e.g., Type.Circle)
810
842
  # Get the base enum type name (strip nullable ? suffix if present)
811
843
  enum_type = field_type.rstrip('?')
812
- initialization = f" = {enum_type}.{field_default};"
844
+ enum_default = self.safe_enum_member_name(str(field_default), set())
845
+ initialization = f" = {enum_type}.{enum_default};"
813
846
  elif isinstance(field_default, str):
814
847
  initialization = f" = \"{field_default}\";"
815
848
  else:
@@ -845,7 +878,7 @@ class AvroToCSharp:
845
878
 
846
879
  def generate_openapi_compat_property(self, field: Dict, field_type: str, field_name: str, annotation_name: str, class_name: str) -> str:
847
880
  """Generates an OpenAPI Generator-compatible property."""
848
- property_name = self.get_openapi_property_name(field_name, class_name)
881
+ property_name = field_name
849
882
  doc = field.get('doc', property_name)
850
883
  prop = f"{INDENT}/// <summary>\n{INDENT}/// {doc}\n{INDENT}/// </summary>\n"
851
884
  if self.is_openapi_optional_avro_field(field):
@@ -866,14 +899,15 @@ class AvroToCSharp:
866
899
  prop += f"{INDENT}public {field_type} {property_name} {{ get; set; }}{initialization}"
867
900
  return prop
868
901
 
869
- def get_openapi_compat_fields(self, fields: List[Dict], class_name: str, parent_namespace: str) -> List[Dict[str, Any]]:
902
+ def get_openapi_compat_fields(self, fields: List[Dict], class_name: str, parent_namespace: str, property_names: List[str] | None = None) -> List[Dict[str, Any]]:
870
903
  """Builds template metadata for OpenAPI Generator-compatible converters."""
904
+ if property_names is None:
905
+ property_names = self.get_csharp_property_names(fields, class_name)
871
906
  result: List[Dict[str, Any]] = []
872
- for field in fields:
907
+ for index, field in enumerate(fields):
873
908
  raw_name = field['name']
874
- safe_name = f"@{raw_name}" if self.is_csharp_reserved_word(raw_name) else raw_name
875
909
  field_type = self.convert_avro_type_to_csharp(class_name, raw_name, field['type'], parent_namespace)
876
- property_name = self.get_openapi_property_name(safe_name, class_name)
910
+ property_name = property_names[index]
877
911
  result.append({
878
912
  "wire_name": raw_name,
879
913
  "property_name": property_name,
@@ -1040,17 +1074,11 @@ class AvroToCSharp:
1040
1074
 
1041
1075
  fields: List[Field] = []
1042
1076
  if avro_schema and 'fields' in avro_schema:
1043
- for field in cast(List[Dict[str,JsonNode]],avro_schema['fields']):
1044
- field_name = str(field['name'])
1045
- if self.is_csharp_reserved_word(field_name):
1046
- field_name = f"@{field_name}"
1047
- if self.openapi_generator_compat:
1048
- field_name = self.get_openapi_property_name(field_name, class_name)
1049
- elif self.pascal_properties:
1050
- field_name = pascal(field_name)
1051
- if field_name == class_name:
1052
- field_name += "_"
1053
- field_type = self.convert_avro_type_to_csharp(class_name, field_name, field['type'], str(avro_schema.get('namespace', '')))
1077
+ avro_fields = cast(List[Dict[str,JsonNode]], avro_schema['fields'])
1078
+ property_names = self.get_csharp_property_names(avro_fields, class_name)
1079
+ for index, field in enumerate(avro_fields):
1080
+ field_name = property_names[index]
1081
+ field_type = self.convert_avro_type_to_csharp(class_name, str(field['name']), field['type'], str(avro_schema.get('namespace', '')))
1054
1082
  is_class = field_type in self.generated_types and self.generated_types[field_type] == "class"
1055
1083
  is_enum = self.is_enum_type(field['type'])
1056
1084
  is_union = field_type in self.generated_types and self.generated_types[field_type] == "union"
@@ -1128,6 +1156,12 @@ class AvroToCSharp:
1128
1156
 
1129
1157
  self.schema_doc = schema
1130
1158
  self.type_dict = build_flat_type_dict(self.schema_doc)
1159
+ if isinstance(self.schema_doc, list):
1160
+ for type_schema in self.type_dict.values():
1161
+ if isinstance(type_schema, dict) and type_schema.get('type') in ['record', 'enum']:
1162
+ full_name = self.concat_namespace(str(type_schema.get('namespace', '')), str(type_schema.get('name', '')))
1163
+ if full_name not in [self.concat_namespace(str(item.get('namespace', '')), str(item.get('name', ''))) for item in self.schema_doc if isinstance(item, dict)]:
1164
+ self.schema_doc.append(type_schema)
1131
1165
  if not os.path.exists(output_dir):
1132
1166
  os.makedirs(output_dir, exist_ok=True)
1133
1167
  if not glob.glob(os.path.join(output_dir, "src", "*.sln")):
@@ -7,6 +7,8 @@ import os
7
7
  import re
8
8
  from typing import Any
9
9
 
10
+ from avrotize.common import unique_name
11
+
10
12
 
11
13
  def _sanitize(name: str) -> str:
12
14
  cleaned = re.sub(r"\W", "_", name)
@@ -25,16 +27,21 @@ class AvroToFlatBuffersConverter:
25
27
  self.namespace_override = namespace
26
28
  self.named: dict[str, dict[str, Any]] = {}
27
29
  self.union_defs: dict[str, list[str]] = {}
30
+ self.type_names: dict[str, str] = {}
31
+ self.used_top_names: set[str] = set()
28
32
 
29
33
  def convert_file(self, avro_schema_path: str) -> str:
30
34
  with open(avro_schema_path, "r", encoding="utf-8") as f:
31
35
  schema = json.load(f)
32
36
  schemas = schema if isinstance(schema, list) else [schema]
37
+ self._prepare_names(schemas)
33
38
  for item in schemas:
34
39
  if isinstance(item, dict) and item.get("type") in {"record", "enum"}:
35
40
  qn = self.qualified_name(item)
36
41
  self.named[qn] = item
37
42
  self.named[item["name"]] = item
43
+ self.named[self.fbs_type_name(qn)] = item
44
+ self.named[self.fbs_type_name(item["name"])] = item
38
45
  namespace = self.namespace_override or self.first_namespace(schemas)
39
46
  lines: list[str] = []
40
47
  if namespace:
@@ -53,9 +60,22 @@ class AvroToFlatBuffersConverter:
53
60
  lines.extend(record_lines)
54
61
  root = self.find_root(schemas)
55
62
  if root:
56
- lines.append(f"root_type {root};")
63
+ lines.append(f"root_type {self.fbs_type_name(root)};")
57
64
  return "\n".join(lines).rstrip() + "\n"
58
65
 
66
+ def _prepare_names(self, schemas: list[Any]) -> None:
67
+ self.type_names = {}
68
+ self.used_top_names = set()
69
+ for item in schemas:
70
+ if isinstance(item, dict) and item.get("type") in {"record", "enum"}:
71
+ candidate = _sanitize(item["name"])
72
+ converted = unique_name(candidate, self.used_top_names)
73
+ self.type_names[item["name"]] = converted
74
+ self.type_names[self.qualified_name(item)] = converted
75
+
76
+ def fbs_type_name(self, name: str) -> str:
77
+ return self.type_names.get(name, self.type_names.get(name.split(".")[-1], _sanitize(name.split(".")[-1])))
78
+
59
79
  @staticmethod
60
80
  def qualified_name(schema: dict[str, Any]) -> str:
61
81
  return f"{schema.get('namespace')}.{schema['name']}" if schema.get("namespace") else schema["name"]
@@ -80,15 +100,19 @@ class AvroToFlatBuffersConverter:
80
100
  def render_enum(self, schema: dict[str, Any]) -> list[str]:
81
101
  ordinals = schema.get("ordinals", {})
82
102
  parts = []
103
+ used_symbols: set[str] = set()
83
104
  for i, symbol in enumerate(schema.get("symbols", [])):
84
105
  value = ordinals.get(symbol, i)
85
- parts.append(f"{_sanitize(symbol)} = {value}")
86
- return [f"enum {schema['name']} : int {{ {', '.join(parts)} }}"]
106
+ parts.append(f"{unique_name(_sanitize(symbol), used_symbols)} = {value}")
107
+ return [f"enum {self.fbs_type_name(self.qualified_name(schema))} : int {{ {', '.join(parts)} }}"]
87
108
 
88
109
  def render_record(self, schema: dict[str, Any]) -> list[str]:
89
- lines = [f"table {schema['name']} {{"]
110
+ record_name = self.fbs_type_name(self.qualified_name(schema))
111
+ lines = [f"table {record_name} {{"]
112
+ used_fields: set[str] = set()
90
113
  for field in schema.get("fields", []):
91
- fbs_type, nullable = self.avro_type_to_fbs(field["type"], schema["name"], field["name"])
114
+ field_name = unique_name(_sanitize(field["name"]), used_fields)
115
+ fbs_type, nullable = self.avro_type_to_fbs(field["type"], record_name, field_name)
92
116
  default = ""
93
117
  avro_default = field.get("fbsDefault", field.get("default"))
94
118
  if avro_default is not None and not isinstance(field["type"], list):
@@ -96,7 +120,7 @@ class AvroToFlatBuffersConverter:
96
120
  attr = ""
97
121
  if not nullable and self.can_be_required(fbs_type):
98
122
  attr = " (required)"
99
- lines.append(f" {_sanitize(field['name'])}: {fbs_type}{default}{attr};")
123
+ lines.append(f" {field_name}: {fbs_type}{default}{attr};")
100
124
  lines.append("}")
101
125
  return lines
102
126
 
@@ -125,11 +149,11 @@ class AvroToFlatBuffersConverter:
125
149
  fbs, _ = self.avro_type_to_fbs(non_null[0], record_name, field_name)
126
150
  return fbs, nullable
127
151
  members = [self.avro_type_to_fbs(t, record_name, field_name)[0] for t in non_null]
128
- union_name = f"{record_name}_{field_name}_Union"
152
+ union_name = unique_name(_sanitize(f"{record_name}_{field_name}_Union"), self.used_top_names)
129
153
  self.union_defs[union_name] = [m for m in members if not m.startswith("[")]
130
154
  return union_name, nullable
131
155
  if isinstance(avro_type, str):
132
- return self.primitive_map.get(avro_type, avro_type.split(".")[-1]), nullable
156
+ return self.primitive_map.get(avro_type, self.fbs_type_name(avro_type)), nullable
133
157
  if isinstance(avro_type, dict):
134
158
  typ = avro_type.get("type")
135
159
  if typ == "array":
@@ -139,7 +163,7 @@ class AvroToFlatBuffersConverter:
139
163
  return "string", nullable
140
164
  if typ == "enum" or typ == "record":
141
165
  self.named[avro_type["name"]] = avro_type
142
- return avro_type["name"], nullable
166
+ return self.fbs_type_name(avro_type["name"]), nullable
143
167
  if typ == "bytes":
144
168
  return "[ubyte]", nullable
145
169
  return self.primitive_map.get(typ, typ), nullable