pydantic-avro 0.9.2__tar.gz → 0.10.0__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.2
3
+ Version: 0.10.0
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.2"
3
+ version = "0.10.0"
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 @@ entry_points = \
18
18
 
19
19
  setup_kwargs = {
20
20
  'name': 'pydantic-avro',
21
- 'version': '0.9.2',
21
+ 'version': '0.10.0',
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'",
@@ -0,0 +1,3 @@
1
+ from pydantic_avro.to_avro.base import AvroBase as AvroBase
2
+
3
+ __all__ = ["AvroBase"]
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Literal, Optional
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
@@ -10,14 +10,30 @@ class AvroBase(BaseModel):
10
10
  """This class provides functionality to convert a pydantic model to an Avro schema."""
11
11
 
12
12
  @classmethod
13
- def avro_schema(cls, by_alias: bool = True, namespace: Optional[str] = None) -> dict:
13
+ def avro_schema(
14
+ cls,
15
+ by_alias: bool = True,
16
+ namespace: Optional[str] = None,
17
+ mode: Literal["validation", "serialization"] = "serialization",
18
+ ) -> dict:
14
19
  """Returns the avro schema for the pydantic class
15
20
 
16
21
  :param by_alias: generate the schemas using the aliases defined, if any
17
22
  :param namespace: Provide an optional namespace string to use in schema generation
23
+ :param mode: The mode for generating the schema. Use 'validation' for input validation
24
+ or 'serialization' for output serialization. Defaults to 'serialization'.
25
+ Only applicable for Pydantic v2.
18
26
  :return: dict with the Avro Schema for the model
19
27
  """
20
- schema = cls.model_json_schema(by_alias=by_alias) if PYDANTIC_V2 else cls.schema(by_alias=by_alias)
28
+ if PYDANTIC_V2:
29
+ schema = cls.model_json_schema(by_alias=by_alias, mode=mode)
30
+ else:
31
+ if mode != "serialization":
32
+ raise ValueError(
33
+ f"The 'mode' parameter is only supported in Pydantic v2. "
34
+ f"Pydantic v1 does not support different schema modes."
35
+ )
36
+ schema = cls.schema(by_alias=by_alias)
21
37
 
22
38
  if namespace is None:
23
39
  # Default namespace will be based on title
@@ -1,3 +1,4 @@
1
+ from copy import deepcopy
1
2
  from typing import Any, Dict, List, Optional, Set
2
3
 
3
4
  from pydantic_avro.to_avro.config import DEFS_NAME
@@ -133,6 +134,13 @@ class AvroTypeConverter:
133
134
  f = field_props.get("format")
134
135
  r = field_props.get("$ref")
135
136
  at = field_props.get("avro_type")
137
+
138
+ # For Pydantic v1, check json_schema_extra for avro_type
139
+ if at is None and "json_schema_extra" in field_props:
140
+ json_schema_extra = field_props.get("json_schema_extra")
141
+ if isinstance(json_schema_extra, dict):
142
+ at = json_schema_extra.get("avro_type")
143
+
136
144
  if "allOf" in field_props and len(field_props["allOf"]) == 1:
137
145
  r = field_props["allOf"][0]["$ref"]
138
146
  if ("prefixItems" in field_props or ("minItems" in field_props and "maxItems" in field_props)) and t == "array":
@@ -142,9 +150,9 @@ class AvroTypeConverter:
142
150
  discriminator = field_props.get("discriminator")
143
151
 
144
152
  if u is not None:
145
- return self._union_to_avro(u, avro_type_dict, discriminator)
153
+ return self._union_to_avro(u, avro_type_dict, discriminator, parent_avro_type=at)
146
154
  elif o is not None:
147
- return self._union_to_avro(o, avro_type_dict, discriminator)
155
+ return self._union_to_avro(o, avro_type_dict, discriminator, parent_avro_type=at)
148
156
  elif r is not None:
149
157
  return self._handle_references(r, avro_type_dict)
150
158
  elif t is None:
@@ -232,15 +240,37 @@ class AvroTypeConverter:
232
240
  value_type = self._get_avro_type_dict(a)["type"]
233
241
  return {"type": "map", "values": value_type}
234
242
 
235
- def _union_to_avro(self, field_props: list, avro_type_dict: dict, discriminator: Optional[dict] = None) -> dict:
243
+ def _should_propagate_avro_type(self, union_element: dict, parent_avro_type: str) -> bool:
244
+ """Determine if parent avro_type should be propagated to this union element"""
245
+ # Don't propagate to null types or elements that already have avro_type
246
+ if union_element.get("type") == "null" or "avro_type" in union_element:
247
+ return False
248
+
249
+ element_format = union_element.get("format")
250
+
251
+ # Only propagate temporal avro_types to date-time formatted elements
252
+ temporal_avro_types = {"timestamp-millis", "timestamp-micros", "time-millis", "time-micros", "date"}
253
+ if parent_avro_type in temporal_avro_types:
254
+ return element_format in {"date-time", "date", "time"}
255
+
256
+ # For other avro_types, only propagate if there's no format (meaning it's not already specialized)
257
+ return element_format is None
258
+
259
+ def _union_to_avro(self, field_props: list, avro_type_dict: dict, discriminator: Optional[dict] = None, parent_avro_type: Optional[str] = None) -> dict:
236
260
  """Returns a type of a union field, including discriminated unions"""
237
261
  # Handle discriminated unions
238
262
  if discriminator is not None:
239
263
  return self._discriminated_union_to_avro(field_props, avro_type_dict, discriminator)
240
264
 
241
- # Standard union handling (unchanged)
265
+ # Standard union handling
242
266
  avro_type_dict["type"] = []
243
267
  for union_element in field_props:
268
+ # Propagate parent avro_type to compatible union elements
269
+ if parent_avro_type is not None and self._should_propagate_avro_type(union_element, parent_avro_type):
270
+ # Create a deep copy to avoid modifying the original
271
+ union_element = deepcopy(union_element)
272
+ union_element["avro_type"] = parent_avro_type
273
+
244
274
  t = self._get_avro_type_dict(union_element)
245
275
  avro_type_dict["type"].append(t["type"])
246
276
  return avro_type_dict
@@ -1 +0,0 @@
1
- from pydantic_avro.to_avro.base import AvroBase
File without changes
File without changes