Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs + refactor for Literal #79

Merged
merged 2 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,14 @@ Outputs as:
}


:class:`typing.Literal`
~~~~~~~~~~~~~~~~~~~~~~~

Avro schema: schema corresponding to the type of the literal value, e.g. ``string``, ``long`` etc.

Mixed types, e.g. ``Literal["", 42]`` are not supported.


:class:`uuid.UUID`
~~~~~~~~~~~~~~~~~~

Expand Down
23 changes: 10 additions & 13 deletions src/py_avro_schema/_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
List,
Literal,
Optional,
Set,
Tuple,
Type,
Union,
Expand Down Expand Up @@ -312,39 +311,37 @@ def data(self, names: NamesType) -> JSONObj:


class LiteralSchema(Schema):
"""An Avro schema of any type for a Python Literal type, e.g. Literal[""]"""
"""An Avro schema of any type for a Python Literal type, e.g. ``Literal[""]``"""

def __init__(self, py_type: Type[Any], namespace: Optional[str] = None, options: Option = Option(0)):
"""
An Avro schema of any type for a Python Literal type, e.g. Literal[""]
An Avro schema of any type for a Python Literal type, e.g. ``Literal[""]``

:param py_type: The Python class to generate a schema for.
:param namespace: The Avro namespace to add to schemas.
:param options: Schema generation options.
"""
super().__init__(py_type, namespace=namespace, options=options)
py_type = _type_from_annotated(py_type)
literal_type = self._literal_value_types(py_type).pop()
# For now we support Literals with the same type only. Potentially we could explose multiple literal types into
# an Avro Union schema, but that may not be something that anyone would every want to use...
try:
(literal_type,) = {type(literal_value) for literal_value in get_args(py_type)}
except ValueError: # Too many values to unpack
raise TypeError("Cannot generate Avro schema for Python typing.Literal with mixed type values")

self.literal_value_schema = _schema_obj(literal_type, namespace=namespace, options=options)

@classmethod
def handles_type(cls, py_type: Type[Any]) -> bool:
"""Whether this schema class can represent a given Python class"""
py_type = _type_from_annotated(py_type)
literal_value_types = cls._literal_value_types(py_type)
# For now we support Literals with the same type only. Potentially we could explose multiple literal types into
# an Avro Union schema, but that may not be something that anyone would every want to use...
return get_origin(py_type) is Literal and len(literal_value_types) == 1
return get_origin(py_type) is Literal

def data(self, names: NamesType) -> JSONType:
"""Return the schema data"""
return self.literal_value_schema.data(names=names)

@staticmethod
def _literal_value_types(py_type) -> Set[Type[Any]]:
"""Return the Python types corresponding to the literal values"""
return {type(literal_value) for literal_value in get_args(py_type)}


class DictAsJSONSchema(Schema):
"""An Avro string schema representing a Python Dict[str, Any] or List[Dict[str, Any]] assuming JSON serialization"""
Expand Down
8 changes: 5 additions & 3 deletions tests/test_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,11 @@ def test_union_of_union_string_int():

def test_literal_different_types():
py_type = Literal["", 42]
expected = {}
with pytest.raises(pas.TypeNotSupportedError):
assert_schema(py_type, expected)
with pytest.raises(
TypeError,
match=re.escape("Cannot generate Avro schema for Python typing.Literal with mixed type values"),
):
py_avro_schema._schemas.schema(py_type)


def test_optional_str():
Expand Down