structurize 3.7.3__tar.gz → 3.7.4__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.3/structurize.egg-info → structurize-3.7.4}/PKG-INFO +1 -1
  2. {structurize-3.7.3 → structurize-3.7.4}/avrotize/_version.py +3 -3
  3. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jsonstostructure.py +169 -12
  4. {structurize-3.7.3 → structurize-3.7.4/structurize.egg-info}/PKG-INFO +1 -1
  5. {structurize-3.7.3 → structurize-3.7.4}/.gitignore +0 -0
  6. {structurize-3.7.3 → structurize-3.7.4}/LICENSE +0 -0
  7. {structurize-3.7.3 → structurize-3.7.4}/MANIFEST.in +0 -0
  8. {structurize-3.7.3 → structurize-3.7.4}/README.md +0 -0
  9. {structurize-3.7.3 → structurize-3.7.4}/avrotize/__init__.py +0 -0
  10. {structurize-3.7.3 → structurize-3.7.4}/avrotize/__main__.py +0 -0
  11. {structurize-3.7.3 → structurize-3.7.4}/avrotize/asn1toavro.py +0 -0
  12. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotize.py +0 -0
  13. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotocapnproto.py +0 -0
  14. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotocpp.py +0 -0
  15. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotocsharp.py +0 -0
  16. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotocsv.py +0 -0
  17. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotocue.py +0 -0
  18. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotodatapackage.py +0 -0
  19. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotodb.py +0 -0
  20. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoflatbuffers.py +0 -0
  21. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotogo.py +0 -0
  22. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotographql.py +0 -0
  23. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoiceberg.py +0 -0
  24. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotojava.py +0 -0
  25. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotojs.py +0 -0
  26. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotojsons.py +0 -0
  27. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotojstruct.py +0 -0
  28. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotojtd.py +0 -0
  29. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotokusto.py +0 -0
  30. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotomd.py +0 -0
  31. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotools.py +0 -0
  32. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoparquet.py +0 -0
  33. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoproto.py +0 -0
  34. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotopython.py +0 -0
  35. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoraml.py +0 -0
  36. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotorust.py +0 -0
  37. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotosmithy.py +0 -0
  38. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotosurreal.py +0 -0
  39. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotothrift.py +0 -0
  40. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotots.py +0 -0
  41. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrototsml.py +0 -0
  42. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrotoxsd.py +0 -0
  43. {structurize-3.7.3 → structurize-3.7.4}/avrotize/avrovalidator.py +0 -0
  44. {structurize-3.7.3 → structurize-3.7.4}/avrotize/capnprototoavro.py +0 -0
  45. {structurize-3.7.3 → structurize-3.7.4}/avrotize/cddltostructure.py +0 -0
  46. {structurize-3.7.3 → structurize-3.7.4}/avrotize/choice_inference.py +0 -0
  47. {structurize-3.7.3 → structurize-3.7.4}/avrotize/commands.json +0 -0
  48. {structurize-3.7.3 → structurize-3.7.4}/avrotize/common.py +0 -0
  49. {structurize-3.7.3 → structurize-3.7.4}/avrotize/constants.py +0 -0
  50. {structurize-3.7.3 → structurize-3.7.4}/avrotize/csvtoavro.py +0 -0
  51. {structurize-3.7.3 → structurize-3.7.4}/avrotize/cuetoavro.py +0 -0
  52. {structurize-3.7.3 → structurize-3.7.4}/avrotize/cuetostructure.py +0 -0
  53. {structurize-3.7.3 → structurize-3.7.4}/avrotize/datapackagetoavro.py +0 -0
  54. {structurize-3.7.3 → structurize-3.7.4}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
  55. {structurize-3.7.3 → structurize-3.7.4}/avrotize/dependencies/typescript/node22/package.json +0 -0
  56. {structurize-3.7.3 → structurize-3.7.4}/avrotize/dependency_resolver.py +0 -0
  57. {structurize-3.7.3 → structurize-3.7.4}/avrotize/dependency_version.py +0 -0
  58. {structurize-3.7.3 → structurize-3.7.4}/avrotize/flatbufferstoavro.py +0 -0
  59. {structurize-3.7.3 → structurize-3.7.4}/avrotize/flatbufferstojstruct.py +0 -0
  60. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jsonstoavro.py +0 -0
  61. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jsontoschema.py +0 -0
  62. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jstructtoavro.py +0 -0
  63. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jstructtoflatbuffers.py +0 -0
  64. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jstructtoraml.py +0 -0
  65. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jstructtosmithy.py +0 -0
  66. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jtdtoavro.py +0 -0
  67. {structurize-3.7.3 → structurize-3.7.4}/avrotize/jtdtostructure.py +0 -0
  68. {structurize-3.7.3 → structurize-3.7.4}/avrotize/kstructtoavro.py +0 -0
  69. {structurize-3.7.3 → structurize-3.7.4}/avrotize/kustotoavro.py +0 -0
  70. {structurize-3.7.3 → structurize-3.7.4}/avrotize/kustotojstruct.py +0 -0
  71. {structurize-3.7.3 → structurize-3.7.4}/avrotize/mcp_server.py +0 -0
  72. {structurize-3.7.3 → structurize-3.7.4}/avrotize/openapitostructure.py +0 -0
  73. {structurize-3.7.3 → structurize-3.7.4}/avrotize/parquettoavro.py +0 -0
  74. {structurize-3.7.3 → structurize-3.7.4}/avrotize/proto2parser.py +0 -0
  75. {structurize-3.7.3 → structurize-3.7.4}/avrotize/proto3parser.py +0 -0
  76. {structurize-3.7.3 → structurize-3.7.4}/avrotize/prototoavro.py +0 -0
  77. {structurize-3.7.3 → structurize-3.7.4}/avrotize/ramltoavro.py +0 -0
  78. {structurize-3.7.3 → structurize-3.7.4}/avrotize/ramltojstruct.py +0 -0
  79. {structurize-3.7.3 → structurize-3.7.4}/avrotize/schema_inference.py +0 -0
  80. {structurize-3.7.3 → structurize-3.7.4}/avrotize/smithytoavro.py +0 -0
  81. {structurize-3.7.3 → structurize-3.7.4}/avrotize/smithytojstruct.py +0 -0
  82. {structurize-3.7.3 → structurize-3.7.4}/avrotize/sqltoavro.py +0 -0
  83. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretocddl.py +0 -0
  84. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretocpp.py +0 -0
  85. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretocsharp.py +0 -0
  86. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretocsv.py +0 -0
  87. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretocue.py +0 -0
  88. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretodatapackage.py +0 -0
  89. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretodb.py +0 -0
  90. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretogo.py +0 -0
  91. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretographql.py +0 -0
  92. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretoiceberg.py +0 -0
  93. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretojava.py +0 -0
  94. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretojs.py +0 -0
  95. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretojsons.py +0 -0
  96. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretojtd.py +0 -0
  97. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretokusto.py +0 -0
  98. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretomd.py +0 -0
  99. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretoparquet.py +0 -0
  100. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretoproto.py +0 -0
  101. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretopython.py +0 -0
  102. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretorust.py +0 -0
  103. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretots.py +0 -0
  104. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretotsml.py +0 -0
  105. {structurize-3.7.3 → structurize-3.7.4}/avrotize/structuretoxsd.py +0 -0
  106. {structurize-3.7.3 → structurize-3.7.4}/avrotize/surrealtoavro.py +0 -0
  107. {structurize-3.7.3 → structurize-3.7.4}/avrotize/thriftstructure.py +0 -0
  108. {structurize-3.7.3 → structurize-3.7.4}/avrotize/thrifttoavro.py +0 -0
  109. {structurize-3.7.3 → structurize-3.7.4}/avrotize/tmslvalidate.py +0 -0
  110. {structurize-3.7.3 → structurize-3.7.4}/avrotize/validate.py +0 -0
  111. {structurize-3.7.3 → structurize-3.7.4}/avrotize/xmltoschema.py +0 -0
  112. {structurize-3.7.3 → structurize-3.7.4}/avrotize/xsdtoavro.py +0 -0
  113. {structurize-3.7.3 → structurize-3.7.4}/build.ps1 +0 -0
  114. {structurize-3.7.3 → structurize-3.7.4}/build.sh +0 -0
  115. {structurize-3.7.3 → structurize-3.7.4}/pyproject.toml +0 -0
  116. {structurize-3.7.3 → structurize-3.7.4}/setup.cfg +0 -0
  117. {structurize-3.7.3 → structurize-3.7.4}/structurize.egg-info/SOURCES.txt +0 -0
  118. {structurize-3.7.3 → structurize-3.7.4}/structurize.egg-info/dependency_links.txt +0 -0
  119. {structurize-3.7.3 → structurize-3.7.4}/structurize.egg-info/entry_points.txt +0 -0
  120. {structurize-3.7.3 → structurize-3.7.4}/structurize.egg-info/requires.txt +0 -0
  121. {structurize-3.7.3 → structurize-3.7.4}/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.3
3
+ Version: 3.7.4
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.3'
22
- __version_tuple__ = version_tuple = (3, 7, 3)
21
+ __version__ = version = '3.7.4'
22
+ __version_tuple__ = version_tuple = (3, 7, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g4eb36ac42'
24
+ __commit_id__ = commit_id = 'gd6893c97b'
@@ -331,6 +331,69 @@ class JsonToStructureConverter:
331
331
 
332
332
  return None
333
333
 
334
+ _STRING_FAMILY = {'string', 'uuid', 'date', 'time', 'datetime', 'duration', 'bytes'}
335
+ _NUMBER_FAMILY = {'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32',
336
+ 'uint64', 'int128', 'uint128', 'float', 'double', 'decimal',
337
+ 'number', 'integer'}
338
+
339
+ def _json_value_category(self, value: Any) -> Optional[str]:
340
+ """Classify a Python-decoded JSON value into a JSON Structure type family."""
341
+ if value is None:
342
+ return 'null'
343
+ if isinstance(value, bool):
344
+ return 'boolean'
345
+ if isinstance(value, (int, float)):
346
+ return 'number'
347
+ if isinstance(value, str):
348
+ return 'string'
349
+ return None
350
+
351
+ def _type_category(self, structure_type: str) -> Optional[str]:
352
+ """Map a scalar JSON Structure type name to its value family."""
353
+ if structure_type in self._STRING_FAMILY:
354
+ return 'string'
355
+ if structure_type in self._NUMBER_FAMILY:
356
+ return 'number'
357
+ if structure_type in ('boolean', 'null'):
358
+ return structure_type
359
+ return None
360
+
361
+ def _enum_type_for(self, structure_type: Any, enum_values: list) -> Any:
362
+ """Return the JSON Structure ``type`` that admits every enum value.
363
+
364
+ JSON Schema permits heterogeneous ``enum`` lists (e.g. a ``null`` or
365
+ boolean member alongside strings). JSON Structure requires the declared
366
+ type to admit each enum value, so widen a single scalar type to a
367
+ type-union covering the JSON families actually present. The declared type
368
+ is always preserved as a union member so string-family refinements such as
369
+ ``uuid`` are never lost, and ``null`` is floated to the front by
370
+ convention. If a value's family already matches the declared type (or the
371
+ declared type is not a recognised scalar), no widening occurs.
372
+ """
373
+ if not isinstance(structure_type, str):
374
+ return structure_type
375
+ base_cat = self._type_category(structure_type)
376
+ if base_cat is None:
377
+ return structure_type
378
+ canonical = {'null': 'null', 'boolean': 'boolean', 'string': 'string', 'number': 'int64'}
379
+ extras: list = []
380
+ for value in enum_values:
381
+ cat = self._json_value_category(value)
382
+ if cat is None or cat == base_cat:
383
+ continue
384
+ member = canonical[cat]
385
+ if member not in extras:
386
+ extras.append(member)
387
+ if not extras:
388
+ return structure_type
389
+ ordered: list = []
390
+ if 'null' in extras:
391
+ ordered.append('null')
392
+ extras = [e for e in extras if e != 'null']
393
+ ordered.append(structure_type)
394
+ ordered.extend(extras)
395
+ return ordered
396
+
334
397
  def json_schema_primitive_to_structure_type(self, json_primitive: Optional[str | list], format: Optional[str], enum: Optional[list], record_name: str, field_name: str, namespace: str, dependencies: list, schema: dict) -> str | dict[str, Any] | list:
335
398
  """
336
399
  Convert a JSON Schema primitive type to JSON Structure primitive type.
@@ -348,10 +411,11 @@ class JsonToStructureConverter:
348
411
 
349
412
  if isinstance(json_primitive, list):
350
413
  if enum:
351
- # Handle enum with multiple types (convert to string enum)
414
+ # Heterogeneous enum: widen the type to admit every value's family.
415
+ enum_values = list(enum)
352
416
  return {
353
- 'type': 'string',
354
- 'enum': list(enum)
417
+ 'type': self._enum_type_for('string', enum_values),
418
+ 'enum': enum_values
355
419
  }
356
420
  else:
357
421
  # Handle union types
@@ -419,7 +483,10 @@ class JsonToStructureConverter:
419
483
 
420
484
  # Handle enums
421
485
  if enum is not None:
422
- result['enum'] = list(enum)
486
+ enum_values = list(enum)
487
+ # Widen the declared type so every enum value's family is admitted.
488
+ result['type'] = self._enum_type_for(structure_type, enum_values)
489
+ result['enum'] = enum_values
423
490
 
424
491
  # Add constraints for string types
425
492
  if structure_type == 'string' and isinstance(schema, dict):
@@ -578,6 +645,28 @@ class JsonToStructureConverter:
578
645
  # propertyNames and keyNames and adds JSONStructureValidation to $uses
579
646
  pass
580
647
 
648
+ def _reconcile_required(self, required: list, emitted_properties: dict) -> list:
649
+ """Reconcile a JSON Schema ``required`` list with the emitted JSON Structure
650
+ property keys.
651
+
652
+ Property names that are not valid identifiers are normalised (the original
653
+ wire name is preserved via ``altnames``); the corresponding ``required``
654
+ entries must reference the same normalised identifier. Entries that do not
655
+ correspond to any emitted property - e.g. a source schema that lists a
656
+ required property it never defines under ``properties`` - are dropped so the
657
+ result validates against the JSON Structure Core metaschema.
658
+ """
659
+ if not required:
660
+ return []
661
+ reconciled: list = []
662
+ seen: set = set()
663
+ for name in required:
664
+ key = name if self.is_valid_identifier(name) else self.normalize_identifier(name)
665
+ if key in emitted_properties and key not in seen:
666
+ reconciled.append(key)
667
+ seen.add(key)
668
+ return reconciled
669
+
581
670
  def create_structure_object(self, properties: dict, required: list, record_name: str, namespace: str, dependencies: list, json_schema: dict, base_uri: str, structure_schema: dict, record_stack: list, recursion_depth: int = 1, original_schema: dict | None = None) -> dict:
582
671
  """
583
672
  Create a JSON Structure object type from properties.
@@ -601,9 +690,8 @@ class JsonToStructureConverter:
601
690
  'type': 'object'
602
691
  }
603
692
 
604
- # Add required field if it's not empty
605
- if required:
606
- structure_obj['required'] = required
693
+ # 'required' is reconciled against the emitted property keys after the
694
+ # property loop below (names may be normalised; dangling entries dropped).
607
695
 
608
696
  # Add name if provided
609
697
  if record_name:
@@ -633,7 +721,14 @@ class JsonToStructureConverter:
633
721
  new_entry['altnames'] = {'json': prop_name}
634
722
  structure_obj['properties'][normalized_name] = new_entry
635
723
  else:
636
- structure_obj['properties'][prop_name] = self._ensure_schema_object(prop_type, structure_schema, prop_name) # Handle patternProperties and additionalProperties
724
+ structure_obj['properties'][prop_name] = self._ensure_schema_object(prop_type, structure_schema, prop_name)
725
+
726
+ # Reconcile required names against the (possibly normalised) property keys.
727
+ reconciled_required = self._reconcile_required(required, structure_obj.get('properties', {}))
728
+ if reconciled_required:
729
+ structure_obj['required'] = reconciled_required
730
+
731
+ # Handle patternProperties and additionalProperties
637
732
  has_additional_schema = False
638
733
  if original_schema:
639
734
  # Check for patternProperties that coexist with properties/additionalProperties
@@ -804,7 +899,7 @@ class JsonToStructureConverter:
804
899
  choice_key = ref[14:] # Remove '#/definitions/' prefix
805
900
  else:
806
901
  choice_key = f"option_{i}"
807
- choice_obj['choices'][choice_key] = {'$ref': ref}
902
+ choice_obj['choices'][choice_key] = self._ensure_schema_object({'$ref': ref}, structure_schema, choice_key)
808
903
  else:
809
904
  # Convert option to structure type
810
905
  choice_key = f"option_{i}"
@@ -837,7 +932,8 @@ class JsonToStructureConverter:
837
932
  values_schema, record_name, 'value', namespace, dependencies,
838
933
  json_schema, base_uri, structure_schema, record_stack, recursion_depth + 1
839
934
  )
840
- # Always wrap as schema object values_type = self._ensure_schema_object(values_type, structure_schema, 'value')
935
+ # Always wrap as schema object
936
+ values_type = self._ensure_schema_object(values_type, structure_schema, 'value')
841
937
  return {
842
938
  'type': 'map',
843
939
  'values': values_type
@@ -992,8 +1088,12 @@ class JsonToStructureConverter:
992
1088
  }
993
1089
 
994
1090
  if required:
995
- result['required'] = required
996
-
1091
+ # Reconcile: drop required entries that reference no emitted property
1092
+ # so the result validates against the JSON Structure Core metaschema.
1093
+ reconciled = [n for n in required if n in structure_properties]
1094
+ if reconciled:
1095
+ result['required'] = reconciled
1096
+
997
1097
  return result
998
1098
 
999
1099
  def add_alternate_names(self, structure: dict, original_name: str) -> dict:
@@ -1915,6 +2015,48 @@ class JsonToStructureConverter:
1915
2015
  'discriminator' in def_schema):
1916
2016
  def_schema['abstract'] = True
1917
2017
 
2018
+ def _ensure_root_type(self, structure_schema: dict, json_schema: Any = None) -> None:
2019
+ """Ensure a definitions-only document nominates a root type.
2020
+
2021
+ A document whose only meaningful content is a library of reusable named
2022
+ types (e.g. an OpenAPI ``components/schemas`` bundle) has no single root
2023
+ instance type, yet the JSON Structure Core metaschema still requires the
2024
+ root to carry ``type``, ``$root`` or composition keywords. Following the
2025
+ spec's ``$root`` idiom (and the "Prohibition of Top-Level Unions" rule),
2026
+ point ``$root`` at:
2027
+ * the referenced type when the source root is a bare ``$ref``; else
2028
+ * the single definition when there is exactly one; else
2029
+ * a synthetic type-union over every top-level definition, so that no
2030
+ type is arbitrarily privileged.
2031
+ """
2032
+ if not isinstance(structure_schema, dict):
2033
+ return
2034
+ if 'type' in structure_schema or '$root' in structure_schema:
2035
+ return
2036
+ definitions = structure_schema.get('definitions')
2037
+ if not isinstance(definitions, dict) or not definitions:
2038
+ return
2039
+ # Prefer an explicit root $ref (the source root is a reference to one type).
2040
+ if (isinstance(json_schema, dict) and isinstance(json_schema.get('$ref'), str)
2041
+ and json_schema['$ref'].startswith('#/')):
2042
+ target = avro_name(json_schema['$ref'].split('/')[-1])
2043
+ if target in definitions:
2044
+ structure_schema['$root'] = f"#/definitions/{target}"
2045
+ return
2046
+ def_names = list(definitions.keys())
2047
+ if len(def_names) == 1:
2048
+ structure_schema['$root'] = f"#/definitions/{def_names[0]}"
2049
+ return
2050
+ # Synthesize a type-union root over all top-level definitions.
2051
+ root_name = avro_name(self.root_class_name)
2052
+ while root_name in definitions:
2053
+ root_name = f"{root_name}_root"
2054
+ definitions[root_name] = {
2055
+ 'name': root_name,
2056
+ 'type': [{'$ref': f"#/definitions/{name}"} for name in def_names]
2057
+ }
2058
+ structure_schema['$root'] = f"#/definitions/{root_name}"
2059
+
1918
2060
  def jsons_to_structure(self, json_schema: Union[dict, list], namespace: str, base_uri: str) -> dict:
1919
2061
  """
1920
2062
  Convert a JSON Schema to JSON Structure format.
@@ -2045,6 +2187,9 @@ class JsonToStructureConverter:
2045
2187
  # Generate default $id for non-dict schemas
2046
2188
  structure_schema['$id'] = f"https://example.com/{namespace.replace('.', '/')}.schema.json"
2047
2189
 
2190
+ # Nominate a root type for definitions-only documents (type libraries).
2191
+ self._ensure_root_type(structure_schema, json_schema)
2192
+
2048
2193
  # Mark abstract types
2049
2194
  self._mark_abstract_types(structure_schema)
2050
2195
 
@@ -2201,6 +2346,18 @@ class JsonToStructureConverter:
2201
2346
  # Handle additionalProperties
2202
2347
  if 'additionalProperties' in structure_type and isinstance(structure_type['additionalProperties'], dict):
2203
2348
  structure_type['additionalProperties'] = self._validate_and_fix_json_structure_type(structure_type['additionalProperties'])
2349
+
2350
+ # Ensure choice branches are schema objects with wrapped refs
2351
+ if structure_type.get('type') == 'choice' and isinstance(structure_type.get('choices'), dict):
2352
+ fixed_choices = {}
2353
+ for choice_key, choice_val in structure_type['choices'].items():
2354
+ if isinstance(choice_val, dict) and '$ref' in choice_val and 'type' not in choice_val:
2355
+ choice_val = {'type': {'$ref': choice_val['$ref']}}
2356
+ fixed_choices[choice_key] = (
2357
+ self._validate_and_fix_json_structure_type(choice_val)
2358
+ if isinstance(choice_val, dict) else choice_val
2359
+ )
2360
+ structure_type['choices'] = fixed_choices
2204
2361
 
2205
2362
  # Handle definitions
2206
2363
  if 'definitions' in structure_type and isinstance(structure_type['definitions'], dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 3.7.3
3
+ Version: 3.7.4
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes