structurize 3.9.1__tar.gz → 3.9.3__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.
- {structurize-3.9.1/structurize.egg-info → structurize-3.9.3}/PKG-INFO +1 -1
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/_version.py +3 -3
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotojstruct.py +122 -14
- structurize-3.9.3/avrotize/avrotojtd.py +404 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/cddltostructure.py +27 -2
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jsonstostructure.py +118 -3
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jstructtoavro.py +18 -5
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jtdtoavro.py +196 -9
- structurize-3.9.3/avrotize/jtdtostructure.py +151 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretocsharp.py +7 -2
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretodatapackage.py +0 -9
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretographql.py +297 -103
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretopython.py +503 -25
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretots.py +73 -13
- {structurize-3.9.1 → structurize-3.9.3/structurize.egg-info}/PKG-INFO +1 -1
- structurize-3.9.1/avrotize/avrotojtd.py +0 -228
- structurize-3.9.1/avrotize/jtdtostructure.py +0 -24
- {structurize-3.9.1 → structurize-3.9.3}/.gitignore +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/LICENSE +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/MANIFEST.in +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/README.md +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/__init__.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/__main__.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/asn1toavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotize.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoasn1.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotocapnproto.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotocpp.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotocsharp.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotocsv.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotocue.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotodatapackage.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotodb.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoflatbuffers.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotogo.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotographql.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoiceberg.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotojava.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotojs.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotojsons.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotokusto.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotomd.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotools.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoparquet.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoproto.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotopython.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoraml.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotorust.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotosmithy.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotosurreal.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotothrift.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotots.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrototsml.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrotoxsd.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/avrovalidator.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/capnprototoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/choice_inference.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/commands.json +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/common.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/constants.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/csvtoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/cuetoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/cuetostructure.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/datapackagetoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/dependencies/typescript/node22/package.json +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/dependency_resolver.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/dependency_version.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/flatbufferstoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/flatbufferstojstruct.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jsonstoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jsontoschema.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jstructtoflatbuffers.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jstructtoraml.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/jstructtosmithy.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/kstructtoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/kustotoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/kustotojstruct.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/mcp_server.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/openapitostructure.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/parquettoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/proto2parser.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/proto3parser.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/prototoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/ramltoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/ramltojstruct.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/rust_xml.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/schema_inference.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/smithytoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/smithytojstruct.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/sqltoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretoasn1.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretocddl.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretocpp.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretocsv.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretocue.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretodb.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretogo.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretoiceberg.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretojava.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretojs.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretojsons.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretojtd.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretokusto.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretomd.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretoparquet.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretoproto.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretorust.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretotsml.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/structuretoxsd.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/surrealtoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/thriftstructure.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/thrifttoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/tmslvalidate.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/validate.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/xmltoschema.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/avrotize/xsdtoavro.py +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/build.ps1 +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/build.sh +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/pyproject.toml +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/setup.cfg +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/structurize.egg-info/SOURCES.txt +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/structurize.egg-info/dependency_links.txt +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/structurize.egg-info/entry_points.txt +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/structurize.egg-info/requires.txt +0 -0
- {structurize-3.9.1 → structurize-3.9.3}/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.9.
|
|
3
|
+
Version: 3.9.3
|
|
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.9.
|
|
22
|
-
__version_tuple__ = version_tuple = (3, 9,
|
|
21
|
+
__version__ = version = '3.9.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 9, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g0848ff37a'
|
|
@@ -13,6 +13,7 @@ class AvroToJsonStructure:
|
|
|
13
13
|
def __init__(self, avro_encoding: bool = False) -> None:
|
|
14
14
|
self.known_types: set[str] = set()
|
|
15
15
|
self.reference_stack: set[str] = set()
|
|
16
|
+
self.definition_paths: dict[str, str] = {}
|
|
16
17
|
self.avro_encoding: bool = avro_encoding
|
|
17
18
|
|
|
18
19
|
# ------------------------------------------------------------------ TOP-LEVEL
|
|
@@ -38,10 +39,29 @@ class AvroToJsonStructure:
|
|
|
38
39
|
"definitions": {},
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
# TEMPORARY: process only first element
|
|
42
42
|
first = avro_schema[0]
|
|
43
43
|
if isinstance(first, dict):
|
|
44
|
-
|
|
44
|
+
self.known_types.clear()
|
|
45
|
+
self.reference_stack.clear()
|
|
46
|
+
self._prepare_definition_paths(avro_schema, namespace)
|
|
47
|
+
|
|
48
|
+
current_namespace, name = self.resolve_full_name(
|
|
49
|
+
first.get("name", f"AnonymousType_{uuid.uuid4().hex}"),
|
|
50
|
+
first.get("namespace", namespace),
|
|
51
|
+
)
|
|
52
|
+
fqn = self.get_fqn(current_namespace, name)
|
|
53
|
+
doc: Dict[str, Any] = {
|
|
54
|
+
"$schema": "https://json-structure.org/meta/core/v0/#",
|
|
55
|
+
"$id": f"https://example.com/schemas/{fqn}",
|
|
56
|
+
"name": name,
|
|
57
|
+
"type": "null",
|
|
58
|
+
"definitions": {},
|
|
59
|
+
}
|
|
60
|
+
for schema in avro_schema:
|
|
61
|
+
if isinstance(schema, dict):
|
|
62
|
+
self.register_definition(schema, namespace, doc["definitions"])
|
|
63
|
+
self._apply_extensions(doc)
|
|
64
|
+
return doc
|
|
45
65
|
|
|
46
66
|
# First element non-dict → return stub
|
|
47
67
|
bad_name = f"invalid_list_root_{uuid.uuid4().hex[:8]}"
|
|
@@ -56,10 +76,11 @@ class AvroToJsonStructure:
|
|
|
56
76
|
# Reset caches for each top-level conversion
|
|
57
77
|
self.known_types.clear()
|
|
58
78
|
self.reference_stack.clear()
|
|
79
|
+
self._prepare_definition_paths(avro_schema, namespace)
|
|
59
80
|
|
|
60
|
-
current_namespace =
|
|
61
|
-
|
|
62
|
-
avro_schema.get("
|
|
81
|
+
current_namespace, name = self.resolve_full_name(
|
|
82
|
+
avro_schema.get("name", f"AnonymousType_{uuid.uuid4().hex}"),
|
|
83
|
+
avro_schema.get("namespace", namespace),
|
|
63
84
|
)
|
|
64
85
|
fqn = self.get_fqn(current_namespace, name)
|
|
65
86
|
|
|
@@ -73,8 +94,26 @@ class AvroToJsonStructure:
|
|
|
73
94
|
|
|
74
95
|
# Build definitions – do NOT skip root
|
|
75
96
|
self.register_definition(avro_schema, current_namespace, doc["definitions"])
|
|
97
|
+
self._apply_extensions(doc)
|
|
76
98
|
return doc
|
|
77
99
|
|
|
100
|
+
def _apply_extensions(self, doc: Dict[str, Any]) -> None:
|
|
101
|
+
uses = set(doc.get("$uses", []))
|
|
102
|
+
if self._contains_key(doc, "default"):
|
|
103
|
+
uses.add("JSONStructureValidation")
|
|
104
|
+
if self._contains_key(doc, "altnames"):
|
|
105
|
+
uses.add("JSONStructureAlternateNames")
|
|
106
|
+
if uses:
|
|
107
|
+
doc["$schema"] = "https://json-structure.org/meta/extended/v0/#"
|
|
108
|
+
doc["$uses"] = sorted(uses)
|
|
109
|
+
|
|
110
|
+
def _contains_key(self, value: Any, key: str) -> bool:
|
|
111
|
+
if isinstance(value, dict):
|
|
112
|
+
return key in value or any(self._contains_key(item, key) for item in value.values())
|
|
113
|
+
if isinstance(value, list):
|
|
114
|
+
return any(self._contains_key(item, key) for item in value)
|
|
115
|
+
return False
|
|
116
|
+
|
|
78
117
|
# ------------------------------------------------------------------ REGISTRATION
|
|
79
118
|
|
|
80
119
|
def register_definition(
|
|
@@ -88,9 +127,9 @@ class AvroToJsonStructure:
|
|
|
88
127
|
Ensure `avro_schema` has an entry in `definitions`.
|
|
89
128
|
"""
|
|
90
129
|
|
|
91
|
-
current_namespace =
|
|
92
|
-
|
|
93
|
-
avro_schema.get("
|
|
130
|
+
current_namespace, name = self.resolve_full_name(
|
|
131
|
+
avro_schema.get("name", f"AnonymousType_{uuid.uuid4().hex}"),
|
|
132
|
+
avro_schema.get("namespace", namespace),
|
|
94
133
|
)
|
|
95
134
|
fqn = self.get_fqn(current_namespace, name)
|
|
96
135
|
|
|
@@ -113,8 +152,10 @@ class AvroToJsonStructure:
|
|
|
113
152
|
|
|
114
153
|
avro_type = avro_schema.get("type")
|
|
115
154
|
# Use the schema's own namespace if provided, otherwise fall back to the passed 'namespace'
|
|
116
|
-
current_schema_namespace =
|
|
117
|
-
|
|
155
|
+
current_schema_namespace, name = self.resolve_full_name(
|
|
156
|
+
avro_schema.get("name", f"AnonymousType_{uuid.uuid4().hex}"),
|
|
157
|
+
avro_schema.get("namespace", namespace),
|
|
158
|
+
)
|
|
118
159
|
fqn = self.get_fqn(current_schema_namespace, name)
|
|
119
160
|
|
|
120
161
|
if fqn in self.reference_stack:
|
|
@@ -202,6 +243,10 @@ class AvroToJsonStructure:
|
|
|
202
243
|
# If a definition was constructed, add it to the definitions map with proper nesting.
|
|
203
244
|
if type_definition_content is not None:
|
|
204
245
|
parts = fqn.split('/')
|
|
246
|
+
definition_name = parts[-1]
|
|
247
|
+
if definition_name != name:
|
|
248
|
+
type_definition_content["name"] = definition_name
|
|
249
|
+
type_definition_content.setdefault("altnames", {})["avro"] = name
|
|
205
250
|
current_level_dict = definitions
|
|
206
251
|
for i, part_name in enumerate(parts):
|
|
207
252
|
if i == len(parts) - 1: # Last part is the type name itself
|
|
@@ -232,7 +277,10 @@ class AvroToJsonStructure:
|
|
|
232
277
|
return {"type": self.get_primitive_types()[avro_type_schema]}
|
|
233
278
|
# Named type reference
|
|
234
279
|
if "." in avro_type_schema:
|
|
235
|
-
|
|
280
|
+
ref_namespace, ref_name = self.resolve_full_name(
|
|
281
|
+
avro_type_schema, context_namespace
|
|
282
|
+
)
|
|
283
|
+
ref_fqn = self.get_fqn(ref_namespace, ref_name)
|
|
236
284
|
else:
|
|
237
285
|
ref_fqn = self.get_fqn(context_namespace, self.clean_name(avro_type_schema))
|
|
238
286
|
# JSON Structure Core requires a type reference to be the value of the
|
|
@@ -268,8 +316,10 @@ class AvroToJsonStructure:
|
|
|
268
316
|
if category in ("record", "enum", "fixed"):
|
|
269
317
|
# Ensure definition exists then reference it
|
|
270
318
|
self.register_definition(avro_type_schema, inline_ns, definitions)
|
|
271
|
-
ref_name = self.
|
|
272
|
-
|
|
319
|
+
ref_namespace, ref_name = self.resolve_full_name(
|
|
320
|
+
avro_type_schema["name"], inline_ns
|
|
321
|
+
)
|
|
322
|
+
ref_fqn = self.get_fqn(ref_namespace, ref_name)
|
|
273
323
|
# Wrap the reference under ``type`` (see note above) so the emitted
|
|
274
324
|
# property/items/values/choice node is a valid JSON Structure schema.
|
|
275
325
|
return {"type": {"$ref": f"#/definitions/{ref_fqn}"}}
|
|
@@ -350,11 +400,69 @@ class AvroToJsonStructure:
|
|
|
350
400
|
def clean_name(self, name: str) -> str:
|
|
351
401
|
return name.replace(".", "_")
|
|
352
402
|
|
|
353
|
-
def
|
|
403
|
+
def resolve_full_name(self, name: str, namespace: str | None) -> tuple[str | None, str]:
|
|
404
|
+
if "." in name:
|
|
405
|
+
namespace, name = name.rsplit(".", 1)
|
|
406
|
+
return namespace, self.clean_name(name)
|
|
407
|
+
|
|
408
|
+
def _prepare_definition_paths(
|
|
409
|
+
self, avro_schema: Any, namespace: str | None = None
|
|
410
|
+
) -> None:
|
|
411
|
+
full_names: set[str] = set()
|
|
412
|
+
|
|
413
|
+
def collect(node: Any, current_namespace: str | None) -> None:
|
|
414
|
+
if isinstance(node, list):
|
|
415
|
+
for item in node:
|
|
416
|
+
collect(item, current_namespace)
|
|
417
|
+
return
|
|
418
|
+
if not isinstance(node, dict):
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
category = node.get("type")
|
|
422
|
+
inline_namespace = node.get("namespace", current_namespace)
|
|
423
|
+
if category in ("record", "enum", "fixed") and node.get("name"):
|
|
424
|
+
type_namespace, type_name = self.resolve_full_name(
|
|
425
|
+
node["name"], inline_namespace
|
|
426
|
+
)
|
|
427
|
+
raw_fqn = self.get_raw_fqn(type_namespace, type_name)
|
|
428
|
+
full_names.add(raw_fqn)
|
|
429
|
+
if category == "record":
|
|
430
|
+
for field in node.get("fields", []):
|
|
431
|
+
collect(field.get("type"), type_namespace)
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
if isinstance(category, (dict, list)):
|
|
435
|
+
collect(category, inline_namespace)
|
|
436
|
+
elif category == "array":
|
|
437
|
+
collect(node.get("items"), inline_namespace)
|
|
438
|
+
elif category == "map":
|
|
439
|
+
collect(node.get("values"), inline_namespace)
|
|
440
|
+
|
|
441
|
+
collect(avro_schema, namespace)
|
|
442
|
+
reserved_names = {"type", "definitions"}
|
|
443
|
+
self.definition_paths.clear()
|
|
444
|
+
for full_name in full_names:
|
|
445
|
+
parts = full_name.split("/")
|
|
446
|
+
encoded_parts = []
|
|
447
|
+
for index, part in enumerate(parts):
|
|
448
|
+
prefix = "/".join(parts[: index + 1])
|
|
449
|
+
if index < len(parts) - 1 and prefix in full_names:
|
|
450
|
+
part += "_"
|
|
451
|
+
elif index == len(parts) - 1 and part in reserved_names:
|
|
452
|
+
part += "_"
|
|
453
|
+
encoded_parts.append(part)
|
|
454
|
+
self.definition_paths[full_name] = "/".join(encoded_parts)
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
def get_raw_fqn(namespace: str | None, name: str) -> str:
|
|
354
458
|
if namespace:
|
|
355
459
|
return f"{namespace.replace('.', '/')}/{name}"
|
|
356
460
|
return name
|
|
357
461
|
|
|
462
|
+
def get_fqn(self, namespace: str | None, name: str) -> str:
|
|
463
|
+
raw_fqn = self.get_raw_fqn(namespace, name)
|
|
464
|
+
return self.definition_paths.get(raw_fqn, raw_fqn)
|
|
465
|
+
|
|
358
466
|
@staticmethod
|
|
359
467
|
def get_primitive_types() -> Dict[str, str]:
|
|
360
468
|
return {
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Avrotize Schema to JSON Type Definition (RFC 8927) converter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from avrotize.common import altname
|
|
9
|
+
|
|
10
|
+
AvroSchema = dict[str, Any] | list[Any] | str
|
|
11
|
+
JtdSchema = dict[str, Any]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AvroToJtdConverter:
|
|
15
|
+
"""Convert Avrotize/Avro schemas to JSON Type Definition schemas."""
|
|
16
|
+
|
|
17
|
+
PRIMITIVE_TO_JTD: dict[str, str] = {
|
|
18
|
+
"boolean": "boolean",
|
|
19
|
+
"int": "int32",
|
|
20
|
+
"long": "uint32",
|
|
21
|
+
"float": "float32",
|
|
22
|
+
"double": "float64",
|
|
23
|
+
"string": "string",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def __init__(self, record_type: str | None = None) -> None:
|
|
27
|
+
"""Initialize the converter."""
|
|
28
|
+
self.record_type = record_type
|
|
29
|
+
self.named_types: dict[str, dict[str, Any]] = {}
|
|
30
|
+
self.simple_names: dict[str, str] = {}
|
|
31
|
+
self.inherited_namespaces: dict[int, str] = {}
|
|
32
|
+
self.inline_named_schema_ids: set[int] = set()
|
|
33
|
+
self.definitions: dict[str, JtdSchema] = {}
|
|
34
|
+
self.converting: set[str] = set()
|
|
35
|
+
self.root_key: str | None = None
|
|
36
|
+
|
|
37
|
+
def convert(self, avro_schema: AvroSchema) -> JtdSchema:
|
|
38
|
+
"""Convert an Avro schema object to a JTD schema dictionary."""
|
|
39
|
+
self._register_named_types(avro_schema)
|
|
40
|
+
root = self._register_top_level(avro_schema)
|
|
41
|
+
if self.record_type:
|
|
42
|
+
root = self._find_named_type(self.record_type)
|
|
43
|
+
if isinstance(root, dict) and root.get("type") in {"record", "enum"}:
|
|
44
|
+
self.root_key = self._type_key(root)
|
|
45
|
+
root_namespace = self._schema_namespace(root) if isinstance(root, dict) else None
|
|
46
|
+
root_jtd = self._convert_type(root, root_namespace)
|
|
47
|
+
if isinstance(root, dict) and root.get("type") in {"record", "enum"} and root.get("jtdRoot"):
|
|
48
|
+
root_name = self._type_key(root)
|
|
49
|
+
self.definitions[root_name] = root_jtd
|
|
50
|
+
return {"definitions": self.definitions, "ref": root_name}
|
|
51
|
+
if self.definitions:
|
|
52
|
+
if isinstance(root, dict) and root.get("type") in {"record", "enum"}:
|
|
53
|
+
root_name = self._type_key(root)
|
|
54
|
+
self.definitions[root_name] = root_jtd
|
|
55
|
+
return {"definitions": self.definitions, "ref": root_name}
|
|
56
|
+
root_jtd = dict(root_jtd)
|
|
57
|
+
root_jtd["definitions"] = self.definitions
|
|
58
|
+
return root_jtd
|
|
59
|
+
|
|
60
|
+
def _register_top_level(self, avro_schema: AvroSchema) -> AvroSchema:
|
|
61
|
+
if isinstance(avro_schema, list):
|
|
62
|
+
if self._is_discriminator_union(avro_schema):
|
|
63
|
+
for item in avro_schema:
|
|
64
|
+
self._register_named(item)
|
|
65
|
+
return avro_schema
|
|
66
|
+
discriminator_branches = [
|
|
67
|
+
item for item in avro_schema
|
|
68
|
+
if isinstance(item, dict) and item.get("type") == "record" and item.get("jtdDiscriminator")
|
|
69
|
+
]
|
|
70
|
+
if self._is_discriminator_union(discriminator_branches):
|
|
71
|
+
for item in avro_schema:
|
|
72
|
+
if isinstance(item, dict) and item.get("type") in {"record", "enum"}:
|
|
73
|
+
self._register_named(item)
|
|
74
|
+
helper_dependencies = self._named_dependencies_of(discriminator_branches)
|
|
75
|
+
return [
|
|
76
|
+
item for item in avro_schema
|
|
77
|
+
if not (
|
|
78
|
+
isinstance(item, dict)
|
|
79
|
+
and item.get("type") in {"record", "enum"}
|
|
80
|
+
and not item.get("jtdDiscriminator")
|
|
81
|
+
and self._type_key(item) in helper_dependencies
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
named_items = [
|
|
85
|
+
item for item in avro_schema
|
|
86
|
+
if isinstance(item, dict) and item.get("type") in {"record", "enum"}
|
|
87
|
+
]
|
|
88
|
+
root_members = [item for item in avro_schema if item not in named_items]
|
|
89
|
+
has_explicit_root = any(item.get("jtdRoot") for item in named_items) or any(
|
|
90
|
+
isinstance(item, dict) and item.get("jtdRoot") for item in root_members
|
|
91
|
+
)
|
|
92
|
+
if named_items and root_members and has_explicit_root:
|
|
93
|
+
for item in named_items:
|
|
94
|
+
self._register_named(item)
|
|
95
|
+
if len(root_members) == 1:
|
|
96
|
+
return root_members[0]
|
|
97
|
+
return root_members
|
|
98
|
+
if not all(isinstance(item, dict) and item.get("type") in {"record", "enum"} for item in avro_schema):
|
|
99
|
+
return avro_schema
|
|
100
|
+
for item in avro_schema:
|
|
101
|
+
self._register_named(item)
|
|
102
|
+
root = next((item for item in avro_schema if item.get("jtdRoot")), avro_schema[-1] if avro_schema else None)
|
|
103
|
+
if root is None:
|
|
104
|
+
raise ValueError("Avro schema list is empty")
|
|
105
|
+
return root
|
|
106
|
+
if isinstance(avro_schema, dict) and avro_schema.get("type") in {"record", "enum"}:
|
|
107
|
+
self._register_named(avro_schema)
|
|
108
|
+
return avro_schema
|
|
109
|
+
|
|
110
|
+
def _register_named_types(
|
|
111
|
+
self,
|
|
112
|
+
avro_type: AvroSchema,
|
|
113
|
+
seen: set[int] | None = None,
|
|
114
|
+
inherited_namespace: str | None = None,
|
|
115
|
+
inline: bool = False,
|
|
116
|
+
) -> None:
|
|
117
|
+
if seen is None:
|
|
118
|
+
seen = set()
|
|
119
|
+
if isinstance(avro_type, list):
|
|
120
|
+
for member in avro_type:
|
|
121
|
+
self._register_named_types(member, seen, inherited_namespace, inline)
|
|
122
|
+
return
|
|
123
|
+
if not isinstance(avro_type, dict) or id(avro_type) in seen:
|
|
124
|
+
return
|
|
125
|
+
seen.add(id(avro_type))
|
|
126
|
+
|
|
127
|
+
schema_type = avro_type.get("type")
|
|
128
|
+
if schema_type in {"record", "enum"} and avro_type.get("name"):
|
|
129
|
+
namespace = avro_type.get("namespace", inherited_namespace)
|
|
130
|
+
if namespace:
|
|
131
|
+
self.inherited_namespaces[id(avro_type)] = str(namespace)
|
|
132
|
+
if inline:
|
|
133
|
+
self.inline_named_schema_ids.add(id(avro_type))
|
|
134
|
+
self._register_named(avro_type)
|
|
135
|
+
inherited_namespace = str(namespace) if namespace else None
|
|
136
|
+
if schema_type == "record":
|
|
137
|
+
for field in avro_type.get("fields", []):
|
|
138
|
+
self._register_named_types(field.get("type", "string"), seen, inherited_namespace, True)
|
|
139
|
+
elif schema_type == "array":
|
|
140
|
+
self._register_named_types(avro_type.get("items", "string"), seen, inherited_namespace, True)
|
|
141
|
+
elif schema_type == "map":
|
|
142
|
+
self._register_named_types(avro_type.get("values", "string"), seen, inherited_namespace, True)
|
|
143
|
+
elif isinstance(schema_type, (dict, list)):
|
|
144
|
+
self._register_named_types(schema_type, seen, inherited_namespace, True)
|
|
145
|
+
|
|
146
|
+
def _register_named(self, schema: dict[str, Any]) -> None:
|
|
147
|
+
key = self._type_key(schema)
|
|
148
|
+
self.named_types[key] = schema
|
|
149
|
+
self.simple_names[schema["name"]] = key
|
|
150
|
+
|
|
151
|
+
def _named_dependencies_of(self, roots: list[Any]) -> set[str]:
|
|
152
|
+
root_keys = {
|
|
153
|
+
self._type_key(root)
|
|
154
|
+
for root in roots
|
|
155
|
+
if isinstance(root, dict) and root.get("type") in {"record", "enum"}
|
|
156
|
+
}
|
|
157
|
+
dependencies: set[str] = set()
|
|
158
|
+
pending = list(roots)
|
|
159
|
+
while pending:
|
|
160
|
+
for reference in self._named_references(pending.pop()):
|
|
161
|
+
if reference in root_keys or reference in dependencies:
|
|
162
|
+
continue
|
|
163
|
+
dependencies.add(reference)
|
|
164
|
+
pending.append(self.named_types[reference])
|
|
165
|
+
return dependencies
|
|
166
|
+
|
|
167
|
+
def _named_references(self, avro_type: AvroSchema, namespace: str | None = None) -> set[str]:
|
|
168
|
+
if isinstance(avro_type, str):
|
|
169
|
+
key = self._resolve_named_key(avro_type, namespace)
|
|
170
|
+
return {key} if key else set()
|
|
171
|
+
if isinstance(avro_type, list):
|
|
172
|
+
references: set[str] = set()
|
|
173
|
+
for member in avro_type:
|
|
174
|
+
references.update(self._named_references(member, namespace))
|
|
175
|
+
return references
|
|
176
|
+
if not isinstance(avro_type, dict):
|
|
177
|
+
return set()
|
|
178
|
+
|
|
179
|
+
schema_type = avro_type.get("type")
|
|
180
|
+
if schema_type == "record":
|
|
181
|
+
record_namespace = self._schema_namespace(avro_type, namespace)
|
|
182
|
+
references: set[str] = set()
|
|
183
|
+
for field in avro_type.get("fields", []):
|
|
184
|
+
references.update(self._named_references(field.get("type", "string"), record_namespace))
|
|
185
|
+
return references
|
|
186
|
+
if schema_type == "array":
|
|
187
|
+
return self._named_references(avro_type.get("items", "string"), namespace)
|
|
188
|
+
if schema_type == "map":
|
|
189
|
+
return self._named_references(avro_type.get("values", "string"), namespace)
|
|
190
|
+
if isinstance(schema_type, (dict, list, str)):
|
|
191
|
+
return self._named_references(schema_type, namespace)
|
|
192
|
+
return set()
|
|
193
|
+
|
|
194
|
+
def _find_named_type(self, record_type: str) -> dict[str, Any]:
|
|
195
|
+
key = self._resolve_named_key(record_type)
|
|
196
|
+
if key and key in self.named_types:
|
|
197
|
+
return self.named_types[key]
|
|
198
|
+
raise ValueError(f"Avro record type '{record_type}' not found")
|
|
199
|
+
|
|
200
|
+
def _type_key(self, schema: dict[str, Any]) -> str:
|
|
201
|
+
namespace = self._schema_namespace(schema)
|
|
202
|
+
return f"{namespace}.{schema['name']}" if namespace else schema["name"]
|
|
203
|
+
|
|
204
|
+
def _schema_namespace(self, schema: dict[str, Any], fallback: str | None = None) -> str | None:
|
|
205
|
+
if "namespace" in schema:
|
|
206
|
+
namespace = schema.get("namespace")
|
|
207
|
+
else:
|
|
208
|
+
namespace = self.inherited_namespaces.get(id(schema), fallback)
|
|
209
|
+
return str(namespace) if namespace else None
|
|
210
|
+
|
|
211
|
+
def _resolve_named_key(self, reference: str, namespace: str | None = None) -> str | None:
|
|
212
|
+
if reference in self.named_types:
|
|
213
|
+
return reference
|
|
214
|
+
if namespace and "." not in reference:
|
|
215
|
+
relative_key = f"{namespace}.{reference}"
|
|
216
|
+
if relative_key in self.named_types:
|
|
217
|
+
return relative_key
|
|
218
|
+
return self.simple_names.get(reference)
|
|
219
|
+
|
|
220
|
+
def _convert_type(self, avro_type: AvroSchema, namespace: str | None = None) -> JtdSchema:
|
|
221
|
+
nullable, non_null = self._strip_nullable(avro_type)
|
|
222
|
+
if self._is_discriminator_union(non_null):
|
|
223
|
+
jtd = self._convert_discriminator(non_null, namespace) # type: ignore[arg-type]
|
|
224
|
+
elif isinstance(non_null, dict) and non_null.get("type") == "record" and non_null.get("jtdDiscriminator"):
|
|
225
|
+
jtd = self._convert_discriminator([non_null], namespace)
|
|
226
|
+
else:
|
|
227
|
+
jtd = self._convert_non_nullable(non_null, namespace)
|
|
228
|
+
if nullable:
|
|
229
|
+
jtd = dict(jtd)
|
|
230
|
+
jtd["nullable"] = True
|
|
231
|
+
return jtd
|
|
232
|
+
|
|
233
|
+
def _convert_non_nullable(self, avro_type: AvroSchema, namespace: str | None = None) -> JtdSchema:
|
|
234
|
+
if isinstance(avro_type, str):
|
|
235
|
+
if avro_type == "null":
|
|
236
|
+
return {"nullable": True}
|
|
237
|
+
if avro_type in self.PRIMITIVE_TO_JTD:
|
|
238
|
+
return {"type": self.PRIMITIVE_TO_JTD[avro_type]}
|
|
239
|
+
ref_key = self._resolve_named_key(avro_type, namespace)
|
|
240
|
+
if ref_key:
|
|
241
|
+
self._ensure_definition(ref_key)
|
|
242
|
+
return {"ref": ref_key}
|
|
243
|
+
return {"type": "string"}
|
|
244
|
+
if isinstance(avro_type, list):
|
|
245
|
+
return self._convert_union(avro_type, namespace)
|
|
246
|
+
if not isinstance(avro_type, dict):
|
|
247
|
+
return {"type": "string"}
|
|
248
|
+
|
|
249
|
+
if avro_type.get("jtdWrappedDefinition"):
|
|
250
|
+
fields = avro_type.get("fields", [])
|
|
251
|
+
if len(fields) == 1:
|
|
252
|
+
return self._convert_type(fields[0].get("type", "string"), namespace)
|
|
253
|
+
if avro_type.get("jtdType"):
|
|
254
|
+
return {"type": avro_type["jtdType"]}
|
|
255
|
+
if avro_type.get("logicalType") == "timestamp-millis" and avro_type.get("type") == "long":
|
|
256
|
+
return {"type": "timestamp"}
|
|
257
|
+
|
|
258
|
+
schema_type = avro_type.get("type")
|
|
259
|
+
if isinstance(schema_type, dict) or isinstance(schema_type, list):
|
|
260
|
+
return self._convert_type(schema_type, namespace)
|
|
261
|
+
if schema_type in self.PRIMITIVE_TO_JTD:
|
|
262
|
+
return {"type": self.PRIMITIVE_TO_JTD[schema_type]}
|
|
263
|
+
if schema_type == "record":
|
|
264
|
+
key = self._type_key(avro_type)
|
|
265
|
+
if id(avro_type) in self.inline_named_schema_ids and key != self.root_key:
|
|
266
|
+
self._ensure_definition(key)
|
|
267
|
+
return {"ref": key}
|
|
268
|
+
return self._convert_record(avro_type, namespace)
|
|
269
|
+
if schema_type == "enum":
|
|
270
|
+
key = self._type_key(avro_type)
|
|
271
|
+
if id(avro_type) in self.inline_named_schema_ids and key != self.root_key:
|
|
272
|
+
self._ensure_definition(key)
|
|
273
|
+
return {"ref": key}
|
|
274
|
+
return self._convert_enum(avro_type)
|
|
275
|
+
if schema_type == "array":
|
|
276
|
+
return {"elements": self._convert_type(avro_type.get("items", "string"), namespace)}
|
|
277
|
+
if schema_type == "map":
|
|
278
|
+
return {"values": self._convert_type(avro_type.get("values", "string"), namespace)}
|
|
279
|
+
if isinstance(schema_type, str):
|
|
280
|
+
ref_key = self._resolve_named_key(schema_type, namespace)
|
|
281
|
+
if ref_key:
|
|
282
|
+
self._ensure_definition(ref_key)
|
|
283
|
+
return {"ref": ref_key}
|
|
284
|
+
return {"type": "string"}
|
|
285
|
+
|
|
286
|
+
def _convert_record(self, schema: dict[str, Any], parent_namespace: str | None = None) -> JtdSchema:
|
|
287
|
+
key = self._type_key(schema)
|
|
288
|
+
if key in self.converting:
|
|
289
|
+
return {"ref": key}
|
|
290
|
+
namespace = self._schema_namespace(schema, parent_namespace)
|
|
291
|
+
self.converting.add(key)
|
|
292
|
+
properties: dict[str, JtdSchema] = {}
|
|
293
|
+
optional_properties: dict[str, JtdSchema] = {}
|
|
294
|
+
for field in schema.get("fields", []):
|
|
295
|
+
if field.get("name") == schema.get("jtdDiscriminator"):
|
|
296
|
+
continue
|
|
297
|
+
property_name = altname(field, "jtd")
|
|
298
|
+
field_nullable, field_type = self._strip_nullable(field.get("type", "string"))
|
|
299
|
+
converted = self._convert_type(field_type, namespace)
|
|
300
|
+
if field_nullable and field.get("default") is None:
|
|
301
|
+
optional_properties[property_name] = converted
|
|
302
|
+
else:
|
|
303
|
+
if field_nullable:
|
|
304
|
+
converted = dict(converted)
|
|
305
|
+
converted["nullable"] = True
|
|
306
|
+
properties[property_name] = converted
|
|
307
|
+
self.converting.remove(key)
|
|
308
|
+
result: JtdSchema = {}
|
|
309
|
+
if properties:
|
|
310
|
+
result["properties"] = properties
|
|
311
|
+
if optional_properties:
|
|
312
|
+
result["optionalProperties"] = optional_properties
|
|
313
|
+
if "jtdAdditionalProperties" in schema:
|
|
314
|
+
result["additionalProperties"] = bool(schema["jtdAdditionalProperties"])
|
|
315
|
+
return result
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _convert_enum(schema: dict[str, Any]) -> JtdSchema:
|
|
319
|
+
original_symbols = schema.get("jtdEnumSymbols", {})
|
|
320
|
+
return {"enum": [original_symbols.get(symbol, symbol) for symbol in schema.get("symbols", [])]}
|
|
321
|
+
|
|
322
|
+
def _convert_union(self, union: list[Any], namespace: str | None = None) -> JtdSchema:
|
|
323
|
+
non_null = [item for item in union if item != "null"]
|
|
324
|
+
if len(non_null) == 1:
|
|
325
|
+
return self._convert_type(non_null[0], namespace)
|
|
326
|
+
if self._is_discriminator_union(non_null):
|
|
327
|
+
return self._convert_discriminator(non_null, namespace)
|
|
328
|
+
discriminator_branches = [
|
|
329
|
+
item for item in non_null
|
|
330
|
+
if isinstance(item, dict) and item.get("type") == "record" and item.get("jtdDiscriminator")
|
|
331
|
+
]
|
|
332
|
+
if self._is_discriminator_union(discriminator_branches):
|
|
333
|
+
tagged_ids = {id(branch) for branch in discriminator_branches}
|
|
334
|
+
converted: list[JtdSchema] = []
|
|
335
|
+
discriminator_added = False
|
|
336
|
+
for item in non_null:
|
|
337
|
+
if id(item) in tagged_ids:
|
|
338
|
+
if not discriminator_added:
|
|
339
|
+
converted.append(self._convert_discriminator(discriminator_branches, namespace))
|
|
340
|
+
discriminator_added = True
|
|
341
|
+
else:
|
|
342
|
+
converted.append(self._convert_type(item, namespace))
|
|
343
|
+
return {"metadata": {"avrotize-union": converted}}
|
|
344
|
+
return {"metadata": {"avrotize-union": [self._convert_type(item, namespace) for item in non_null]}}
|
|
345
|
+
|
|
346
|
+
def _convert_discriminator(self, union: list[Any], namespace: str | None = None) -> JtdSchema:
|
|
347
|
+
discriminator = union[0].get("jtdDiscriminator")
|
|
348
|
+
mapping: dict[str, JtdSchema] = {}
|
|
349
|
+
for branch in union:
|
|
350
|
+
tag = branch.get("jtdMappingKey")
|
|
351
|
+
if tag is None:
|
|
352
|
+
tag = branch.get("name")
|
|
353
|
+
mapping[str(tag)] = self._convert_record(branch, namespace)
|
|
354
|
+
return {"discriminator": discriminator, "mapping": mapping}
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _strip_nullable(avro_type: AvroSchema) -> tuple[bool, AvroSchema]:
|
|
358
|
+
if isinstance(avro_type, list) and "null" in avro_type:
|
|
359
|
+
non_null = [item for item in avro_type if item != "null"]
|
|
360
|
+
if len(non_null) == 1:
|
|
361
|
+
return True, non_null[0]
|
|
362
|
+
return True, non_null
|
|
363
|
+
return False, avro_type
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def _is_discriminator_union(avro_type: AvroSchema) -> bool:
|
|
367
|
+
return (
|
|
368
|
+
isinstance(avro_type, list)
|
|
369
|
+
and bool(avro_type)
|
|
370
|
+
and all(isinstance(item, dict) and item.get("type") == "record" and item.get("jtdDiscriminator") for item in avro_type)
|
|
371
|
+
and len({item.get("jtdDiscriminator") for item in avro_type if isinstance(item, dict)}) == 1
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def _ensure_definition(self, key: str) -> None:
|
|
375
|
+
if key in self.definitions:
|
|
376
|
+
return
|
|
377
|
+
if key not in self.named_types:
|
|
378
|
+
return
|
|
379
|
+
if key in self.converting:
|
|
380
|
+
self.definitions[key] = {"ref": key}
|
|
381
|
+
return
|
|
382
|
+
self.definitions[key] = {}
|
|
383
|
+
try:
|
|
384
|
+
schema = self.named_types[key]
|
|
385
|
+
namespace = self._schema_namespace(schema)
|
|
386
|
+
if id(schema) in self.inline_named_schema_ids and schema.get("type") == "record":
|
|
387
|
+
self.definitions[key] = self._convert_record(schema, namespace)
|
|
388
|
+
elif id(schema) in self.inline_named_schema_ids and schema.get("type") == "enum":
|
|
389
|
+
self.definitions[key] = self._convert_enum(schema)
|
|
390
|
+
else:
|
|
391
|
+
self.definitions[key] = self._convert_non_nullable(schema, namespace)
|
|
392
|
+
except Exception:
|
|
393
|
+
self.definitions.pop(key, None)
|
|
394
|
+
raise
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def convert_avro_to_jtd(avro_schema_path: str, jtd_file_path: str, record_type: str | None = None) -> None:
|
|
398
|
+
"""Convert an Avrotize Schema file to a JSON Type Definition file."""
|
|
399
|
+
with open(avro_schema_path, "r", encoding="utf-8") as avro_file:
|
|
400
|
+
avro_schema = json.load(avro_file)
|
|
401
|
+
converter = AvroToJtdConverter(record_type=record_type)
|
|
402
|
+
jtd_schema = converter.convert(avro_schema)
|
|
403
|
+
with open(jtd_file_path, "w", encoding="utf-8") as jtd_file:
|
|
404
|
+
json.dump(jtd_schema, jtd_file, indent=2)
|