structurize 3.6.0__tar.gz → 3.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {structurize-3.6.0/structurize.egg-info → structurize-3.6.1}/PKG-INFO +1 -1
  2. {structurize-3.6.0 → structurize-3.6.1}/avrotize/_version.py +3 -3
  3. {structurize-3.6.0 → structurize-3.6.1}/avrotize/jstructtoavro.py +53 -7
  4. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretokusto.py +32 -3
  5. {structurize-3.6.0 → structurize-3.6.1/structurize.egg-info}/PKG-INFO +1 -1
  6. {structurize-3.6.0 → structurize-3.6.1}/.gitignore +0 -0
  7. {structurize-3.6.0 → structurize-3.6.1}/LICENSE +0 -0
  8. {structurize-3.6.0 → structurize-3.6.1}/MANIFEST.in +0 -0
  9. {structurize-3.6.0 → structurize-3.6.1}/README.md +0 -0
  10. {structurize-3.6.0 → structurize-3.6.1}/avrotize/__init__.py +0 -0
  11. {structurize-3.6.0 → structurize-3.6.1}/avrotize/__main__.py +0 -0
  12. {structurize-3.6.0 → structurize-3.6.1}/avrotize/asn1toavro.py +0 -0
  13. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotize.py +0 -0
  14. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotocpp.py +0 -0
  15. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotocsharp.py +0 -0
  16. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotocsv.py +0 -0
  17. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotodatapackage.py +0 -0
  18. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotodb.py +0 -0
  19. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotogo.py +0 -0
  20. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotographql.py +0 -0
  21. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotoiceberg.py +0 -0
  22. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotojava.py +0 -0
  23. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotojs.py +0 -0
  24. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotojsons.py +0 -0
  25. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotojstruct.py +0 -0
  26. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotokusto.py +0 -0
  27. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotomd.py +0 -0
  28. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotools.py +0 -0
  29. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotoparquet.py +0 -0
  30. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotoproto.py +0 -0
  31. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotopython.py +0 -0
  32. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotorust.py +0 -0
  33. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotots.py +0 -0
  34. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrototsml.py +0 -0
  35. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrotoxsd.py +0 -0
  36. {structurize-3.6.0 → structurize-3.6.1}/avrotize/avrovalidator.py +0 -0
  37. {structurize-3.6.0 → structurize-3.6.1}/avrotize/cddltostructure.py +0 -0
  38. {structurize-3.6.0 → structurize-3.6.1}/avrotize/choice_inference.py +0 -0
  39. {structurize-3.6.0 → structurize-3.6.1}/avrotize/commands.json +0 -0
  40. {structurize-3.6.0 → structurize-3.6.1}/avrotize/common.py +0 -0
  41. {structurize-3.6.0 → structurize-3.6.1}/avrotize/constants.py +0 -0
  42. {structurize-3.6.0 → structurize-3.6.1}/avrotize/csvtoavro.py +0 -0
  43. {structurize-3.6.0 → structurize-3.6.1}/avrotize/datapackagetoavro.py +0 -0
  44. {structurize-3.6.0 → structurize-3.6.1}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
  45. {structurize-3.6.0 → structurize-3.6.1}/avrotize/dependencies/typescript/node22/package.json +0 -0
  46. {structurize-3.6.0 → structurize-3.6.1}/avrotize/dependency_resolver.py +0 -0
  47. {structurize-3.6.0 → structurize-3.6.1}/avrotize/dependency_version.py +0 -0
  48. {structurize-3.6.0 → structurize-3.6.1}/avrotize/jsonstoavro.py +0 -0
  49. {structurize-3.6.0 → structurize-3.6.1}/avrotize/jsonstostructure.py +0 -0
  50. {structurize-3.6.0 → structurize-3.6.1}/avrotize/jsontoschema.py +0 -0
  51. {structurize-3.6.0 → structurize-3.6.1}/avrotize/kstructtoavro.py +0 -0
  52. {structurize-3.6.0 → structurize-3.6.1}/avrotize/kustotoavro.py +0 -0
  53. {structurize-3.6.0 → structurize-3.6.1}/avrotize/kustotojstruct.py +0 -0
  54. {structurize-3.6.0 → structurize-3.6.1}/avrotize/mcp_server.py +0 -0
  55. {structurize-3.6.0 → structurize-3.6.1}/avrotize/openapitostructure.py +0 -0
  56. {structurize-3.6.0 → structurize-3.6.1}/avrotize/parquettoavro.py +0 -0
  57. {structurize-3.6.0 → structurize-3.6.1}/avrotize/proto2parser.py +0 -0
  58. {structurize-3.6.0 → structurize-3.6.1}/avrotize/proto3parser.py +0 -0
  59. {structurize-3.6.0 → structurize-3.6.1}/avrotize/prototoavro.py +0 -0
  60. {structurize-3.6.0 → structurize-3.6.1}/avrotize/schema_inference.py +0 -0
  61. {structurize-3.6.0 → structurize-3.6.1}/avrotize/sqltoavro.py +0 -0
  62. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretocddl.py +0 -0
  63. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretocpp.py +0 -0
  64. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretocsharp.py +0 -0
  65. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretocsv.py +0 -0
  66. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretodatapackage.py +0 -0
  67. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretodb.py +0 -0
  68. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretogo.py +0 -0
  69. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretographql.py +0 -0
  70. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretoiceberg.py +0 -0
  71. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretojava.py +0 -0
  72. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretojs.py +0 -0
  73. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretojsons.py +0 -0
  74. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretomd.py +0 -0
  75. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretoproto.py +0 -0
  76. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretopython.py +0 -0
  77. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretorust.py +0 -0
  78. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretots.py +0 -0
  79. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretotsml.py +0 -0
  80. {structurize-3.6.0 → structurize-3.6.1}/avrotize/structuretoxsd.py +0 -0
  81. {structurize-3.6.0 → structurize-3.6.1}/avrotize/tmslvalidate.py +0 -0
  82. {structurize-3.6.0 → structurize-3.6.1}/avrotize/validate.py +0 -0
  83. {structurize-3.6.0 → structurize-3.6.1}/avrotize/xmltoschema.py +0 -0
  84. {structurize-3.6.0 → structurize-3.6.1}/avrotize/xsdtoavro.py +0 -0
  85. {structurize-3.6.0 → structurize-3.6.1}/build.ps1 +0 -0
  86. {structurize-3.6.0 → structurize-3.6.1}/build.sh +0 -0
  87. {structurize-3.6.0 → structurize-3.6.1}/pyproject.toml +0 -0
  88. {structurize-3.6.0 → structurize-3.6.1}/setup.cfg +0 -0
  89. {structurize-3.6.0 → structurize-3.6.1}/structurize.egg-info/SOURCES.txt +0 -0
  90. {structurize-3.6.0 → structurize-3.6.1}/structurize.egg-info/dependency_links.txt +0 -0
  91. {structurize-3.6.0 → structurize-3.6.1}/structurize.egg-info/entry_points.txt +0 -0
  92. {structurize-3.6.0 → structurize-3.6.1}/structurize.egg-info/requires.txt +0 -0
  93. {structurize-3.6.0 → structurize-3.6.1}/structurize.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 3.6.0
3
+ Version: 3.6.1
4
4
  Summary: Tools to convert from and to JSON Structure from various other schema languages.
5
5
  Author-email: Clemens Vasters <clemensv@microsoft.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.6.0'
22
- __version_tuple__ = version_tuple = (3, 6, 0)
21
+ __version__ = version = '3.6.1'
22
+ __version_tuple__ = version_tuple = (3, 6, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g1329b8c45'
24
+ __commit_id__ = commit_id = 'ga0404973f'
@@ -8,7 +8,7 @@ This is the reverse operation of avrotojstruct.py.
8
8
  import json
9
9
  from typing import Any, Dict, List, Union, Optional
10
10
 
11
- from avrotize.common import ANY_VALUE_RECORD, any_value_name, deduplicate_any_value_record, generic_type
11
+ from avrotize.common import ANY_VALUE_RECORD, any_value_name, deduplicate_any_value_record, generic_type, avro_name, avro_name_with_altname
12
12
 
13
13
 
14
14
  class JsonStructureToAvro:
@@ -388,10 +388,16 @@ class JsonStructureToAvro:
388
388
 
389
389
  fields = []
390
390
  for prop_name, prop_schema in properties.items():
391
+ # Property keys may be non-identifiers (e.g. "dr-type", "1"). Avro field
392
+ # names must match [A-Za-z_][A-Za-z0-9_]*, so sanitize and preserve the
393
+ # original wire key as an Avro alias for round-trip fidelity (issue #382).
394
+ avro_field_name, original_name = avro_name_with_altname(prop_name)
391
395
  field = {
392
- 'name': prop_name,
393
- 'type': self._convert_type_reference(prop_schema, name_hint=prop_name)
396
+ 'name': avro_field_name,
397
+ 'type': self._convert_type_reference(prop_schema, name_hint=avro_field_name)
394
398
  }
399
+ if original_name is not None:
400
+ field['aliases'] = [original_name]
395
401
 
396
402
  # Build documentation with annotations
397
403
  doc = self._build_doc_with_annotations(
@@ -418,22 +424,61 @@ class JsonStructureToAvro:
418
424
  avro_record['fields'] = fields
419
425
  return avro_record
420
426
 
427
+ def _sanitize_enum_symbols(self, symbols: List[Any]) -> tuple:
428
+ """
429
+ Sanitize JSON Structure enum values into valid Avro enum symbols.
430
+
431
+ Avro enum symbols must match [A-Za-z_][A-Za-z0-9_]* and be unique. JSON
432
+ Structure enum values may be arbitrary (e.g. "N/A", "KAR-MQTT", 1). This
433
+ sanitizes each value, resolves collisions with a numeric suffix, and returns
434
+ both the symbol list and a mapping of sanitized-symbol -> original-value for
435
+ any symbol that was changed (issue #383).
436
+
437
+ Returns:
438
+ tuple: (avro_symbols, symbol_mapping)
439
+ """
440
+ avro_symbols: List[str] = []
441
+ symbol_mapping: Dict[str, Any] = {}
442
+ seen = set()
443
+ for value in symbols:
444
+ base = avro_name(str(value))
445
+ candidate = base
446
+ suffix = 1
447
+ while candidate in seen:
448
+ candidate = f"{base}_{suffix}"
449
+ suffix += 1
450
+ seen.add(candidate)
451
+ avro_symbols.append(candidate)
452
+ if candidate != str(value):
453
+ symbol_mapping[candidate] = value
454
+ return avro_symbols, symbol_mapping
455
+
421
456
  def _convert_enum(self, schema: Dict[str, Any], namespace: Optional[str], name: str) -> Dict[str, Any]:
422
457
  """Convert JSON Structure enum to Avro enum."""
458
+ avro_symbols, symbol_mapping = self._sanitize_enum_symbols(schema['enum'])
423
459
  avro_enum: Dict[str, Any] = {
424
460
  'type': 'enum',
425
461
  'name': name,
426
- 'symbols': schema['enum']
462
+ 'symbols': avro_symbols
427
463
  }
428
464
 
429
465
  if namespace:
430
466
  avro_enum['namespace'] = namespace
431
467
 
468
+ doc_parts = []
432
469
  if 'description' in schema:
433
- avro_enum['doc'] = schema['description']
470
+ doc_parts.append(schema['description'])
471
+ if symbol_mapping:
472
+ doc_parts.append(
473
+ 'Original enum values (Avro symbol -> wire value): '
474
+ + json.dumps(symbol_mapping))
475
+ if doc_parts:
476
+ avro_enum['doc'] = ' '.join(doc_parts)
434
477
 
435
478
  if 'default' in schema:
436
- avro_enum['default'] = schema['default']
479
+ original_values = list(schema['enum'])
480
+ if schema['default'] in original_values:
481
+ avro_enum['default'] = avro_symbols[original_values.index(schema['default'])]
437
482
 
438
483
  return avro_enum
439
484
 
@@ -531,11 +576,12 @@ class JsonStructureToAvro:
531
576
  # Build enum type for discriminator
532
577
  enum_name = f'{name}Type'
533
578
  choice_names = list(choices.keys())
579
+ discriminator_symbols, _ = self._sanitize_enum_symbols(choice_names)
534
580
 
535
581
  discriminator_enum: Dict[str, Any] = {
536
582
  'type': 'enum',
537
583
  'name': enum_name,
538
- 'symbols': choice_names
584
+ 'symbols': discriminator_symbols
539
585
  }
540
586
 
541
587
  if namespace:
@@ -1,12 +1,41 @@
1
1
  """Converts a JSON Structure schema to a Kusto table schema."""
2
2
 
3
3
  import json
4
+ import re
4
5
  import sys
5
6
  from typing import Any, List, Optional, Dict, Union
6
7
  from avrotize.common import build_flat_type_dict, inline_avro_references, strip_first_doc
7
8
  from azure.kusto.data import KustoClient, KustoConnectionStringBuilder, ClientRequestProperties
8
9
 
9
10
 
11
+ _KUSTO_BARE_IDENTIFIER = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$')
12
+
13
+
14
+ def kusto_column_name(name: str) -> str:
15
+ """Return a Kusto-quoted column identifier.
16
+
17
+ Bare identifiers ([A-Za-z_][A-Za-z0-9_]*) are wrapped as [name]; any other
18
+ name (e.g. "dr-type") is quoted as ['name'] so hyphens and other characters
19
+ are valid (issue #382).
20
+ """
21
+ if _KUSTO_BARE_IDENTIFIER.match(name):
22
+ return f"[{name}]"
23
+ escaped = name.replace('\\', '\\\\').replace("'", "\\'")
24
+ return f"['{escaped}']"
25
+
26
+
27
+ def kusto_json_path(name: str, prefix: str = "$") -> str:
28
+ """Return a JSONPath expression for an ingestion-mapping property name.
29
+
30
+ Bare identifiers use dotted notation (``$.name``); any other name uses
31
+ bracket notation (``$['dr-type']``) so non-identifier keys are valid.
32
+ """
33
+ if _KUSTO_BARE_IDENTIFIER.match(name):
34
+ return f"{prefix}.{name}"
35
+ escaped = name.replace('\\', '\\\\').replace("'", "\\'")
36
+ return f"{prefix}['{escaped}']"
37
+
38
+
10
39
  def kusto_string_literal(value: str) -> str:
11
40
  """Return a KQL verbatim string literal for a raw Python string."""
12
41
  return '@"' + value.replace('"', '""') + '"'
@@ -268,7 +297,7 @@ class StructureToKusto:
268
297
  if isinstance(prop_schema, dict) and 'const' in prop_schema:
269
298
  continue
270
299
  column_type = self.convert_structure_type_to_kusto_type(prop_schema, schema_doc)
271
- columns.append(f" [{column_name}]: {column_type}")
300
+ columns.append(f" {kusto_column_name(column_name)}: {column_type}")
272
301
  if emit_cloudevents_columns:
273
302
  columns.append(" [___type]: string")
274
303
  columns.append(" [___source]: string")
@@ -365,7 +394,7 @@ class StructureToKusto:
365
394
  continue
366
395
  column_name = prop_name
367
396
  mapping_entries.append(
368
- f" {{\"column\": \"{column_name}\", \"path\": \"$.{prop_name}\"}}")
397
+ f" {{\"column\": \"{column_name}\", \"path\": \"{kusto_json_path(prop_name)}\"}}")
369
398
  kusto.append("```\n[\n" + ",\n".join(mapping_entries) + "\n]\n```\n\n")
370
399
 
371
400
  if emit_cloudevents_columns:
@@ -383,7 +412,7 @@ class StructureToKusto:
383
412
  continue
384
413
  column_name = prop_name
385
414
  ce_entries.append(
386
- f" {{\"column\": \"{column_name}\", \"path\": \"$.data.{prop_name}\"}}")
415
+ f" {{\"column\": \"{column_name}\", \"path\": \"{kusto_json_path(prop_name, '$.data')}\"}}")
387
416
  kusto.append("```\n[\n" + ",\n".join(ce_entries) + "\n]\n```\n\n")
388
417
 
389
418
  if emit_cloudevents_columns:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 3.6.0
3
+ Version: 3.6.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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes