diff --git a/docs/types.rst b/docs/types.rst index 2802558..65b8787 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -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` ~~~~~~~~~~~~~~~~~~ diff --git a/src/py_avro_schema/_schemas.py b/src/py_avro_schema/_schemas.py index db2692b..6701e87 100644 --- a/src/py_avro_schema/_schemas.py +++ b/src/py_avro_schema/_schemas.py @@ -36,7 +36,6 @@ List, Literal, Optional, - Set, Tuple, Type, Union, @@ -312,11 +311,11 @@ 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. @@ -324,27 +323,25 @@ def __init__(self, py_type: Type[Any], namespace: Optional[str] = None, 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""" diff --git a/tests/test_primitives.py b/tests/test_primitives.py index ebd56aa..7653414 100644 --- a/tests/test_primitives.py +++ b/tests/test_primitives.py @@ -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():