localstack-py-avro-schema 3.9.2__tar.gz → 3.9.4__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.2 → localstack_py_avro_schema-3.9.4}/PKG-INFO +1 -1
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/localstack_py_avro_schema.egg-info/PKG-INFO +1 -1
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_schemas.py +69 -26
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_plain_class.py +13 -2
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_primitives.py +11 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.editorconfig +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.flake8 +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.github/workflows/release-package.yml +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.github/workflows/test-package.yml +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.gitignore +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.pre-commit-config.yaml +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/.readthedocs.yaml +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/LICENSE +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/LICENSE_HEADER.txt +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/README.md +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/conf.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/index.rst +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/modules.rst +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/py_avro_schema.rst +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/tutorial.rst +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/docs/types.rst +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/pyproject.toml +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/setup.cfg +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/localstack_py_avro_schema.egg-info/SOURCES.txt +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/localstack_py_avro_schema.egg-info/dependency_links.txt +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/localstack_py_avro_schema.egg-info/requires.txt +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/localstack_py_avro_schema.egg-info/top_level.txt +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/__init__.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_alias.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_testing.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_typing.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/py.typed +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_avro_schema.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_dataclass.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_logicals.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_pydantic.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_typed_dict.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_typing.py +0 -0
- {localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tox.ini +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.4
|
|
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.4
|
|
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
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_schemas.py
RENAMED
|
@@ -45,9 +45,11 @@ from typing import (
|
|
|
45
45
|
get_type_hints,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
import avro.name
|
|
48
49
|
import more_itertools
|
|
49
50
|
import orjson
|
|
50
51
|
import typeguard
|
|
52
|
+
from avro.errors import InvalidName
|
|
51
53
|
|
|
52
54
|
import py_avro_schema._typing
|
|
53
55
|
from py_avro_schema._alias import get_aliases, get_field_aliases_and_actual_type
|
|
@@ -135,9 +137,10 @@ JSON_OPTIONS = [opt for opt in Option if opt.name and opt.name.startswith("JSON_
|
|
|
135
137
|
_SCHEMA_CLASSES = []
|
|
136
138
|
|
|
137
139
|
|
|
138
|
-
def register_schema(cls):
|
|
140
|
+
def register_schema(cls: type | None = None, *, priority: int = 0):
|
|
139
141
|
"""
|
|
140
142
|
Decorator to register a class as a known ``Schema``
|
|
143
|
+
It also accept a priority value to sort the list of schemas. Default schemas have priority 0.
|
|
141
144
|
|
|
142
145
|
Schema classes are instantiated when calling ``schema``. Example use::
|
|
143
146
|
|
|
@@ -147,10 +150,15 @@ def register_schema(cls):
|
|
|
147
150
|
def handles_type(cls, py_type: Type) -> bool:
|
|
148
151
|
...
|
|
149
152
|
...
|
|
150
|
-
|
|
151
153
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
|
|
155
|
+
def _wrapper(_cls):
|
|
156
|
+
"""Wrapper function to attach priority and sort the list of schemas."""
|
|
157
|
+
_cls.__py_avro_priority = priority
|
|
158
|
+
_SCHEMA_CLASSES.append(_cls)
|
|
159
|
+
return _cls
|
|
160
|
+
|
|
161
|
+
return _wrapper if not cls else _wrapper(cls)
|
|
154
162
|
|
|
155
163
|
|
|
156
164
|
def schema(
|
|
@@ -186,7 +194,7 @@ def _schema_obj(py_type: Type, namespace: Optional[str] = None, options: Option
|
|
|
186
194
|
:param options: Schema generation options.
|
|
187
195
|
"""
|
|
188
196
|
# Find concrete Schema subclasses defined in the current module
|
|
189
|
-
for schema_class in _SCHEMA_CLASSES:
|
|
197
|
+
for schema_class in sorted(_SCHEMA_CLASSES, key=lambda c: getattr(c, "__py_avro_priority", 0)):
|
|
190
198
|
# Find the first schema class that handles py_type
|
|
191
199
|
schema_obj = schema_class(py_type, namespace=namespace, options=options) # type: ignore
|
|
192
200
|
if schema_obj:
|
|
@@ -843,19 +851,33 @@ class EnumSchema(NamedSchema):
|
|
|
843
851
|
if symbol_types != {str}:
|
|
844
852
|
raise TypeError(f"Avro enum schema members must be strings. {py_type} uses {symbol_types} values.")
|
|
845
853
|
|
|
854
|
+
def _is_valid_enum(self) -> bool:
|
|
855
|
+
"""Checks if all the symbols of the enum are valid Avro names."""
|
|
856
|
+
try:
|
|
857
|
+
for _symbol in self.symbols:
|
|
858
|
+
avro.name.validate_basename(_symbol)
|
|
859
|
+
except InvalidName:
|
|
860
|
+
return False
|
|
861
|
+
return True
|
|
862
|
+
|
|
846
863
|
def data_before_deduplication(self, names: NamesType) -> JSONObj:
|
|
847
864
|
"""Return the schema data"""
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
"
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
865
|
+
if not self._is_valid_enum():
|
|
866
|
+
# Special case for StrEnum might contain invalid avro names. We just use a StrSubclassSchema schema instead.
|
|
867
|
+
enum_schema = {"type": "string", "namedString": self.name}
|
|
868
|
+
else:
|
|
869
|
+
enum_schema = {
|
|
870
|
+
"type": "enum",
|
|
871
|
+
"name": self.name,
|
|
872
|
+
"symbols": self.symbols,
|
|
873
|
+
# This is the default for the enum, not the default value for a record field using the enum type!
|
|
874
|
+
# See Avro schema specification for use. For now, we force the default value to be the first symbol.
|
|
875
|
+
# This means that if the writer schema has an additional member that the reader schema does NOT have,
|
|
876
|
+
# the reader will simply and silently assume the default specified here.
|
|
877
|
+
# Now that may not always be what we want, but standard lib Python enums don't really have a way
|
|
878
|
+
# to specify this.
|
|
879
|
+
"default": self.symbols[0],
|
|
880
|
+
}
|
|
859
881
|
if self.namespace is not None:
|
|
860
882
|
enum_schema["namespace"] = self.namespace
|
|
861
883
|
fqn = f"{self.namespace}.{self.name}"
|
|
@@ -1064,7 +1086,15 @@ class PydanticSchema(RecordSchema):
|
|
|
1064
1086
|
|
|
1065
1087
|
@register_schema
|
|
1066
1088
|
class PlainClassSchema(RecordSchema):
|
|
1067
|
-
"""
|
|
1089
|
+
"""
|
|
1090
|
+
An Avro record schema for a plain Python class with type annotations, e.g.,
|
|
1091
|
+
::
|
|
1092
|
+
class MyClass:
|
|
1093
|
+
var: str
|
|
1094
|
+
|
|
1095
|
+
def __init__(self, var: str = "foo"):
|
|
1096
|
+
self.var = var
|
|
1097
|
+
"""
|
|
1068
1098
|
|
|
1069
1099
|
@classmethod
|
|
1070
1100
|
def handles_type(cls, py_type: Type) -> bool:
|
|
@@ -1077,13 +1107,13 @@ class PlainClassSchema(RecordSchema):
|
|
|
1077
1107
|
and not hasattr(py_type, "__pydantic_private__")
|
|
1078
1108
|
# If we are subclassing a string, used the "named string" approach
|
|
1079
1109
|
and (inspect.isclass(py_type) and not issubclass(py_type, str))
|
|
1080
|
-
#
|
|
1081
|
-
and bool(get_type_hints(py_type
|
|
1110
|
+
# and any other class with typed annotations
|
|
1111
|
+
and bool(get_type_hints(py_type))
|
|
1082
1112
|
)
|
|
1083
1113
|
|
|
1084
1114
|
def __init__(self, py_type: Type, namespace: Optional[str] = None, options: Option = Option(0)):
|
|
1085
1115
|
"""
|
|
1086
|
-
An Avro record schema for a plain Python class with
|
|
1116
|
+
An Avro record schema for a plain Python class with type hints
|
|
1087
1117
|
|
|
1088
1118
|
:param py_type: The Python class to generate a schema for.
|
|
1089
1119
|
:param namespace: The Avro namespace to add to schemas.
|
|
@@ -1091,17 +1121,30 @@ class PlainClassSchema(RecordSchema):
|
|
|
1091
1121
|
"""
|
|
1092
1122
|
super().__init__(py_type, namespace=namespace, options=options)
|
|
1093
1123
|
py_type = _type_from_annotated(py_type)
|
|
1094
|
-
|
|
1095
|
-
self.py_fields =
|
|
1124
|
+
|
|
1125
|
+
self.py_fields: list[tuple[str, type]] = []
|
|
1126
|
+
for k, v in py_type.__annotations__.items():
|
|
1127
|
+
self.py_fields.append((k, v))
|
|
1128
|
+
# We store __init__ parameters with default values. They can be used as defaults for the record.
|
|
1129
|
+
self.signature_fields = {
|
|
1130
|
+
param.name: (param.annotation, param.default)
|
|
1131
|
+
for param in list(inspect.signature(py_type.__init__).parameters.values())[1:]
|
|
1132
|
+
if param.default is not inspect._empty
|
|
1133
|
+
}
|
|
1096
1134
|
self.record_fields = [self._record_field(field) for field in self.py_fields]
|
|
1097
1135
|
|
|
1098
|
-
def _record_field(self, py_field:
|
|
1136
|
+
def _record_field(self, py_field: tuple[str, Type]) -> RecordField:
|
|
1099
1137
|
"""Return an Avro record field object for a given Python instance attribute"""
|
|
1100
|
-
|
|
1101
|
-
|
|
1138
|
+
aliases, actual_type = get_field_aliases_and_actual_type(py_field[1])
|
|
1139
|
+
name = py_field[0]
|
|
1140
|
+
default = dataclasses.MISSING
|
|
1141
|
+
if field := self.signature_fields.get(name):
|
|
1142
|
+
_annotation, _default = field
|
|
1143
|
+
if actual_type is _annotation:
|
|
1144
|
+
default = _default
|
|
1102
1145
|
field_obj = RecordField(
|
|
1103
1146
|
py_type=actual_type,
|
|
1104
|
-
name=
|
|
1147
|
+
name=name,
|
|
1105
1148
|
namespace=self.namespace_override,
|
|
1106
1149
|
default=default,
|
|
1107
1150
|
aliases=aliases,
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_plain_class.py
RENAMED
|
@@ -24,6 +24,11 @@ def test_plain_class_with_type_hints():
|
|
|
24
24
|
class PyType:
|
|
25
25
|
"""A port, not using dataclass"""
|
|
26
26
|
|
|
27
|
+
name: str
|
|
28
|
+
country: str
|
|
29
|
+
latitude: float
|
|
30
|
+
longitude: float
|
|
31
|
+
|
|
27
32
|
def __init__(
|
|
28
33
|
self, name: str, *, country: str = "NLD", latitude: float, longitude: float
|
|
29
34
|
): # note the non-default kwargs near the end!
|
|
@@ -65,6 +70,8 @@ def test_plain_class_annotated():
|
|
|
65
70
|
class PyType:
|
|
66
71
|
"""A port, not using dataclass"""
|
|
67
72
|
|
|
73
|
+
name: str
|
|
74
|
+
|
|
68
75
|
def __init__(self, name: str):
|
|
69
76
|
self.name = name
|
|
70
77
|
|
|
@@ -84,7 +91,9 @@ def test_plain_class_annotated():
|
|
|
84
91
|
|
|
85
92
|
def test_field_alias():
|
|
86
93
|
class PyType:
|
|
87
|
-
|
|
94
|
+
name: Annotated[str, Alias("old_name")]
|
|
95
|
+
|
|
96
|
+
def __init__(self, name):
|
|
88
97
|
self.name = name
|
|
89
98
|
|
|
90
99
|
expected = {
|
|
@@ -105,7 +114,9 @@ def test_plain_class_no_type_hints():
|
|
|
105
114
|
class PyType:
|
|
106
115
|
"""A port, not using dataclass"""
|
|
107
116
|
|
|
108
|
-
def __init__(
|
|
117
|
+
def __init__(
|
|
118
|
+
self, name: str, *, country: str = "NLD", latitude: float, longitude: float
|
|
119
|
+
): # note the non-default kwargs near the end!
|
|
109
120
|
self.name = name
|
|
110
121
|
self.country = country.upper()
|
|
111
122
|
self.latitude = latitude
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_primitives.py
RENAMED
|
@@ -508,3 +508,14 @@ def test_enum_docs():
|
|
|
508
508
|
"default": "RED",
|
|
509
509
|
}
|
|
510
510
|
assert_schema(PyType, expected, do_doc=True)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def test_str_enum_invalid_name():
|
|
514
|
+
class OriginProtocolPolicy(str, enum.Enum):
|
|
515
|
+
http_only = "http-only"
|
|
516
|
+
match_viewer = "match-viewer"
|
|
517
|
+
https_only = "https-only"
|
|
518
|
+
|
|
519
|
+
expected = {"namedString": "OriginProtocolPolicy", "type": "string"}
|
|
520
|
+
|
|
521
|
+
assert_schema(OriginProtocolPolicy, expected)
|
|
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
|
|
File without changes
|
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/__init__.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_alias.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_testing.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/_typing.py
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/src/py_avro_schema/py.typed
RENAMED
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_avro_schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{localstack_py_avro_schema-3.9.2 → localstack_py_avro_schema-3.9.4}/tests/test_typed_dict.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|