structurize 2.20.5__py3-none-any.whl → 2.21.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avrotize/_version.py +3 -3
- avrotize/avrotopython.py +4 -2
- avrotize/structuretocsharp.py +2 -2
- avrotize/structuretogo.py +9 -0
- avrotize/structuretojava.py +86 -1
- avrotize/structuretopython.py +41 -14
- avrotize/structuretots.py +86 -24
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/METADATA +1 -1
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/RECORD +13 -13
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/WHEEL +0 -0
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/entry_points.txt +0 -0
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/licenses/LICENSE +0 -0
- {structurize-2.20.5.dist-info → structurize-2.21.1.dist-info}/top_level.txt +0 -0
avrotize/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.
|
|
32
|
-
__version_tuple__ = version_tuple = (2,
|
|
31
|
+
__version__ = version = '2.21.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 21, 1)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g9b9841dd3'
|
avrotize/avrotopython.py
CHANGED
|
@@ -327,7 +327,8 @@ class AvroToPython:
|
|
|
327
327
|
def generate_test_class(self, package_name: str, class_name: str, fields: List[Dict[str, str]], import_types: Set[str]) -> None:
|
|
328
328
|
"""Generates a unit test class for a Python data class"""
|
|
329
329
|
test_class_name = f"Test_{class_name}"
|
|
330
|
-
|
|
330
|
+
flat_package = package_name.replace('.', '_').lower()
|
|
331
|
+
tests_package_name = flat_package if flat_package.startswith('test_') else f"test_{flat_package}"
|
|
331
332
|
test_class_definition = process_template(
|
|
332
333
|
"avrotopython/test_class.jinja",
|
|
333
334
|
package_name=package_name,
|
|
@@ -348,7 +349,8 @@ class AvroToPython:
|
|
|
348
349
|
def generate_test_enum(self, package_name: str, class_name: str, symbols: List[str]) -> None:
|
|
349
350
|
"""Generates a unit test class for a Python enum"""
|
|
350
351
|
test_class_name = f"Test_{class_name}"
|
|
351
|
-
|
|
352
|
+
flat_package = package_name.replace('.', '_').lower()
|
|
353
|
+
tests_package_name = flat_package if flat_package.startswith('test_') else f"test_{flat_package}"
|
|
352
354
|
test_class_definition = process_template(
|
|
353
355
|
"avrotopython/test_enum.jinja",
|
|
354
356
|
package_name=package_name,
|
avrotize/structuretocsharp.py
CHANGED
|
@@ -2072,8 +2072,8 @@ class StructureToCSharp:
|
|
|
2072
2072
|
if enum_values:
|
|
2073
2073
|
for value in enum_values:
|
|
2074
2074
|
if isinstance(value, str):
|
|
2075
|
-
# Convert to PascalCase enum member name
|
|
2076
|
-
symbol_name =
|
|
2075
|
+
# Convert to PascalCase enum member name - must match generate_enum logic
|
|
2076
|
+
symbol_name = pascal(str(value).replace('-', '_').replace(' ', '_'))
|
|
2077
2077
|
symbols.append(symbol_name)
|
|
2078
2078
|
else:
|
|
2079
2079
|
# For numeric enums, use Value1, Value2, etc.
|
avrotize/structuretogo.py
CHANGED
|
@@ -523,6 +523,10 @@ class StructureToGo:
|
|
|
523
523
|
v = f'float64({random.uniform(-100, 100)})'
|
|
524
524
|
elif go_type == '[]byte':
|
|
525
525
|
v = '[]byte("' + ''.join(random.choices(string.ascii_letters + string.digits, k=10)) + '")'
|
|
526
|
+
elif go_type == 'time.Time':
|
|
527
|
+
v = 'time.Now()'
|
|
528
|
+
elif go_type == 'time.Duration':
|
|
529
|
+
v = 'time.Hour'
|
|
526
530
|
elif go_type.startswith('[]'):
|
|
527
531
|
inner_type = go_type[2:]
|
|
528
532
|
v = f'{go_type}{{{self.random_value(inner_type)}}}'
|
|
@@ -549,10 +553,15 @@ class StructureToGo:
|
|
|
549
553
|
'enums': self.enums,
|
|
550
554
|
'base_package': self.base_package,
|
|
551
555
|
}
|
|
556
|
+
needs_time_import = False
|
|
552
557
|
for struct in context['structs']:
|
|
553
558
|
for field in struct['fields']:
|
|
554
559
|
if 'value' not in field:
|
|
555
560
|
field['value'] = self.random_value(field['type'])
|
|
561
|
+
# Check if time package is needed
|
|
562
|
+
if 'time.Time' in field['type'] or 'time.Duration' in field['type']:
|
|
563
|
+
needs_time_import = True
|
|
564
|
+
context['needs_time_import'] = needs_time_import
|
|
556
565
|
helpers_file_name = os.path.join(self.output_dir, 'pkg', self.base_package, f"{self.base_package}_helpers.go")
|
|
557
566
|
render_template('structuretogo/go_helpers.jinja', helpers_file_name, **context)
|
|
558
567
|
|
avrotize/structuretojava.py
CHANGED
|
@@ -384,7 +384,12 @@ class StructureToJava:
|
|
|
384
384
|
field_count=len(field_names)
|
|
385
385
|
)
|
|
386
386
|
|
|
387
|
-
|
|
387
|
+
# Generate createTestInstance() method for testing (only for non-abstract classes)
|
|
388
|
+
create_test_instance = ''
|
|
389
|
+
if not is_abstract:
|
|
390
|
+
create_test_instance = self.generate_create_test_instance_method(class_name, fields, schema_namespace)
|
|
391
|
+
|
|
392
|
+
class_definition = class_definition.rstrip() + create_test_instance + equals_hashcode + "\n}\n"
|
|
388
393
|
|
|
389
394
|
if write_file:
|
|
390
395
|
self.write_to_file(package, class_name, class_definition)
|
|
@@ -455,6 +460,86 @@ class StructureToJava:
|
|
|
455
460
|
return str(value)
|
|
456
461
|
return f"/* unsupported const value */"
|
|
457
462
|
|
|
463
|
+
def get_test_value(self, java_type: str) -> str:
|
|
464
|
+
""" Get a test value for a Java type """
|
|
465
|
+
# Handle arrays/lists
|
|
466
|
+
if java_type.startswith("List<") or java_type.startswith("ArrayList<"):
|
|
467
|
+
return "new java.util.ArrayList<>()"
|
|
468
|
+
if java_type.startswith("Map<"):
|
|
469
|
+
return "new java.util.HashMap<>()"
|
|
470
|
+
if java_type.endswith("[]"):
|
|
471
|
+
return f"new {java_type[:-2]}[0]"
|
|
472
|
+
|
|
473
|
+
# Primitive test values
|
|
474
|
+
test_values = {
|
|
475
|
+
'String': '"test-string"',
|
|
476
|
+
'string': '"test-string"',
|
|
477
|
+
'int': '42',
|
|
478
|
+
'Integer': '42',
|
|
479
|
+
'long': '42L',
|
|
480
|
+
'Long': '42L',
|
|
481
|
+
'double': '3.14',
|
|
482
|
+
'Double': '3.14',
|
|
483
|
+
'float': '3.14f',
|
|
484
|
+
'Float': '3.14f',
|
|
485
|
+
'boolean': 'true',
|
|
486
|
+
'Boolean': 'true',
|
|
487
|
+
'byte': '(byte)0',
|
|
488
|
+
'Byte': '(byte)0',
|
|
489
|
+
'short': '(short)0',
|
|
490
|
+
'Short': '(short)0',
|
|
491
|
+
'BigInteger': 'java.math.BigInteger.ZERO',
|
|
492
|
+
'BigDecimal': 'java.math.BigDecimal.ZERO',
|
|
493
|
+
'byte[]': 'new byte[0]',
|
|
494
|
+
'LocalDate': 'java.time.LocalDate.now()',
|
|
495
|
+
'LocalTime': 'java.time.LocalTime.now()',
|
|
496
|
+
'Instant': 'java.time.Instant.now()',
|
|
497
|
+
'Duration': 'java.time.Duration.ZERO',
|
|
498
|
+
'UUID': 'java.util.UUID.randomUUID()',
|
|
499
|
+
'URI': 'java.net.URI.create("http://example.com")',
|
|
500
|
+
'Object': 'new Object()',
|
|
501
|
+
'void': 'null',
|
|
502
|
+
'Void': 'null',
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if java_type in test_values:
|
|
506
|
+
return test_values[java_type]
|
|
507
|
+
|
|
508
|
+
# Check if it's a generated type (enum or class)
|
|
509
|
+
if java_type in self.generated_types_java_package:
|
|
510
|
+
type_kind = self.generated_types_java_package[java_type]
|
|
511
|
+
if type_kind == "enum":
|
|
512
|
+
return f'{java_type}.values()[0]'
|
|
513
|
+
elif type_kind == "class":
|
|
514
|
+
return f'{java_type}.createTestInstance()'
|
|
515
|
+
|
|
516
|
+
# Default: try to instantiate
|
|
517
|
+
return f'new {java_type}()'
|
|
518
|
+
|
|
519
|
+
def generate_create_test_instance_method(self, class_name: str, fields: List[Dict], parent_package: str) -> str:
|
|
520
|
+
""" Generates a static createTestInstance method that creates a fully initialized instance """
|
|
521
|
+
method = f"\n{INDENT}/**\n{INDENT} * Creates a test instance with all required fields populated\n{INDENT} * @return a fully initialized test instance\n{INDENT} */\n"
|
|
522
|
+
method += f"{INDENT}public static {class_name} createTestInstance() {{\n"
|
|
523
|
+
method += f"{INDENT*2}{class_name} instance = new {class_name}();\n"
|
|
524
|
+
|
|
525
|
+
for field in fields:
|
|
526
|
+
# Skip const fields
|
|
527
|
+
if field.get('is_const', False):
|
|
528
|
+
continue
|
|
529
|
+
|
|
530
|
+
field_name = field['name']
|
|
531
|
+
field_type = field['type']
|
|
532
|
+
|
|
533
|
+
# Get a test value for this field
|
|
534
|
+
test_value = self.get_test_value(field_type)
|
|
535
|
+
|
|
536
|
+
# Setter name: set{Pascal(field_name)}
|
|
537
|
+
method += f"{INDENT*2}instance.set{pascal(field_name)}({test_value});\n"
|
|
538
|
+
|
|
539
|
+
method += f"{INDENT*2}return instance;\n"
|
|
540
|
+
method += f"{INDENT}}}\n"
|
|
541
|
+
return method
|
|
542
|
+
|
|
458
543
|
def generate_enum(self, structure_schema: Dict, field_name: str, parent_package: str, write_file: bool) -> JavaType:
|
|
459
544
|
""" Generates a Java enum from JSON Structure enum schema """
|
|
460
545
|
|
avrotize/structuretopython.py
CHANGED
|
@@ -39,6 +39,7 @@ class StructureToPython:
|
|
|
39
39
|
self.schema_doc: JsonNode = None
|
|
40
40
|
self.generated_types: Dict[str, str] = {}
|
|
41
41
|
self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
|
|
42
|
+
self.generated_enum_symbols: Dict[str, List[str]] = {}
|
|
42
43
|
self.type_dict: Dict[str, Dict] = {}
|
|
43
44
|
self.definitions: Dict[str, Any] = {}
|
|
44
45
|
self.schema_registry: Dict[str, Dict] = {}
|
|
@@ -285,6 +286,7 @@ class StructureToPython:
|
|
|
285
286
|
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
|
|
286
287
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
287
288
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
289
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
288
290
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
289
291
|
|
|
290
292
|
if python_qualified_name in self.generated_types:
|
|
@@ -357,8 +359,8 @@ class StructureToPython:
|
|
|
357
359
|
)
|
|
358
360
|
|
|
359
361
|
if write_file:
|
|
360
|
-
self.write_to_file(
|
|
361
|
-
self.generate_test_class(
|
|
362
|
+
self.write_to_file(package_name, class_name, class_definition)
|
|
363
|
+
self.generate_test_class(package_name, class_name, field_docstrings, import_types)
|
|
362
364
|
|
|
363
365
|
self.generated_types[python_qualified_name] = 'class'
|
|
364
366
|
self.generated_structure_types[python_qualified_name] = structure_schema
|
|
@@ -423,6 +425,7 @@ class StructureToPython:
|
|
|
423
425
|
class_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
424
426
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
425
427
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
428
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
426
429
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
427
430
|
|
|
428
431
|
if python_qualified_name in self.generated_types:
|
|
@@ -441,10 +444,11 @@ class StructureToPython:
|
|
|
441
444
|
)
|
|
442
445
|
|
|
443
446
|
if write_file:
|
|
444
|
-
self.write_to_file(
|
|
445
|
-
self.generate_test_enum(
|
|
447
|
+
self.write_to_file(package_name, class_name, enum_definition)
|
|
448
|
+
self.generate_test_enum(package_name, class_name, symbols)
|
|
446
449
|
|
|
447
450
|
self.generated_types[python_qualified_name] = 'enum'
|
|
451
|
+
self.generated_enum_symbols[python_qualified_name] = symbols
|
|
448
452
|
return python_qualified_name
|
|
449
453
|
|
|
450
454
|
def generate_choice(self, structure_schema: Dict, parent_namespace: str,
|
|
@@ -511,6 +515,7 @@ class StructureToPython:
|
|
|
511
515
|
class_name = pascal(structure_schema.get('name', 'UnnamedMap'))
|
|
512
516
|
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
513
517
|
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
518
|
+
package_name = self.python_package_from_structure_type(schema_namespace, class_name)
|
|
514
519
|
python_qualified_name = self.python_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
515
520
|
|
|
516
521
|
if python_qualified_name in self.generated_types:
|
|
@@ -535,7 +540,7 @@ class StructureToPython:
|
|
|
535
540
|
)
|
|
536
541
|
|
|
537
542
|
if write_file:
|
|
538
|
-
self.write_to_file(
|
|
543
|
+
self.write_to_file(package_name, class_name, map_definition)
|
|
539
544
|
|
|
540
545
|
self.generated_types[python_qualified_name] = 'map'
|
|
541
546
|
return python_qualified_name
|
|
@@ -596,7 +601,24 @@ class StructureToPython:
|
|
|
596
601
|
elif field_type.startswith('typing.Union['):
|
|
597
602
|
field_type = resolve(field_type)
|
|
598
603
|
return generate_value(field_type)
|
|
599
|
-
|
|
604
|
+
if field_type in test_values:
|
|
605
|
+
return test_values[field_type]
|
|
606
|
+
# Check if this is an enum type - use first symbol value
|
|
607
|
+
# Look up by fully qualified name or by short name (class name only)
|
|
608
|
+
enum_symbols = None
|
|
609
|
+
if field_type in self.generated_enum_symbols:
|
|
610
|
+
enum_symbols = self.generated_enum_symbols[field_type]
|
|
611
|
+
else:
|
|
612
|
+
# Try to find by short name (the field type might be just the class name)
|
|
613
|
+
for qualified_name, symbols in self.generated_enum_symbols.items():
|
|
614
|
+
if qualified_name.endswith('.' + field_type) or qualified_name == field_type:
|
|
615
|
+
enum_symbols = symbols
|
|
616
|
+
break
|
|
617
|
+
if enum_symbols:
|
|
618
|
+
return f"{field_type.split('.')[-1]}.{enum_symbols[0]}"
|
|
619
|
+
# For complex types, use None since fields are typically optional
|
|
620
|
+
# This avoids needing to construct nested objects with required args
|
|
621
|
+
return 'None'
|
|
600
622
|
|
|
601
623
|
return generate_value(field_type)
|
|
602
624
|
|
|
@@ -604,7 +626,8 @@ class StructureToPython:
|
|
|
604
626
|
import_types: Set[str]) -> None:
|
|
605
627
|
"""Generates a unit test class for a Python dataclass"""
|
|
606
628
|
test_class_name = f"Test_{class_name}"
|
|
607
|
-
|
|
629
|
+
# Use a simpler file naming scheme based on class name only
|
|
630
|
+
test_file_name = f"test_{class_name.lower()}"
|
|
608
631
|
test_class_definition = process_template(
|
|
609
632
|
"structuretopython/test_class.jinja",
|
|
610
633
|
package_name=package_name,
|
|
@@ -617,7 +640,7 @@ class StructureToPython:
|
|
|
617
640
|
)
|
|
618
641
|
|
|
619
642
|
base_dir = os.path.join(self.output_dir, "tests")
|
|
620
|
-
test_file_path = os.path.join(base_dir, f"{
|
|
643
|
+
test_file_path = os.path.join(base_dir, f"{test_file_name}.py")
|
|
621
644
|
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
622
645
|
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
623
646
|
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
@@ -626,7 +649,8 @@ class StructureToPython:
|
|
|
626
649
|
def generate_test_enum(self, package_name: str, class_name: str, symbols: List[str]) -> None:
|
|
627
650
|
"""Generates a unit test class for a Python enum"""
|
|
628
651
|
test_class_name = f"Test_{class_name}"
|
|
629
|
-
|
|
652
|
+
# Use a simpler file naming scheme based on class name only
|
|
653
|
+
test_file_name = f"test_{class_name.lower()}"
|
|
630
654
|
test_class_definition = process_template(
|
|
631
655
|
"structuretopython/test_enum.jinja",
|
|
632
656
|
package_name=package_name,
|
|
@@ -635,7 +659,7 @@ class StructureToPython:
|
|
|
635
659
|
symbols=symbols
|
|
636
660
|
)
|
|
637
661
|
base_dir = os.path.join(self.output_dir, "tests")
|
|
638
|
-
test_file_path = os.path.join(base_dir, f"{
|
|
662
|
+
test_file_path = os.path.join(base_dir, f"{test_file_name}.py")
|
|
639
663
|
if not os.path.exists(os.path.dirname(test_file_path)):
|
|
640
664
|
os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
|
|
641
665
|
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
@@ -643,9 +667,8 @@ class StructureToPython:
|
|
|
643
667
|
|
|
644
668
|
def write_to_file(self, package: str, class_name: str, python_code: str):
|
|
645
669
|
"""Writes a Python class to a file"""
|
|
646
|
-
#
|
|
647
|
-
|
|
648
|
-
parent_package_name = '.'.join(full_package.split('.')[:-1])
|
|
670
|
+
# The containing directory is the parent package (matches avrotopython.py)
|
|
671
|
+
parent_package_name = '.'.join(package.split('.')[:-1])
|
|
649
672
|
parent_package_path = os.sep.join(parent_package_name.split('.')).lower()
|
|
650
673
|
directory_path = os.path.join(self.output_dir, "src", parent_package_path)
|
|
651
674
|
if not os.path.exists(directory_path):
|
|
@@ -758,7 +781,11 @@ class StructureToPython:
|
|
|
758
781
|
def convert_structure_to_python(structure_schema_path, py_file_path, package_name='', dataclasses_json_annotation=False, avro_annotation=False):
|
|
759
782
|
"""Converts JSON Structure schema to Python dataclasses"""
|
|
760
783
|
if not package_name:
|
|
761
|
-
|
|
784
|
+
# Strip .json extension, then also strip .struct suffix if present (*.struct.json naming convention)
|
|
785
|
+
base_name = os.path.splitext(os.path.basename(structure_schema_path))[0]
|
|
786
|
+
if base_name.endswith('.struct'):
|
|
787
|
+
base_name = base_name[:-7] # Remove '.struct' suffix
|
|
788
|
+
package_name = base_name.lower().replace('-', '_')
|
|
762
789
|
|
|
763
790
|
structure_to_python = StructureToPython(package_name, dataclasses_json_annotation=dataclasses_json_annotation, avro_annotation=avro_annotation)
|
|
764
791
|
structure_to_python.convert(structure_schema_path, py_file_path)
|
avrotize/structuretots.py
CHANGED
|
@@ -332,15 +332,25 @@ class StructureToTypeScript:
|
|
|
332
332
|
class_name, prop_name, prop_schema, namespace, import_types)
|
|
333
333
|
is_required = prop_name in required_props
|
|
334
334
|
is_optional = not is_required
|
|
335
|
+
field_type_no_null = self.strip_nullable(field_type)
|
|
336
|
+
|
|
337
|
+
# Check if the field type is an enum
|
|
338
|
+
is_enum = False
|
|
339
|
+
for import_type in import_types:
|
|
340
|
+
if import_type.endswith('.' + field_type_no_null) or import_type == field_type_no_null:
|
|
341
|
+
if import_type in self.generated_types and self.generated_types[import_type] == 'enum':
|
|
342
|
+
is_enum = True
|
|
343
|
+
break
|
|
335
344
|
|
|
336
345
|
fields.append({
|
|
337
346
|
'name': self.safe_name(prop_name),
|
|
338
347
|
'original_name': prop_name,
|
|
339
348
|
'type': field_type,
|
|
340
|
-
'type_no_null':
|
|
349
|
+
'type_no_null': field_type_no_null,
|
|
341
350
|
'is_required': is_required,
|
|
342
351
|
'is_optional': is_optional,
|
|
343
|
-
'is_primitive': self.is_typescript_primitive(
|
|
352
|
+
'is_primitive': self.is_typescript_primitive(field_type_no_null.replace('[]', '')),
|
|
353
|
+
'is_enum': is_enum,
|
|
344
354
|
'docstring': prop_schema.get('description', '') if isinstance(prop_schema, dict) else ''
|
|
345
355
|
})
|
|
346
356
|
|
|
@@ -359,6 +369,11 @@ class StructureToTypeScript:
|
|
|
359
369
|
relative_import_path = f'./{relative_import_path}'
|
|
360
370
|
imports_with_paths[import_type_name] = relative_import_path + '.js'
|
|
361
371
|
|
|
372
|
+
# Prepare required fields with test values for createInstance()
|
|
373
|
+
required_fields = [f for f in fields if f.get('is_required', not f.get('is_optional', False))]
|
|
374
|
+
for field in required_fields:
|
|
375
|
+
field['test_value'] = self.generate_test_value(field)
|
|
376
|
+
|
|
362
377
|
# Generate class definition using template
|
|
363
378
|
class_definition = process_template(
|
|
364
379
|
"structuretots/class_core.ts.jinja",
|
|
@@ -368,6 +383,7 @@ class StructureToTypeScript:
|
|
|
368
383
|
is_abstract=is_abstract,
|
|
369
384
|
docstring=structure_schema.get('description', '').strip() if 'description' in structure_schema else f'A {class_name} class.',
|
|
370
385
|
fields=fields,
|
|
386
|
+
required_fields=required_fields,
|
|
371
387
|
imports=imports_with_paths,
|
|
372
388
|
typedjson_annotation=self.typedjson_annotation,
|
|
373
389
|
)
|
|
@@ -384,8 +400,10 @@ class StructureToTypeScript:
|
|
|
384
400
|
write_file: bool = True) -> str:
|
|
385
401
|
""" Generates a TypeScript enum from JSON Structure enum """
|
|
386
402
|
enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
387
|
-
|
|
388
|
-
|
|
403
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
404
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
405
|
+
# Use schema_namespace (not parent_namespace) to match the file location
|
|
406
|
+
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(schema_namespace, enum_name)
|
|
389
407
|
|
|
390
408
|
if typescript_qualified_name in self.generated_types:
|
|
391
409
|
return typescript_qualified_name
|
|
@@ -522,17 +540,36 @@ class StructureToTypeScript:
|
|
|
522
540
|
if field_type.startswith('{ [key: string]:'):
|
|
523
541
|
return '{}'
|
|
524
542
|
|
|
525
|
-
#
|
|
526
|
-
|
|
543
|
+
# Handle enums - use first value with Object.values()
|
|
544
|
+
if field.get('is_enum', False):
|
|
545
|
+
return f'Object.values({field_type})[0] as {field_type}'
|
|
546
|
+
|
|
547
|
+
# Return test value for primitives, or call createInstance() for complex types (classes)
|
|
548
|
+
return test_values.get(field_type, f'{field_type}.createInstance()')
|
|
527
549
|
|
|
528
550
|
def generate_test_class(self, namespace: str, class_name: str, fields: List[Dict[str, Any]]) -> None:
|
|
529
551
|
"""Generates a unit test class for a TypeScript class"""
|
|
530
552
|
# Get only required fields for the test
|
|
531
553
|
required_fields = [f for f in fields if f['is_required']]
|
|
532
554
|
|
|
555
|
+
# Collect enum imports needed for test file
|
|
556
|
+
enum_imports: Dict[str, str] = {}
|
|
557
|
+
|
|
533
558
|
# Generate test values for required fields
|
|
534
559
|
for field in required_fields:
|
|
535
560
|
field['test_value'] = self.generate_test_value(field)
|
|
561
|
+
# Check if this field is an enum and needs an import
|
|
562
|
+
if field.get('is_enum', False):
|
|
563
|
+
enum_type = field['type_no_null']
|
|
564
|
+
# Find the enum in generated_types to get its full path
|
|
565
|
+
for qualified_name, type_kind in self.generated_types.items():
|
|
566
|
+
if type_kind == 'enum' and qualified_name.endswith('.' + enum_type):
|
|
567
|
+
# Build import path - lowercase namespace like write_to_file does
|
|
568
|
+
parts = qualified_name.split('.')
|
|
569
|
+
enum_namespace = '.'.join(parts[:-1]).lower()
|
|
570
|
+
enum_import_path = enum_namespace.replace('.', '/') + '/' + enum_type
|
|
571
|
+
enum_imports[enum_type] = f'../src/{enum_import_path}'
|
|
572
|
+
break
|
|
536
573
|
|
|
537
574
|
# Determine relative path from test directory to src
|
|
538
575
|
namespace_path = namespace.replace('.', '/') if namespace else ''
|
|
@@ -545,7 +582,8 @@ class StructureToTypeScript:
|
|
|
545
582
|
"structuretots/test_class.ts.jinja",
|
|
546
583
|
class_name=class_name,
|
|
547
584
|
required_fields=required_fields,
|
|
548
|
-
relative_path=relative_path
|
|
585
|
+
relative_path=relative_path,
|
|
586
|
+
enum_imports=enum_imports
|
|
549
587
|
)
|
|
550
588
|
|
|
551
589
|
# Write test file
|
|
@@ -589,17 +627,30 @@ class StructureToTypeScript:
|
|
|
589
627
|
f.write(gitignore)
|
|
590
628
|
|
|
591
629
|
def generate_index(self) -> None:
|
|
592
|
-
""" Generates index.ts that exports all generated types """
|
|
630
|
+
""" Generates index.ts that exports all generated types with aliased exports """
|
|
593
631
|
exports = []
|
|
594
632
|
for qualified_name, type_kind in self.generated_types.items():
|
|
595
|
-
|
|
596
|
-
|
|
633
|
+
# Split the qualified_name into parts
|
|
634
|
+
parts = qualified_name.split('.')
|
|
635
|
+
type_name = parts[-1] # The actual type name
|
|
636
|
+
namespace = '.'.join(parts[:-1]) # The namespace excluding the type
|
|
637
|
+
|
|
638
|
+
# Construct the relative path to the .js file
|
|
597
639
|
if namespace:
|
|
598
640
|
# Lowercase the namespace to match the directory structure created by write_to_file
|
|
599
641
|
relative_path = namespace.lower().replace('.', '/') + '/' + type_name
|
|
600
642
|
else:
|
|
601
643
|
relative_path = type_name
|
|
602
|
-
|
|
644
|
+
|
|
645
|
+
if not relative_path.startswith('./'):
|
|
646
|
+
relative_path = './' + relative_path
|
|
647
|
+
|
|
648
|
+
# Construct the alias name by joining all parts with underscores (PascalCase)
|
|
649
|
+
alias_parts = [pascal(part) for part in parts]
|
|
650
|
+
alias_name = '_'.join(alias_parts)
|
|
651
|
+
|
|
652
|
+
# Generate the export statement with alias (like avrotots does)
|
|
653
|
+
exports.append(f"export {{ {type_name} as {alias_name} }} from '{relative_path}.js';")
|
|
603
654
|
|
|
604
655
|
index_content = '\n'.join(exports) + '\n' if exports else ''
|
|
605
656
|
|
|
@@ -619,23 +670,34 @@ class StructureToTypeScript:
|
|
|
619
670
|
self.convert_schema(schema, output_dir, package_name)
|
|
620
671
|
|
|
621
672
|
def convert_schema(self, schema: JsonNode, output_dir: str, package_name: str = '') -> None:
|
|
622
|
-
""" Converts a JSON Structure schema to TypeScript classes """
|
|
673
|
+
""" Converts a JSON Structure schema (or list of schemas) to TypeScript classes """
|
|
674
|
+
# Normalize to list
|
|
675
|
+
if not isinstance(schema, list):
|
|
676
|
+
schema = [schema]
|
|
677
|
+
|
|
623
678
|
self.output_dir = output_dir
|
|
624
679
|
self.schema_doc = schema
|
|
625
680
|
|
|
626
|
-
# Register schema IDs
|
|
627
|
-
|
|
681
|
+
# Register schema IDs for all schemas
|
|
682
|
+
for s in schema:
|
|
683
|
+
if isinstance(s, dict):
|
|
684
|
+
self.register_schema_ids(s)
|
|
628
685
|
|
|
629
|
-
# Process
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
686
|
+
# Process each schema
|
|
687
|
+
for s in schema:
|
|
688
|
+
if not isinstance(s, dict):
|
|
689
|
+
continue
|
|
690
|
+
|
|
691
|
+
# Process definitions
|
|
692
|
+
if 'definitions' in s:
|
|
693
|
+
for def_name, def_schema in s['definitions'].items():
|
|
694
|
+
if isinstance(def_schema, dict):
|
|
695
|
+
self.generate_class_or_choice(def_schema, '', write_file=True, explicit_name=def_name)
|
|
696
|
+
|
|
697
|
+
# Process root schema if it's an object or choice
|
|
698
|
+
if 'type' in s:
|
|
699
|
+
root_namespace = s.get('namespace', '')
|
|
700
|
+
self.generate_class_or_choice(s, root_namespace, write_file=True)
|
|
639
701
|
|
|
640
702
|
# Generate project files
|
|
641
703
|
self.generate_package_json(package_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.21.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
avrotize/__init__.py,sha256=t5h5wkHXr6M0mmHAB5rhjZ3Gxy9xutGTGIfojfao9rI,3820
|
|
2
2
|
avrotize/__main__.py,sha256=5pY8dYAURcOnFRvgb6fgaOIa_SOzPLIWbU8-ZTQ0jG4,88
|
|
3
|
-
avrotize/_version.py,sha256=
|
|
3
|
+
avrotize/_version.py,sha256=OG0OetWko5QnyYEYqpKY6elpmzM-s46bO3p-pMIncRc,714
|
|
4
4
|
avrotize/asn1toavro.py,sha256=QDNwfBfXMxSH-k487CA3CaGCGDzOLs4PpVbbENm5uF0,8386
|
|
5
5
|
avrotize/avrotize.py,sha256=VHFpBltMVBpyt0ju3ZWW725BKjQ4Fk-nrAy8udW-X44,5713
|
|
6
6
|
avrotize/avrotocpp.py,sha256=hRZV247_TDD7Sm6_8sFx-UH5SueLLx2Wg6TvAVUX0iE,25693
|
|
@@ -20,7 +20,7 @@ avrotize/avrotomd.py,sha256=WHPHnfmkI3xDNIHKZ3ReYxj6tib1eCny3JOznNSN6r8,5348
|
|
|
20
20
|
avrotize/avrotools.py,sha256=dTbGgWQyKdSuvCf4yuoymwhYO5gX9ywPu-klIXYwKZM,6052
|
|
21
21
|
avrotize/avrotoparquet.py,sha256=qm5hfia5elW1Yn4KACG8bbudLAqQSwGk3fIkTvdT5Rg,9088
|
|
22
22
|
avrotize/avrotoproto.py,sha256=STqbdGjVrgKrlKXt-6dZlekW_Oq0W0StRx80St1XqIc,22486
|
|
23
|
-
avrotize/avrotopython.py,sha256=
|
|
23
|
+
avrotize/avrotopython.py,sha256=K1r6STp9OJJfQOwVMVpKUKRdYuo89JHmp_wLIO4d0WE,31331
|
|
24
24
|
avrotize/avrotorust.py,sha256=QMIBNkFpDlH44kuQo24k5D-f0lmdhoA5b7hEbhKsnMw,22214
|
|
25
25
|
avrotize/avrotots.py,sha256=u_XLjlHN0Gof5QYlpqK4X9WoX9rL30TjQMPg4TiyYnI,33241
|
|
26
26
|
avrotize/avrotoxsd.py,sha256=iGQq_8kC0kfKsqvqS6s_mO-kJ8N5G8vXOwqRI_DZUxc,17744
|
|
@@ -44,29 +44,29 @@ avrotize/proto3parser.py,sha256=MfE84c-oAWWuzYmKlEZ5g5LUF7YzZaASFh2trX3UCaw,1560
|
|
|
44
44
|
avrotize/prototoavro.py,sha256=hqXBGRxYojaEbEgoHZxXwMG4R1nWC7UMl_XNLWfqH38,17346
|
|
45
45
|
avrotize/structuretocddl.py,sha256=RK_dTJf0oAo6BIBM48NHRcWC96OtUjlgUC6HzXs5Lkk,21234
|
|
46
46
|
avrotize/structuretocpp.py,sha256=tBWOvyZPYQ1CHN6RgDnWlmzJ1giOyQ9SlHBHWvhPyiw,35898
|
|
47
|
-
avrotize/structuretocsharp.py,sha256=
|
|
47
|
+
avrotize/structuretocsharp.py,sha256=qYYAV6M8I2xv-VUzOobPT1_FYAoBBe0VRTMe9eY8r1g,122229
|
|
48
48
|
avrotize/structuretocsv.py,sha256=w9cwXAnnakKaeTtXsLWWO8KwYnXUxyXvC7a-ZKs-E94,13851
|
|
49
49
|
avrotize/structuretodatapackage.py,sha256=NEHRt30KfVDWH1EQakvuMdRZTtfVXx8fsaYud0ofb2g,29768
|
|
50
50
|
avrotize/structuretodb.py,sha256=3QE_TCdNklGH5ymzGsEnX1sI4OhvX2AYKPH7xtR5tHk,43926
|
|
51
|
-
avrotize/structuretogo.py,sha256=
|
|
51
|
+
avrotize/structuretogo.py,sha256=FNL5et5xxAoS0YSPJWGliWAFZPH_p8Nmu9pSEAT4J0U,32876
|
|
52
52
|
avrotize/structuretographql.py,sha256=wcGXnrup5v5saRa1BhR6o-X8q8ujsQMVqrFHQTBPjww,20468
|
|
53
53
|
avrotize/structuretoiceberg.py,sha256=itKb33Kj-7-udk4eHTLmTEasIeh1ggpZ3e_bwCxLABM,15344
|
|
54
|
-
avrotize/structuretojava.py,sha256=
|
|
54
|
+
avrotize/structuretojava.py,sha256=nlXRMfLcAEzGg3Q379UdQ6i9W7eqPjMFXFrN5d09r1E,46955
|
|
55
55
|
avrotize/structuretojs.py,sha256=TJuhUGqn0F2omjPVcrPeFPHY2KynRceFg9gMGvEenxA,29608
|
|
56
56
|
avrotize/structuretojsons.py,sha256=PJrQBaf6yQHu5eFkePxbjPBEmL-fYfX2wj6OmH1jsWw,22495
|
|
57
57
|
avrotize/structuretokusto.py,sha256=rOKgYIcm7ZK8RS-VvMFPNzPzwtv7c4dIKU-fKjrJLyM,30618
|
|
58
58
|
avrotize/structuretomd.py,sha256=exfCldYbieVdduhotSoLrxsbphmyJQyeQso9qv4qyUw,13642
|
|
59
59
|
avrotize/structuretoproto.py,sha256=Aq0-fwMXSjjAxgZ5mq1kpo_TauigMRrJK9LNyoN-YGs,42679
|
|
60
|
-
avrotize/structuretopython.py,sha256=
|
|
60
|
+
avrotize/structuretopython.py,sha256=2dCa33nZBXgh996p3zA9uF3S1LHbOcpMSuoDXQPjAnw,39627
|
|
61
61
|
avrotize/structuretorust.py,sha256=ChRmO7uzU-pMdDdS0Vtg-MVUaOaNhNUPwH-ZKKOHglU,35134
|
|
62
|
-
avrotize/structuretots.py,sha256=
|
|
62
|
+
avrotize/structuretots.py,sha256=W552IJbTetZuTKtFE5A8B75neyRJXP7Q3ioWZCunLtE,35182
|
|
63
63
|
avrotize/structuretoxsd.py,sha256=01VpasyWSMOx04sILHLP7H-WkhGdXAEGKohUUfgrNf0,32797
|
|
64
64
|
avrotize/xsdtoavro.py,sha256=nQtNH_3pEZBp67oUCPqzhvItEExHTe-8obsIfNRXt8Y,19064
|
|
65
65
|
avrotize/dependencies/cpp/vcpkg/vcpkg.json,sha256=se5qnUVQ1Q6wN_DqgIioqKg_y7ouh9oly2iBAJJXkgw,414
|
|
66
66
|
avrotize/dependencies/typescript/node22/package.json,sha256=oAW_2X-b715kV7aajwuONZEMkyQUJGviG3GwnoUa7hU,387
|
|
67
|
-
structurize-2.
|
|
68
|
-
structurize-2.
|
|
69
|
-
structurize-2.
|
|
70
|
-
structurize-2.
|
|
71
|
-
structurize-2.
|
|
72
|
-
structurize-2.
|
|
67
|
+
structurize-2.21.1.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
|
|
68
|
+
structurize-2.21.1.dist-info/METADATA,sha256=8jowGG-vb0Pw-3Ae2bMu-iPg2vFpUMQA6-K0TR69ENw,3670
|
|
69
|
+
structurize-2.21.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
70
|
+
structurize-2.21.1.dist-info/entry_points.txt,sha256=biIH7jA5auhVqfbwHVk2gmD_gvrPYKgjpCAn0JWZ-Rs,55
|
|
71
|
+
structurize-2.21.1.dist-info/top_level.txt,sha256=yn-yQ0Cm1O9fbF8KJgv4IIvX4YRGelKgPqZF1wS5P50,9
|
|
72
|
+
structurize-2.21.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|