Skip to content

Commit

Permalink
Refactor literal type implementation and mixed type checking
Browse files Browse the repository at this point in the history
  • Loading branch information
faph committed Sep 2, 2024
1 parent 3066f31 commit a82608f
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 16 deletions.
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

0 comments on commit a82608f

Please sign in to comment.