localstack-py-avro-schema 3.9.1__py3-none-any.whl → 3.9.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localstack-py-avro-schema
3
- Version: 3.9.1
3
+ Version: 3.9.2
4
4
  Summary: Generate Apache Avro schemas for Python types including standard library data-classes and Pydantic data models.
5
5
  Author-email: LocalStack Contributors <info@localstack.cloud>, "J.P. Morgan Chase & Co." <open_source@jpmorgan.com>
6
6
  License: Apache License
@@ -0,0 +1,12 @@
1
+ localstack_py_avro_schema-3.9.2.dist-info/licenses/LICENSE,sha256=zE1N4JILDTkSIDtdmqdnKKxKEQh_VdqeoAV2230eNOI,10141
2
+ localstack_py_avro_schema-3.9.2.dist-info/licenses/LICENSE_HEADER.txt,sha256=Nx3RGmYbJKjIKM8Y2yMrCPJgCw4oBt_H62PxDKlAsto,564
3
+ py_avro_schema/__init__.py,sha256=bK6hTw4XJy5bAP4lgIvJR5LHtEiyUWq4GNJ5w-uSEpc,2414
4
+ py_avro_schema/_alias.py,sha256=nCQqtn7IbpjV0ibpJf9aMX9FvwVx2EDC1iKzwjQ7CqI,3412
5
+ py_avro_schema/_schemas.py,sha256=I5otHWPh8tcws3ey4iwsx7c0Zz1VNLmMTabTh43E-7s,46993
6
+ py_avro_schema/_testing.py,sha256=3aSfMbNDDeatl3H7GEUcynxK81HFiF-WCLOZ3FCCLiw,2261
7
+ py_avro_schema/_typing.py,sha256=9kBlPA7C33zZP3nRhCvSJA_0m54uvUo0AQ4wJJmSxMk,3291
8
+ py_avro_schema/py.typed,sha256=HnEDkaznpgfRW07Qfogy4tFLu_4dcQ5YcOsI7pmU5rQ,52
9
+ localstack_py_avro_schema-3.9.2.dist-info/METADATA,sha256=6K-dy4M5Pj2-uoHcRF15vZVRz7pfiibgQcAnysW4YKY,15164
10
+ localstack_py_avro_schema-3.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ localstack_py_avro_schema-3.9.2.dist-info/top_level.txt,sha256=0t4oO_5rsdfZGSk8iSzK4TjdyfVz-RE1IZ2TIz6YvI0,15
12
+ localstack_py_avro_schema-3.9.2.dist-info/RECORD,,
py_avro_schema/_alias.py CHANGED
@@ -5,7 +5,9 @@ This module maintains global state via the _ALIASES registry.
5
5
  Decorators will modify this state when applied to classes.
6
6
  """
7
7
 
8
+ import dataclasses
8
9
  from collections import defaultdict
10
+ from typing import Annotated, Type, get_args, get_origin
9
11
 
10
12
  FQN = str
11
13
  """Fully qualified name for a Python type"""
@@ -13,6 +15,20 @@ _ALIASES: dict[FQN, set[FQN]] = defaultdict(set)
13
15
  """Maps the FQN of a Python type to a set of aliases"""
14
16
 
15
17
 
18
+ @dataclasses.dataclass
19
+ class Alias:
20
+ """Alias for a record field"""
21
+
22
+ alias: str
23
+
24
+
25
+ @dataclasses.dataclass
26
+ class Aliases:
27
+ """Aliases for a record field"""
28
+
29
+ aliases: list[str]
30
+
31
+
16
32
  def get_fully_qualified_name(py_type: type) -> str:
17
33
  """Returns the fully qualified name for a Python type"""
18
34
  module = getattr(py_type, "__module__", None)
@@ -77,3 +93,24 @@ def get_aliases(fqn: str) -> list[str]:
77
93
  if aliases := _ALIASES.get(fqn):
78
94
  return sorted(aliases)
79
95
  return []
96
+
97
+
98
+ def get_field_aliases_and_actual_type(py_type: Type) -> tuple[list[str] | None, Type]:
99
+ """
100
+ Check if a type contains an alias metadata via `Alias` or `Aliases` as metadata.
101
+ It returns the eventual aliases and the type.
102
+ """
103
+ # py_type is not annotated. It can't have aliases
104
+ if get_origin(py_type) is not Annotated:
105
+ return [], py_type
106
+
107
+ args = get_args(py_type)
108
+ actual_type, annotation = args[0], args[1]
109
+
110
+ # Annotated type but not an alias. We do nothing.
111
+ if type(annotation) not in (Alias, Aliases):
112
+ return [], py_type
113
+
114
+ # If the annotated type is an alias, we extract the aliases and return the actual type
115
+ aliases = annotation.aliases if type(annotation) is Aliases else [annotation.alias]
116
+ return aliases, actual_type
@@ -50,7 +50,7 @@ import orjson
50
50
  import typeguard
51
51
 
52
52
  import py_avro_schema._typing
53
- from py_avro_schema._alias import get_aliases
53
+ from py_avro_schema._alias import get_aliases, get_field_aliases_and_actual_type
54
54
 
55
55
  if TYPE_CHECKING:
56
56
  # Pydantic not necessarily required at runtime
@@ -909,6 +909,7 @@ class RecordField:
909
909
  py_type: Type,
910
910
  name: str,
911
911
  namespace: Optional[str],
912
+ aliases: list[str] | None = None,
912
913
  default: Any = dataclasses.MISSING,
913
914
  docs: str = "",
914
915
  options: Option = Option(0),
@@ -919,12 +920,16 @@ class RecordField:
919
920
  :param py_type: The Python class or type
920
921
  :param name: Field name
921
922
  :param namespace: Avro schema namespace
923
+ :param aliases: Aliases for the field
922
924
  :param default: Field default value
923
925
  :param docs: Field documentation or description
924
926
  :param options: Schema generation options
925
927
  """
928
+ if aliases is None:
929
+ aliases = []
926
930
  self.py_type = py_type
927
931
  self.name = name
932
+ self.aliases = aliases
928
933
  self._namespace = namespace
929
934
  self.default = default
930
935
  self.docs = docs
@@ -949,6 +954,8 @@ class RecordField:
949
954
  "name": self.name,
950
955
  "type": self.schema.data(names=names),
951
956
  }
957
+ if self.aliases:
958
+ field_data["aliases"] = sorted(self.aliases)
952
959
  if self.default != dataclasses.MISSING:
953
960
  field_data["default"] = self.schema.make_default(self.default)
954
961
  if self.docs and Option.NO_DOC not in self.options:
@@ -984,11 +991,13 @@ class DataclassSchema(RecordSchema):
984
991
  default = py_field.default
985
992
  if callable(py_field.default_factory): # type: ignore
986
993
  default = py_field.default_factory() # type: ignore
994
+ aliases, actual_type = get_field_aliases_and_actual_type(py_field.type) # type: ignore
987
995
  field_obj = RecordField(
988
- py_type=py_field.type, # type: ignore
996
+ py_type=actual_type, # type: ignore
989
997
  name=py_field.name,
990
998
  namespace=self.namespace_override,
991
999
  default=default,
1000
+ aliases=aliases,
992
1001
  options=self.options,
993
1002
  )
994
1003
 
@@ -1024,11 +1033,13 @@ class PydanticSchema(RecordSchema):
1024
1033
  default = dataclasses.MISSING if py_field.is_required() else py_field.get_default(call_default_factory=True)
1025
1034
  py_type = self._annotation(name)
1026
1035
  record_name = py_field.alias if Option.USE_FIELD_ALIAS in self.options and py_field.alias else name
1036
+ aliases, actual_type = get_field_aliases_and_actual_type(py_type)
1027
1037
  field_obj = RecordField(
1028
- py_type=py_type,
1038
+ py_type=actual_type,
1029
1039
  name=record_name,
1030
1040
  namespace=self.namespace_override,
1031
1041
  default=default,
1042
+ aliases=aliases,
1032
1043
  docs=py_field.description or "",
1033
1044
  options=self.options,
1034
1045
  )
@@ -1087,11 +1098,13 @@ class PlainClassSchema(RecordSchema):
1087
1098
  def _record_field(self, py_field: inspect.Parameter) -> RecordField:
1088
1099
  """Return an Avro record field object for a given Python instance attribute"""
1089
1100
  default = py_field.default if py_field.default != inspect.Parameter.empty else dataclasses.MISSING
1101
+ aliases, actual_type = get_field_aliases_and_actual_type(py_field.annotation)
1090
1102
  field_obj = RecordField(
1091
- py_type=py_field.annotation,
1103
+ py_type=actual_type,
1092
1104
  name=py_field.name,
1093
1105
  namespace=self.namespace_override,
1094
1106
  default=default,
1107
+ aliases=aliases,
1095
1108
  options=self.options,
1096
1109
  )
1097
1110
  return field_obj
@@ -1116,15 +1129,17 @@ class TypedDictSchema(RecordSchema):
1116
1129
  """
1117
1130
  super().__init__(py_type, namespace=namespace, options=options)
1118
1131
  py_type = _type_from_annotated(py_type)
1119
- self.py_fields: dict[str, Type] = get_type_hints(py_type)
1132
+ self.py_fields: dict[str, Type] = get_type_hints(py_type, include_extras=True)
1120
1133
  self.record_fields = [self._record_field(field) for field in self.py_fields.items()]
1121
1134
 
1122
1135
  def _record_field(self, py_field: tuple[str, Type]) -> RecordField:
1123
1136
  """Return an Avro record field object for a given TypedDict field"""
1137
+ aliases, actual_type = get_field_aliases_and_actual_type(py_field[1])
1124
1138
  field_obj = RecordField(
1125
- py_type=py_field[1],
1139
+ py_type=actual_type,
1126
1140
  name=py_field[0],
1127
1141
  namespace=self.namespace_override,
1142
+ aliases=aliases,
1128
1143
  options=self.options,
1129
1144
  )
1130
1145
  return field_obj
@@ -1,12 +0,0 @@
1
- localstack_py_avro_schema-3.9.1.dist-info/licenses/LICENSE,sha256=zE1N4JILDTkSIDtdmqdnKKxKEQh_VdqeoAV2230eNOI,10141
2
- localstack_py_avro_schema-3.9.1.dist-info/licenses/LICENSE_HEADER.txt,sha256=Nx3RGmYbJKjIKM8Y2yMrCPJgCw4oBt_H62PxDKlAsto,564
3
- py_avro_schema/__init__.py,sha256=bK6hTw4XJy5bAP4lgIvJR5LHtEiyUWq4GNJ5w-uSEpc,2414
4
- py_avro_schema/_alias.py,sha256=B0wfiva1ErVna1rpTCjhuvYHhoYOz_-YgcW8C2TrIOo,2360
5
- py_avro_schema/_schemas.py,sha256=uyBheFP-0o7RBBBJVZg2FNxErwoydlgojYc88B51kDg,46237
6
- py_avro_schema/_testing.py,sha256=3aSfMbNDDeatl3H7GEUcynxK81HFiF-WCLOZ3FCCLiw,2261
7
- py_avro_schema/_typing.py,sha256=9kBlPA7C33zZP3nRhCvSJA_0m54uvUo0AQ4wJJmSxMk,3291
8
- py_avro_schema/py.typed,sha256=HnEDkaznpgfRW07Qfogy4tFLu_4dcQ5YcOsI7pmU5rQ,52
9
- localstack_py_avro_schema-3.9.1.dist-info/METADATA,sha256=DcY8xlpg6j-rl_gcSRsoVfdAzmI_S491g83ZBcCyYOo,15164
10
- localstack_py_avro_schema-3.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- localstack_py_avro_schema-3.9.1.dist-info/top_level.txt,sha256=0t4oO_5rsdfZGSk8iSzK4TjdyfVz-RE1IZ2TIz6YvI0,15
12
- localstack_py_avro_schema-3.9.1.dist-info/RECORD,,