Repository: pydantic/pydantic

Issue:
Multi-value Literal isn't compliant with OpenAPI
Multi-value Literal returns `const` in the JSON sub-schema of an `anyOf`, which isn't supported by OpenAPI:

> anyOf – the subschemas must be OpenAPI schemas and not standard JSON Schemas.


Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.4
            pydantic compiled: False
                 install path: /Users/[...]/venv/lib/python3.7/site-packages/pydantic
               python version: 3.7.4 (default, Sep  7 2019, 18:27:02)  [Clang 10.0.1 (clang-1001.0.46.4)]
                     platform: Darwin-18.7.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']

```

# Code to reproduce

```python
from pydantic import BaseModel
from typing_extensions import Literal

class FooBar(BaseModel):
    values: Literal["value_1", "value_2"]

print(FooBar.schema_json(indent=2))
```

output: 
```
{
  "title": "FooBar",
  "type": "object",
  "properties": {
    "values": {
      "title": "Values",
      "anyOf": [
        {
          "const": "value_1",
          "type": "string"
        },
        {
          "const": "value_2",
          "type": "string"
        }
      ]
    }
  },
  "required": [
    "values"
  ]
}
```

Literal with only one value is OpenAPI compliant btw

This issue is linked to: https://github.com/tiangolo/fastapi/issues/562, https://github.com/samuelcolvin/pydantic/pull/649 (the simple `enum` would work well)


Comments:
This is not really a bug in Pydantic. Pydantic generates valid JSON Schema.

OpenAPI uses an old version of JSON Schema that only supports enums.

But you can change your model to use an enum instead: https://pydantic-docs.helpmanual.io/usage/types/#enums-and-choices

It achieves the same and will generate a JSON Schema with enums, that is valid with OpenAPI.
Should we change pydantic so literal with multiple choices matches enum?
@tiangolo I agree, my concern is more about the benefit of the Literal type as a one-liner choice on a Pydantic/FastAPI/Swagger UI context.

@samuelcolvin If it breaks too many things, I would suggest a simple note in the documentation to avoid this unexpected behaviour (Literal with one element works well in fastapi context but not with multiple elements)
Please add a pr to improve the documentation.

We should change literals in V2.
> Should we change pydantic so literal with multiple choices matches enum?

@samuelcolvin actually that makes sense, a `Literal` generates a JSON Schema `const`, and it could only have a single value. So, yes, a multi-valued `Literal` *should* create a JSON Schema equivalent to an enum.
okay, do you think the change represents a "breaking change" that needs to wait for v2, or a bug fix than can go out in a minor version?
Sorry for the delay (still figuring out how to handle GitHub email notifications), I think that looks like a "bug fix" or extra feature that could go in a minor version.
Is there any progress? I have the same problem.
Not yet, would you like to submit a PR.
Hi guys, thanks for the research on this topics.

For us, this is right now a blocker to go to production (not fulfilling good practices in documentation)...
So would you mind to provide me a small hint on where to start in the code, I will take a look and let see if I can do a PR 😊 

Thanks again for the efforts!

@JavierMartinezAlc it looks like it is implemented [here](https://github.com/samuelcolvin/pydantic/blob/master/pydantic/schema.py#L778) and/or [there](https://github.com/samuelcolvin/pydantic/blob/master/pydantic/schema.py#L834)

Patch:
diff --git a/pydantic/schema.py b/pydantic/schema.py
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -1,5 +1,6 @@
 import re
 import warnings
+from collections import defaultdict
 from datetime import date, datetime, time, timedelta
 from decimal import Decimal
 from enum import Enum
@@ -64,12 +65,12 @@
 from .typing import (
     NONE_TYPES,
     ForwardRef,
+    all_literal_values,
     get_args,
     get_origin,
     is_callable_type,
     is_literal_type,
     is_namedtuple,
-    literal_values,
 )
 from .utils import ROOT_KEY, get_model, lenient_issubclass, sequence_like
 
@@ -789,19 +790,21 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
         f_schema['const'] = field.default
     field_type = field.type_
     if is_literal_type(field_type):
-        values = literal_values(field_type)
-        if len(values) > 1:
+        values = all_literal_values(field_type)
+
+        if len({v.__class__ for v in values}) > 1:
             return field_schema(
-                multivalue_literal_field_for_schema(values, field),
+                multitypes_literal_field_for_schema(values, field),
                 by_alias=by_alias,
                 model_name_map=model_name_map,
                 ref_prefix=ref_prefix,
                 ref_template=ref_template,
                 known_models=known_models,
             )
-        literal_value = values[0]
-        field_type = literal_value.__class__
-        f_schema['const'] = literal_value
+
+        # All values have the same type
+        field_type = values[0].__class__
+        f_schema['enum'] = list(values)
 
     if lenient_issubclass(field_type, Enum):
         enum_name = model_name_map[field_type]
@@ -859,10 +862,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: ModelField) -> ModelField:
+def multitypes_literal_field_for_schema(values: Tuple[Any, ...], field: ModelField) -> ModelField:
+    """
+    To support `Literal` with values of different types, we split it into multiple `Literal` with same type
+    e.g. `Literal['qwe', 'asd', 1, 2]` becomes `Union[Literal['qwe', 'asd'], Literal[1, 2]]`
+    """
+    literal_distinct_types = defaultdict(list)
+    for v in values:
+        literal_distinct_types[v.__class__].append(v)
+    distinct_literals = (Literal[tuple(same_type_values)] for same_type_values in literal_distinct_types.values())
+
     return ModelField(
         name=field.name,
-        type_=Union[tuple(Literal[value] for value in values)],  # type: ignore
+        type_=Union[tuple(distinct_literals)],  # type: ignore
         class_validators=field.class_validators,
         model_config=field.model_config,
         default=field.default,
diff --git a/pydantic/typing.py b/pydantic/typing.py
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -203,7 +203,7 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
     'resolve_annotations',
     'is_callable_type',
     'is_literal_type',
-    'literal_values',
+    'all_literal_values',
     'is_namedtuple',
     'is_typeddict',
     'is_new_type',


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1759,14 +1759,22 @@ class Model(BaseModel):
         a: Literal[1]
         b: Literal['a']
         c: Literal['a', 1]
+        d: Literal['a', Literal['b'], 1, 2]
 
     assert Model.schema() == {
         'properties': {
-            'a': {'title': 'A', 'type': 'integer', 'const': 1},
-            'b': {'title': 'B', 'type': 'string', 'const': 'a'},
-            'c': {'anyOf': [{'type': 'string', 'const': 'a'}, {'type': 'integer', 'const': 1}], 'title': 'C'},
+            'a': {'title': 'A', 'type': 'integer', 'enum': [1]},
+            'b': {'title': 'B', 'type': 'string', 'enum': ['a']},
+            'c': {'title': 'C', 'anyOf': [{'type': 'string', 'enum': ['a']}, {'type': 'integer', 'enum': [1]}]},
+            'd': {
+                'title': 'D',
+                'anyOf': [
+                    {'type': 'string', 'enum': ['a', 'b']},
+                    {'type': 'integer', 'enum': [1, 2]},
+                ],
+            },
         },
-        'required': ['a', 'b', 'c'],
+        'required': ['a', 'b', 'c', 'd'],
         'title': 'Model',
         'type': 'object',
     }