localstack-py-avro-schema 3.9.0__tar.gz → 3.9.2__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.
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.github/workflows/test-package.yml +1 -3
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/PKG-INFO +1 -1
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/localstack_py_avro_schema.egg-info/PKG-INFO +1 -1
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/localstack_py_avro_schema.egg-info/SOURCES.txt +1 -0
- localstack_py_avro_schema-3.9.2/src/py_avro_schema/_alias.py +116 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_schemas.py +27 -5
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_avro_schema.py +21 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_dataclass.py +26 -2
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_plain_class.py +24 -1
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_pydantic.py +19 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_typed_dict.py +29 -3
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tox.ini +0 -4
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.editorconfig +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.flake8 +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.github/workflows/release-package.yml +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.gitignore +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.pre-commit-config.yaml +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/.readthedocs.yaml +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/LICENSE +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/LICENSE_HEADER.txt +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/README.md +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/conf.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/index.rst +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/modules.rst +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/py_avro_schema.rst +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/tutorial.rst +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/docs/types.rst +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/pyproject.toml +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/setup.cfg +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/localstack_py_avro_schema.egg-info/dependency_links.txt +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/localstack_py_avro_schema.egg-info/requires.txt +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/localstack_py_avro_schema.egg-info/top_level.txt +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/__init__.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_testing.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_typing.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/py.typed +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_logicals.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_primitives.py +0 -0
- {localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_typing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-py-avro-schema
|
|
3
|
-
Version: 3.9.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: localstack-py-avro-schema
|
|
3
|
-
Version: 3.9.
|
|
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
|
|
@@ -22,6 +22,7 @@ src/localstack_py_avro_schema.egg-info/dependency_links.txt
|
|
|
22
22
|
src/localstack_py_avro_schema.egg-info/requires.txt
|
|
23
23
|
src/localstack_py_avro_schema.egg-info/top_level.txt
|
|
24
24
|
src/py_avro_schema/__init__.py
|
|
25
|
+
src/py_avro_schema/_alias.py
|
|
25
26
|
src/py_avro_schema/_schemas.py
|
|
26
27
|
src/py_avro_schema/_testing.py
|
|
27
28
|
src/py_avro_schema/_typing.py
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to register aliases for Python types
|
|
3
|
+
|
|
4
|
+
This module maintains global state via the _ALIASES registry.
|
|
5
|
+
Decorators will modify this state when applied to classes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import dataclasses
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from typing import Annotated, Type, get_args, get_origin
|
|
11
|
+
|
|
12
|
+
FQN = str
|
|
13
|
+
"""Fully qualified name for a Python type"""
|
|
14
|
+
_ALIASES: dict[FQN, set[FQN]] = defaultdict(set)
|
|
15
|
+
"""Maps the FQN of a Python type to a set of aliases"""
|
|
16
|
+
|
|
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
|
+
|
|
32
|
+
def get_fully_qualified_name(py_type: type) -> str:
|
|
33
|
+
"""Returns the fully qualified name for a Python type"""
|
|
34
|
+
module = getattr(py_type, "__module__", None)
|
|
35
|
+
qualname = getattr(py_type, "__qualname__", py_type.__name__)
|
|
36
|
+
|
|
37
|
+
# py-avro-schema does not consider <locals> in the namespace.
|
|
38
|
+
# we skip it here as well for consistency
|
|
39
|
+
if module and "<locals>" in qualname:
|
|
40
|
+
return f"{module}.{py_type.__name__}"
|
|
41
|
+
|
|
42
|
+
if module and module not in ("builtins", "__main__"):
|
|
43
|
+
return f"{module}.{qualname}"
|
|
44
|
+
return qualname
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_type_aliases(aliases: list[FQN]):
|
|
48
|
+
"""
|
|
49
|
+
Decorator to register aliases for a given type.
|
|
50
|
+
It allows for compatible schemas following a change type (e.g., a rename), if the type fields do not
|
|
51
|
+
change in an incompatible way.
|
|
52
|
+
|
|
53
|
+
Example::
|
|
54
|
+
@register_type_aliases(aliases=["py_avro_schema.OldAddress"])
|
|
55
|
+
class Address(TypedDict):
|
|
56
|
+
street: str
|
|
57
|
+
number: int
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def _wrapper(cls):
|
|
61
|
+
"""Wrapper function that updates the aliases dictionary"""
|
|
62
|
+
fqn = get_fully_qualified_name(cls)
|
|
63
|
+
_ALIASES[fqn].update(aliases)
|
|
64
|
+
return cls
|
|
65
|
+
|
|
66
|
+
return _wrapper
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def register_type_alias(alias: FQN):
|
|
70
|
+
"""
|
|
71
|
+
Decorator to register a single alias for a given type.
|
|
72
|
+
It allows for compatible schemas following a change type (e.g., a rename), if the type fields do not
|
|
73
|
+
change in an incompatible way.
|
|
74
|
+
|
|
75
|
+
Example::
|
|
76
|
+
@register_type_alias(alias="py_avro_schema.OldAddress")
|
|
77
|
+
class Address(TypedDict):
|
|
78
|
+
street: str
|
|
79
|
+
number: int
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def _wrapper(cls):
|
|
83
|
+
"""Wrapper function that updates the aliases dictionary"""
|
|
84
|
+
fqn = get_fully_qualified_name(cls)
|
|
85
|
+
_ALIASES[fqn].add(alias)
|
|
86
|
+
return cls
|
|
87
|
+
|
|
88
|
+
return _wrapper
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_aliases(fqn: str) -> list[str]:
|
|
92
|
+
"""Returns the list of aliases for a given type"""
|
|
93
|
+
if aliases := _ALIASES.get(fqn):
|
|
94
|
+
return sorted(aliases)
|
|
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
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_schemas.py
RENAMED
|
@@ -50,6 +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, get_field_aliases_and_actual_type
|
|
53
54
|
|
|
54
55
|
if TYPE_CHECKING:
|
|
55
56
|
# Pydantic not necessarily required at runtime
|
|
@@ -857,6 +858,9 @@ class EnumSchema(NamedSchema):
|
|
|
857
858
|
}
|
|
858
859
|
if self.namespace is not None:
|
|
859
860
|
enum_schema["namespace"] = self.namespace
|
|
861
|
+
fqn = f"{self.namespace}.{self.name}"
|
|
862
|
+
if aliases := get_aliases(fqn):
|
|
863
|
+
enum_schema["aliases"] = aliases
|
|
860
864
|
if Option.NO_DOC not in self.options:
|
|
861
865
|
doc = _doc_for_class(self.py_type)
|
|
862
866
|
if doc:
|
|
@@ -887,6 +891,9 @@ class RecordSchema(NamedSchema):
|
|
|
887
891
|
}
|
|
888
892
|
if self.namespace is not None:
|
|
889
893
|
record_schema["namespace"] = self.namespace
|
|
894
|
+
fqn = f"{self.namespace}.{self.name}"
|
|
895
|
+
if aliases := get_aliases(fqn):
|
|
896
|
+
record_schema["aliases"] = aliases
|
|
890
897
|
if Option.NO_DOC not in self.options:
|
|
891
898
|
doc = _doc_for_class(self.py_type)
|
|
892
899
|
if doc:
|
|
@@ -902,6 +909,7 @@ class RecordField:
|
|
|
902
909
|
py_type: Type,
|
|
903
910
|
name: str,
|
|
904
911
|
namespace: Optional[str],
|
|
912
|
+
aliases: list[str] | None = None,
|
|
905
913
|
default: Any = dataclasses.MISSING,
|
|
906
914
|
docs: str = "",
|
|
907
915
|
options: Option = Option(0),
|
|
@@ -912,12 +920,16 @@ class RecordField:
|
|
|
912
920
|
:param py_type: The Python class or type
|
|
913
921
|
:param name: Field name
|
|
914
922
|
:param namespace: Avro schema namespace
|
|
923
|
+
:param aliases: Aliases for the field
|
|
915
924
|
:param default: Field default value
|
|
916
925
|
:param docs: Field documentation or description
|
|
917
926
|
:param options: Schema generation options
|
|
918
927
|
"""
|
|
928
|
+
if aliases is None:
|
|
929
|
+
aliases = []
|
|
919
930
|
self.py_type = py_type
|
|
920
931
|
self.name = name
|
|
932
|
+
self.aliases = aliases
|
|
921
933
|
self._namespace = namespace
|
|
922
934
|
self.default = default
|
|
923
935
|
self.docs = docs
|
|
@@ -942,6 +954,8 @@ class RecordField:
|
|
|
942
954
|
"name": self.name,
|
|
943
955
|
"type": self.schema.data(names=names),
|
|
944
956
|
}
|
|
957
|
+
if self.aliases:
|
|
958
|
+
field_data["aliases"] = sorted(self.aliases)
|
|
945
959
|
if self.default != dataclasses.MISSING:
|
|
946
960
|
field_data["default"] = self.schema.make_default(self.default)
|
|
947
961
|
if self.docs and Option.NO_DOC not in self.options:
|
|
@@ -977,11 +991,13 @@ class DataclassSchema(RecordSchema):
|
|
|
977
991
|
default = py_field.default
|
|
978
992
|
if callable(py_field.default_factory): # type: ignore
|
|
979
993
|
default = py_field.default_factory() # type: ignore
|
|
994
|
+
aliases, actual_type = get_field_aliases_and_actual_type(py_field.type) # type: ignore
|
|
980
995
|
field_obj = RecordField(
|
|
981
|
-
py_type=
|
|
996
|
+
py_type=actual_type, # type: ignore
|
|
982
997
|
name=py_field.name,
|
|
983
998
|
namespace=self.namespace_override,
|
|
984
999
|
default=default,
|
|
1000
|
+
aliases=aliases,
|
|
985
1001
|
options=self.options,
|
|
986
1002
|
)
|
|
987
1003
|
|
|
@@ -1017,11 +1033,13 @@ class PydanticSchema(RecordSchema):
|
|
|
1017
1033
|
default = dataclasses.MISSING if py_field.is_required() else py_field.get_default(call_default_factory=True)
|
|
1018
1034
|
py_type = self._annotation(name)
|
|
1019
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)
|
|
1020
1037
|
field_obj = RecordField(
|
|
1021
|
-
py_type=
|
|
1038
|
+
py_type=actual_type,
|
|
1022
1039
|
name=record_name,
|
|
1023
1040
|
namespace=self.namespace_override,
|
|
1024
1041
|
default=default,
|
|
1042
|
+
aliases=aliases,
|
|
1025
1043
|
docs=py_field.description or "",
|
|
1026
1044
|
options=self.options,
|
|
1027
1045
|
)
|
|
@@ -1080,11 +1098,13 @@ class PlainClassSchema(RecordSchema):
|
|
|
1080
1098
|
def _record_field(self, py_field: inspect.Parameter) -> RecordField:
|
|
1081
1099
|
"""Return an Avro record field object for a given Python instance attribute"""
|
|
1082
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)
|
|
1083
1102
|
field_obj = RecordField(
|
|
1084
|
-
py_type=
|
|
1103
|
+
py_type=actual_type,
|
|
1085
1104
|
name=py_field.name,
|
|
1086
1105
|
namespace=self.namespace_override,
|
|
1087
1106
|
default=default,
|
|
1107
|
+
aliases=aliases,
|
|
1088
1108
|
options=self.options,
|
|
1089
1109
|
)
|
|
1090
1110
|
return field_obj
|
|
@@ -1109,15 +1129,17 @@ class TypedDictSchema(RecordSchema):
|
|
|
1109
1129
|
"""
|
|
1110
1130
|
super().__init__(py_type, namespace=namespace, options=options)
|
|
1111
1131
|
py_type = _type_from_annotated(py_type)
|
|
1112
|
-
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)
|
|
1113
1133
|
self.record_fields = [self._record_field(field) for field in self.py_fields.items()]
|
|
1114
1134
|
|
|
1115
1135
|
def _record_field(self, py_field: tuple[str, Type]) -> RecordField:
|
|
1116
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])
|
|
1117
1138
|
field_obj = RecordField(
|
|
1118
|
-
py_type=
|
|
1139
|
+
py_type=actual_type,
|
|
1119
1140
|
name=py_field[0],
|
|
1120
1141
|
namespace=self.namespace_override,
|
|
1142
|
+
aliases=aliases,
|
|
1121
1143
|
options=self.options,
|
|
1122
1144
|
)
|
|
1123
1145
|
return field_obj
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_avro_schema.py
RENAMED
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
# specific language governing permissions and limitations under the License.
|
|
11
11
|
|
|
12
12
|
import dataclasses
|
|
13
|
+
import json
|
|
14
|
+
from typing import TypedDict
|
|
13
15
|
|
|
14
16
|
import avro.schema
|
|
15
17
|
import orjson
|
|
16
18
|
|
|
17
19
|
import py_avro_schema as pas
|
|
20
|
+
from py_avro_schema._alias import register_type_alias, register_type_aliases
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def test_package_has_version():
|
|
@@ -43,3 +46,21 @@ def test_dataclass_string_field():
|
|
|
43
46
|
json_data = pas.generate(PyType)
|
|
44
47
|
assert json_data == orjson.dumps(expected)
|
|
45
48
|
assert avro.schema.parse(json_data)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_avro_type_aliases():
|
|
52
|
+
@register_type_aliases(aliases=["test_avro_schema.VeryOldDict", "test_avro_schema.OldDict"])
|
|
53
|
+
class PyTypedDict(TypedDict):
|
|
54
|
+
value: str
|
|
55
|
+
|
|
56
|
+
json_data = pas.generate(PyTypedDict)
|
|
57
|
+
assert json.loads(json_data)["aliases"] == ["test_avro_schema.OldDict", "test_avro_schema.VeryOldDict"]
|
|
58
|
+
|
|
59
|
+
register_type_alias(alias="test_avro_schema.SuperOldDict")(PyTypedDict)
|
|
60
|
+
pas.generate.cache_clear()
|
|
61
|
+
json_data = pas.generate(PyTypedDict)
|
|
62
|
+
assert json.loads(json_data)["aliases"] == [
|
|
63
|
+
"test_avro_schema.OldDict",
|
|
64
|
+
"test_avro_schema.SuperOldDict",
|
|
65
|
+
"test_avro_schema.VeryOldDict",
|
|
66
|
+
]
|
|
@@ -19,6 +19,7 @@ from typing import Annotated, Dict, List, Optional, Tuple
|
|
|
19
19
|
import pytest
|
|
20
20
|
|
|
21
21
|
import py_avro_schema as pas
|
|
22
|
+
from py_avro_schema._alias import Alias, register_type_aliases
|
|
22
23
|
from py_avro_schema._testing import assert_schema
|
|
23
24
|
|
|
24
25
|
|
|
@@ -400,6 +401,7 @@ def test_dataclass_repeated_string_field():
|
|
|
400
401
|
|
|
401
402
|
|
|
402
403
|
def test_dataclass_repeated_enum_field():
|
|
404
|
+
@register_type_aliases(aliases=["test_dataclass.OldEnum"])
|
|
403
405
|
class PyTypeEnum(enum.Enum):
|
|
404
406
|
RED = "RED"
|
|
405
407
|
GREEN = "GREEN"
|
|
@@ -412,12 +414,15 @@ def test_dataclass_repeated_enum_field():
|
|
|
412
414
|
expected = {
|
|
413
415
|
"type": "record",
|
|
414
416
|
"name": "PyType",
|
|
417
|
+
"namespace": "test_dataclass",
|
|
415
418
|
"fields": [
|
|
416
419
|
{
|
|
417
420
|
"name": "field_child_1",
|
|
418
421
|
"type": {
|
|
419
422
|
"type": "enum",
|
|
420
423
|
"name": "PyTypeEnum",
|
|
424
|
+
"namespace": "test_dataclass",
|
|
425
|
+
"aliases": ["test_dataclass.OldEnum"],
|
|
421
426
|
"symbols": [
|
|
422
427
|
"RED",
|
|
423
428
|
"GREEN",
|
|
@@ -427,11 +432,11 @@ def test_dataclass_repeated_enum_field():
|
|
|
427
432
|
},
|
|
428
433
|
{
|
|
429
434
|
"name": "field_child_2",
|
|
430
|
-
"type": "PyTypeEnum",
|
|
435
|
+
"type": "test_dataclass.PyTypeEnum",
|
|
431
436
|
},
|
|
432
437
|
],
|
|
433
438
|
}
|
|
434
|
-
assert_schema(PyType, expected)
|
|
439
|
+
assert_schema(PyType, expected, do_auto_namespace=True)
|
|
435
440
|
|
|
436
441
|
|
|
437
442
|
def test_self_ref_field():
|
|
@@ -839,3 +844,22 @@ def test_sequence_schema_defaults_with_items():
|
|
|
839
844
|
"type": "record",
|
|
840
845
|
}
|
|
841
846
|
assert_schema(PyType, expected)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def test_field_alias():
|
|
850
|
+
@dataclasses.dataclass
|
|
851
|
+
class PyType:
|
|
852
|
+
field_b: Annotated[str, Alias("field_a")]
|
|
853
|
+
|
|
854
|
+
expected = {
|
|
855
|
+
"type": "record",
|
|
856
|
+
"name": "PyType",
|
|
857
|
+
"fields": [
|
|
858
|
+
{
|
|
859
|
+
"aliases": ["field_a"],
|
|
860
|
+
"name": "field_b",
|
|
861
|
+
"type": "string",
|
|
862
|
+
}
|
|
863
|
+
],
|
|
864
|
+
}
|
|
865
|
+
assert_schema(PyType, expected)
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_plain_class.py
RENAMED
|
@@ -15,10 +15,12 @@ from typing import Annotated
|
|
|
15
15
|
import pytest
|
|
16
16
|
|
|
17
17
|
import py_avro_schema
|
|
18
|
+
from py_avro_schema._alias import Alias, register_type_aliases
|
|
18
19
|
from py_avro_schema._testing import assert_schema
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def test_plain_class_with_type_hints():
|
|
23
|
+
@register_type_aliases(aliases=["test_plain_class.OldPyType"])
|
|
22
24
|
class PyType:
|
|
23
25
|
"""A port, not using dataclass"""
|
|
24
26
|
|
|
@@ -33,6 +35,8 @@ def test_plain_class_with_type_hints():
|
|
|
33
35
|
expected = {
|
|
34
36
|
"type": "record",
|
|
35
37
|
"name": "PyType",
|
|
38
|
+
"namespace": "test_plain_class",
|
|
39
|
+
"aliases": ["test_plain_class.OldPyType"],
|
|
36
40
|
"fields": [
|
|
37
41
|
{
|
|
38
42
|
"name": "name",
|
|
@@ -54,7 +58,7 @@ def test_plain_class_with_type_hints():
|
|
|
54
58
|
],
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
assert_schema(PyType, expected)
|
|
61
|
+
assert_schema(PyType, expected, do_auto_namespace=True)
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
def test_plain_class_annotated():
|
|
@@ -78,6 +82,25 @@ def test_plain_class_annotated():
|
|
|
78
82
|
assert_schema(Annotated[PyType, ...], expected)
|
|
79
83
|
|
|
80
84
|
|
|
85
|
+
def test_field_alias():
|
|
86
|
+
class PyType:
|
|
87
|
+
def __init__(self, name: Annotated[str, Alias("old_name")]):
|
|
88
|
+
self.name = name
|
|
89
|
+
|
|
90
|
+
expected = {
|
|
91
|
+
"type": "record",
|
|
92
|
+
"name": "PyType",
|
|
93
|
+
"fields": [
|
|
94
|
+
{
|
|
95
|
+
"aliases": ["old_name"],
|
|
96
|
+
"name": "name",
|
|
97
|
+
"type": "string",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}
|
|
101
|
+
assert_schema(PyType, expected)
|
|
102
|
+
|
|
103
|
+
|
|
81
104
|
def test_plain_class_no_type_hints():
|
|
82
105
|
class PyType:
|
|
83
106
|
"""A port, not using dataclass"""
|
|
@@ -17,6 +17,7 @@ import pydantic
|
|
|
17
17
|
import pytest
|
|
18
18
|
|
|
19
19
|
import py_avro_schema as pas
|
|
20
|
+
from py_avro_schema._alias import Alias
|
|
20
21
|
from py_avro_schema._testing import assert_schema
|
|
21
22
|
|
|
22
23
|
|
|
@@ -496,6 +497,24 @@ def test_annotated_decimal():
|
|
|
496
497
|
assert_schema(PyType, expected)
|
|
497
498
|
|
|
498
499
|
|
|
500
|
+
def test_field_alias():
|
|
501
|
+
class PyType(pydantic.BaseModel):
|
|
502
|
+
field_a: Annotated[str, Alias("field_b")]
|
|
503
|
+
|
|
504
|
+
expected = {
|
|
505
|
+
"type": "record",
|
|
506
|
+
"name": "PyType",
|
|
507
|
+
"fields": [
|
|
508
|
+
{
|
|
509
|
+
"aliases": ["field_b"],
|
|
510
|
+
"name": "field_a",
|
|
511
|
+
"type": "string",
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
}
|
|
515
|
+
assert_schema(PyType, expected)
|
|
516
|
+
|
|
517
|
+
|
|
499
518
|
def test_annotated_decimal_in_base():
|
|
500
519
|
class Base(pydantic.BaseModel):
|
|
501
520
|
field_a: Annotated[
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_typed_dict.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from typing import TypedDict
|
|
1
|
+
from typing import Annotated, TypedDict
|
|
2
2
|
|
|
3
|
+
from py_avro_schema._alias import Alias, register_type_alias
|
|
3
4
|
from py_avro_schema._testing import assert_schema
|
|
4
5
|
|
|
5
6
|
|
|
@@ -27,8 +28,9 @@ def test_typed_dict():
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def test_type_dict_nested():
|
|
31
|
+
@register_type_alias("test_typed_dict.OldAddress")
|
|
30
32
|
class Address(TypedDict):
|
|
31
|
-
street: str
|
|
33
|
+
street: Annotated[str, Alias("address")]
|
|
32
34
|
number: int
|
|
33
35
|
|
|
34
36
|
class User(TypedDict):
|
|
@@ -39,6 +41,7 @@ def test_type_dict_nested():
|
|
|
39
41
|
expected = {
|
|
40
42
|
"type": "record",
|
|
41
43
|
"name": "User",
|
|
44
|
+
"namespace": "test_typed_dict",
|
|
42
45
|
"fields": [
|
|
43
46
|
{
|
|
44
47
|
"name": "name",
|
|
@@ -49,13 +52,36 @@ def test_type_dict_nested():
|
|
|
49
52
|
"name": "address",
|
|
50
53
|
"type": {
|
|
51
54
|
"name": "Address",
|
|
55
|
+
"namespace": "test_typed_dict",
|
|
56
|
+
"aliases": ["test_typed_dict.OldAddress"],
|
|
52
57
|
"type": "record",
|
|
53
58
|
"fields": [
|
|
54
|
-
{"name": "street", "type": "string"},
|
|
59
|
+
{"aliases": ["address"], "name": "street", "type": "string"},
|
|
55
60
|
{"name": "number", "type": "long"},
|
|
56
61
|
],
|
|
57
62
|
},
|
|
58
63
|
},
|
|
59
64
|
],
|
|
60
65
|
}
|
|
66
|
+
assert_schema(User, expected, do_auto_namespace=True)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_field_alias():
|
|
70
|
+
class User(TypedDict):
|
|
71
|
+
name: Annotated[str, Alias("username")]
|
|
72
|
+
age: int
|
|
73
|
+
|
|
74
|
+
expected = {
|
|
75
|
+
"type": "record",
|
|
76
|
+
"name": "User",
|
|
77
|
+
"fields": [
|
|
78
|
+
{
|
|
79
|
+
"aliases": ["username"],
|
|
80
|
+
"name": "name",
|
|
81
|
+
"type": "string",
|
|
82
|
+
},
|
|
83
|
+
{"name": "age", "type": "long"},
|
|
84
|
+
],
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
assert_schema(User, expected)
|
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
[tox]
|
|
16
16
|
envlist =
|
|
17
|
-
py39
|
|
18
|
-
py310
|
|
19
17
|
py311
|
|
20
18
|
py312
|
|
21
19
|
py313
|
|
@@ -28,8 +26,6 @@ skip_missing_interpreters = true
|
|
|
28
26
|
[gh-actions]
|
|
29
27
|
# Mapping from GitHub Actions Python versions to Tox environments
|
|
30
28
|
python =
|
|
31
|
-
3.9: py39
|
|
32
|
-
3.10: py310
|
|
33
29
|
3.11: py311
|
|
34
30
|
3.12: py312
|
|
35
31
|
3.13: py313, linting, docs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/__init__.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_testing.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/_typing.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/src/py_avro_schema/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{localstack_py_avro_schema-3.9.0 → localstack_py_avro_schema-3.9.2}/tests/test_primitives.py
RENAMED
|
File without changes
|
|
File without changes
|