diff --git a/HISTORY.rst b/HISTORY.rst index 7632f8a94f..72f0be4167 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,10 @@ History ------- +v0.32.3 (unreleased) +.................... +* fix error messages for ``Literal`` types with multiple allowed values, #770 by @dmontagu + v0.32.2 (2019-08-17) .................... * fix ``__post_init__`` usage with dataclass inheritance, fix #739 by @samuelcolvin diff --git a/pydantic/fields.py b/pydantic/fields.py index 848ac1c5d6..5b9181f001 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -31,7 +31,6 @@ display_as_type, is_literal_type, lenient_issubclass, - literal_values, sequence_like, ) from .validators import NoneType, constant_validator, dict_validator, find_validators @@ -197,11 +196,7 @@ def _populate_sub_fields(self) -> None: # noqa: C901 (ignore complexity) # python 3.7 only, Pattern is a typing object but without sub fields return if is_literal_type(self.type_): - values = literal_values(self.type_) - if len(values) > 1: - self.type_ = Union[tuple(Literal[value] for value in values)] - else: - return + return origin = getattr(self.type_, '__origin__', None) if origin is None: # field is not "typing" object eg. Union, Dict, List etc. diff --git a/pydantic/schema.py b/pydantic/schema.py index c62959789e..8be7d4ac77 100644 --- a/pydantic/schema.py +++ b/pydantic/schema.py @@ -49,6 +49,7 @@ constr, ) from .utils import ( + Literal, is_callable_type, is_literal_type, is_new_type, @@ -758,8 +759,16 @@ def field_singleton_schema( # noqa: C901 (ignore complexity) if is_new_type(field_type): field_type = new_type_supertype(field_type) if is_literal_type(field_type): - # If there were multiple literal values, field.sub_fields would not be falsy - literal_value = literal_values(field_type)[0] + values = literal_values(field_type) + if len(values) > 1: + return field_schema( + multivalue_literal_field_for_schema(values, field), + by_alias=by_alias, + model_name_map=model_name_map, + ref_prefix=ref_prefix, + known_models=known_models, + ) + literal_value = values[0] field_type = type(literal_value) f_schema['const'] = literal_value if issubclass(field_type, Enum): @@ -807,6 +816,19 @@ def field_singleton_schema( # noqa: C901 (ignore complexity) raise ValueError(f'Value not declarable with JSON Schema, field: {field}') +def multivalue_literal_field_for_schema(values: Tuple[Any, ...], field: Field) -> Field: + return Field( + name=field.name, + type_=Union[tuple(Literal[value] for value in values)], + class_validators=field.class_validators, + model_config=field.model_config, + default=field.default, + required=field.required, + alias=field.alias, + schema=field.schema, + ) + + def encode_default(dft: Any) -> Any: if isinstance(dft, (int, float, str)): return dft diff --git a/tests/test_types.py b/tests/test_types.py index c330774152..af908d4289 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1730,14 +1730,8 @@ class Model(BaseModel): assert exc_info.value.errors() == [ { 'loc': ('a_or_b',), - 'msg': "unexpected value; permitted: 'a'", + 'msg': "unexpected value; permitted: 'a', 'b'", 'type': 'value_error.const', - 'ctx': {'given': 'c', 'permitted': ('a',)}, - }, - { - 'loc': ('a_or_b',), - 'msg': "unexpected value; permitted: 'b'", - 'type': 'value_error.const', - 'ctx': {'given': 'c', 'permitted': ('b',)}, - }, + 'ctx': {'given': 'c', 'permitted': ('a', 'b')}, + } ]