pydantic-avro 0.9.0__tar.gz → 0.9.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pydantic-avro
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: Converting pydantic classes to avro schemas
5
5
  Home-page: https://github.com/godatadriven/pydantic-avro
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pydantic-avro"
3
- version = "0.9.0"
3
+ version = "0.9.1"
4
4
  description = "Converting pydantic classes to avro schemas"
5
5
  authors = ["Peter van 't Hof' <peter.vanthof@godatadriven.com>"]
6
6
 
@@ -18,7 +18,7 @@ python = ">=3.8.1,<4.0"
18
18
  pydantic = ">=1.4,<3.0"
19
19
 
20
20
  [tool.poetry.dev-dependencies]
21
- coverage = { version = "^7.2.2", extras = ["toml"] }
21
+ coverage = { version = "^7.6.1", extras = ["toml"] }
22
22
  pytest = "^8.3.5"
23
23
  pytest-mock = "^3.10.0"
24
24
  pyproject-flake8 = "^7.0.0"
@@ -28,6 +28,7 @@ pytest-cov = "^5.0.0"
28
28
  mypy = "^1.1.1"
29
29
  avro = "^1.12.0"
30
30
  fastavro = "^1.8.1"
31
+ typing-extensions = "^4.13.2"
31
32
 
32
33
  [tool.poetry.scripts]
33
34
  pydantic-avro = "pydantic_avro.__main__:root_main"
@@ -18,7 +18,7 @@ entry_points = \
18
18
 
19
19
  setup_kwargs = {
20
20
  'name': 'pydantic-avro',
21
- 'version': '0.9.0',
21
+ 'version': '0.9.1',
22
22
  'description': 'Converting pydantic classes to avro schemas',
23
23
  'long_description': '[![Python package](https://github.com/godatadriven/pydantic-avro/actions/workflows/python-package.yml/badge.svg)](https://github.com/godatadriven/pydantic-avro/actions/workflows/python-package.yml)\n[![codecov](https://codecov.io/gh/godatadriven/pydantic-avro/branch/main/graph/badge.svg?token=5L08GOERAW)](https://codecov.io/gh/godatadriven/pydantic-avro)\n[![PyPI version](https://badge.fury.io/py/pydantic-avro.svg)](https://badge.fury.io/py/pydantic-avro)\n[![CodeQL](https://github.com/godatadriven/pydantic-avro/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/godatadriven/pydantic-avro/actions/workflows/codeql-analysis.yml)\n\n# pydantic-avro\n\nThis library can convert a pydantic class to a avro schema or generate python code from a avro schema.\n\n### Install\n\n```bash\npip install pydantic-avro\n```\n\n### Pydantic class to avro schema\n\n```python\nimport json\nfrom typing import Optional\n\nfrom pydantic_avro.base import AvroBase\n\n\nclass TestModel(AvroBase):\n key1: str\n key2: int\n key2: Optional[str]\n\n\nschema_dict: dict = TestModel.avro_schema()\nprint(json.dumps(schema_dict))\n\n```\n\n### Avro schema to pydantic\n\n```shell\n# Print to stdout\npydantic-avro avro_to_pydantic --asvc /path/to/schema.asvc\n\n# Save it to a file\npydantic-avro avro_to_pydantic --asvc /path/to/schema.asvc --output /path/to/output.py\n```\n\n### Specify expected Avro type\n\n```python\nfrom datetime import datetime\nfrom pydantic import Field\nfrom pydantic_avro.base import AvroBase \n\nclass ExampleModel(AvroBase):\n field1: int = Field(..., avro_type="long") # Explicitly set Avro type to "long"\n field2: datetime = Field(..., avro_type="timestamp-millis") # Explicitly set Avro type to "timestamp-millis"\n```\n\n### Install for developers\n\n###### Install package\n\n- Requirement: Poetry 1.*\n\n```shell\npoetry install\n```\n\n###### Run unit tests\n```shell\npytest\ncoverage run -m pytest # with coverage\n# or (depends on your local env) \npoetry run pytest\npoetry run coverage run -m pytest # with coverage\n```\n\n##### Run linting\n\nThe linting is checked in the github workflow. To fix and review issues run this:\n```shell\nblack . # Auto fix all issues\nisort . # Auto fix all issues\npflake . # Only display issues, fixing is manual\n```\n',
24
24
  'author': "Peter van 't Hof'",
@@ -137,11 +137,14 @@ class AvroTypeConverter:
137
137
  r = field_props["allOf"][0]["$ref"]
138
138
  if ("prefixItems" in field_props or ("minItems" in field_props and "maxItems" in field_props)) and t == "array":
139
139
  t = "tuple"
140
-
141
140
  u = field_props.get("anyOf")
141
+ o = field_props.get("oneOf")
142
+ discriminator = field_props.get("discriminator")
142
143
 
143
144
  if u is not None:
144
- return self._union_to_avro(u, avro_type_dict)
145
+ return self._union_to_avro(u, avro_type_dict, discriminator)
146
+ elif o is not None:
147
+ return self._union_to_avro(o, avro_type_dict, discriminator)
145
148
  elif r is not None:
146
149
  return self._handle_references(r, avro_type_dict)
147
150
  elif t is None:
@@ -229,8 +232,13 @@ class AvroTypeConverter:
229
232
  value_type = self._get_avro_type_dict(a)["type"]
230
233
  return {"type": "map", "values": value_type}
231
234
 
232
- def _union_to_avro(self, field_props: list, avro_type_dict: dict) -> dict:
233
- """Returns a type of a union field"""
235
+ def _union_to_avro(self, field_props: list, avro_type_dict: dict, discriminator: Optional[dict] = None) -> dict:
236
+ """Returns a type of a union field, including discriminated unions"""
237
+ # Handle discriminated unions
238
+ if discriminator is not None:
239
+ return self._discriminated_union_to_avro(field_props, avro_type_dict, discriminator)
240
+
241
+ # Standard union handling (unchanged)
234
242
  avro_type_dict["type"] = []
235
243
  for union_element in field_props:
236
244
  t = self._get_avro_type_dict(union_element)
@@ -277,3 +285,39 @@ class AvroTypeConverter:
277
285
  tn = tn["type"]
278
286
  avro_type_dict["type"] = {"type": "array", "items": tn}
279
287
  return avro_type_dict
288
+
289
+ def _discriminated_union_to_avro(self, variants: list, avro_type_dict: dict, discriminator: dict) -> dict:
290
+ """Handles discriminated unions with a discriminator field"""
291
+ discriminator_property = discriminator.get("propertyName", "type")
292
+ variant_types = []
293
+
294
+ # Process each variant in the union
295
+ for variant in variants:
296
+ # Get the discriminator value for this variant
297
+ disc_value = None
298
+ if "properties" in variant and discriminator_property in variant.get("properties", {}):
299
+ prop = variant["properties"][discriminator_property]
300
+ if "const" in prop:
301
+ disc_value = prop["const"]
302
+ elif "enum" in prop and len(prop["enum"]) == 1:
303
+ disc_value = prop["enum"][0]
304
+
305
+ # If we can't determine the discriminator value, generate a regular record
306
+ if disc_value is None:
307
+ variant_type = self._get_avro_type_dict(variant)
308
+ else:
309
+ # Create a named record for this variant
310
+ variant_name = f"{disc_value}Variant"
311
+ if "$ref" in variant:
312
+ ref_schema = get_definition(variant["$ref"], self.root_schema)
313
+ variant_fields = self.fields_to_avro_dicts(ref_schema)
314
+ else:
315
+ variant_fields = self.fields_to_avro_dicts(variant)
316
+
317
+ variant_type = {"type": {"type": "record", "name": variant_name, "fields": variant_fields}}
318
+
319
+ variant_types.append(variant_type["type"])
320
+
321
+ # Set the union of all variant types
322
+ avro_type_dict["type"] = variant_types
323
+ return avro_type_dict
File without changes
File without changes