Repository: koxudaxi/datamodel-code-generator

Issue:
--enum-field-as-literal=one converts integer to string - still
**Describe the bug**
When using `--enum-field-as-literal=one`, literal integers get converted to strings, depending on the exact version of `datamodel`'s dependencies which are installed. For details see the bottom of the description. This is highly problematic when using `datamodel-code-generator` as a library, as it's output changes unpredictably depending on which exact version of other dependencies are installed.

This is not a duplicate of https://github.com/koxudaxi/datamodel-code-generator/issues/440 .

**To Reproduce**

Example schema:
```json
    {
        "title": "SomeModel",
        "type": "object",
        "properties": {
            "attribute": {
                "title": "Attribute",
                "enum": [
                    1
                ],
                "type": "integer"
            }
        },
        "required": [
            "attribute"
        ]
    }
```

Used commandline:
```
$ datamodel-codegen --input file.json --enum-field-as-literal=one
```

**Expected behavior**
I expected the result to look something like
```
class SomeModel(BaseModel):
    attribute: Literal[1] = Field(..., title='Attribute')
```
instead it looks like
```
class SomeModel(BaseModel):
    attribute: Literal['1'] = Field(..., title='Attribute')
```

**Version:**
 - OS: Linux
 - Python version: 3.8.0
 - datamodel-code-generator version: 0.11.16

**Additional context**
The problem seems to lie in https://github.com/koxudaxi/datamodel-code-generator/blob/e2dcb199fc6da3c22aa5df4dd209721f1e71507e/datamodel_code_generator/types.py#L78 

Python caches specified generics - see also https://bugs.python.org/issue45679 -, which means that if
```
List[Union[str, int]]
```
was used in some dependency _before_ python parses this part, `List[Union[int, str]]` magically becomes `List[Union[str, int]]`. This is turn makes pydantic parse `[1]` to `['1']`. Whether or not `List[Union[str, int]]` was parsed by python before parsing `types.py` depends on the exact version of the dependencies which are installed.

For an example of this type caching, the following code runs without error in python 3.8:
```
from typing import List, Union

List[Union[str, int]]

assert str(List[Union[int, str]]) == "typing.List[typing.Union[str, int]]"
```
For how this can confuse pydantic, also the following code runs without error in python 3.8 with pydantic version 1.9.0:
```
from pydantic import BaseModel

from typing import List, Literal, Union

List[Union[str, int]]

class SomeModel(BaseModel):
    literals: List[Union[int, str]]

my_instance = SomeModel(literals=[1])

assert type(my_instance.literals[0]) == str
```
See also the warning in https://pydantic-docs.helpmanual.io/usage/types/#unions


Discussion:
Using the new pydantic 1.9 feature https://pydantic-docs.helpmanual.io/usage/model_config/#smart-union in `DataType` fixes the problem.
As an easier solution, one can just put the type in quotes to make the error disappear. I'll send an MR.

Resolving patch:
diff --git a/datamodel_code_generator/types.py b/datamodel_code_generator/types.py
--- a/datamodel_code_generator/types.py
+++ b/datamodel_code_generator/types.py
@@ -75,7 +75,7 @@ class Config:
     is_dict: bool = False
     is_list: bool = False
     is_custom_type: bool = False
-    literals: List[Union[int, str]] = []
+    literals: 'List[Union[int, str]]' = []
     use_standard_collections: bool = False
     use_generic_container: bool = False
     alias: Optional[str] = None


Test patch:
diff --git a/tests/data/expected/main/main_openapi_enum_models_all/output.py b/tests/data/expected/main/main_openapi_enum_models_all/output.py
--- a/tests/data/expected/main/main_openapi_enum_models_all/output.py
+++ b/tests/data/expected/main/main_openapi_enum_models_all/output.py
@@ -16,6 +16,7 @@ class Pet(BaseModel):
     tag: Optional[str] = None
     kind: Optional[Literal['dog', 'cat']] = None
     type: Optional[Literal['animal']] = None
+    number: Literal[1]
 
 
 class Pets(BaseModel):
diff --git a/tests/data/expected/main/main_openapi_enum_models_as_literal_py37/output.py b/tests/data/expected/main/main_openapi_enum_models_as_literal_py37/output.py
--- a/tests/data/expected/main/main_openapi_enum_models_as_literal_py37/output.py
+++ b/tests/data/expected/main/main_openapi_enum_models_as_literal_py37/output.py
@@ -17,6 +17,7 @@ class Pet(BaseModel):
     tag: Optional[str] = None
     kind: Optional[Literal['dog', 'cat']] = None
     type: Optional[Literal['animal']] = None
+    number: Literal[1]
 
 
 class Pets(BaseModel):
diff --git a/tests/data/expected/main/main_openapi_enum_models_one/output.py b/tests/data/expected/main/main_openapi_enum_models_one/output.py
--- a/tests/data/expected/main/main_openapi_enum_models_one/output.py
+++ b/tests/data/expected/main/main_openapi_enum_models_one/output.py
@@ -21,6 +21,7 @@ class Pet(BaseModel):
     tag: Optional[str] = None
     kind: Optional[Kind] = None
     type: Optional[Literal['animal']] = None
+    number: Literal[1]
 
 
 class Pets(BaseModel):
diff --git a/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py36.py b/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py36.py
--- a/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py36.py
+++ b/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py36.py
@@ -13,12 +13,17 @@ class Type(Enum):
     animal = 'animal'
 
 
+class Number(Enum):
+    integer_1 = 1
+
+
 class Pet(BaseModel):
     id: int
     name: str
     tag: Optional[str] = None
     kind: Optional['Kind'] = None
     type: Optional['Type'] = None
+    number: 'Number'
 
 
 class Pets(BaseModel):
diff --git a/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py37.py b/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py37.py
--- a/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py37.py
+++ b/tests/data/expected/parser/openapi/openapi_parser_parse_enum_models/output_py37.py
@@ -15,12 +15,17 @@ class Type(Enum):
     animal = 'animal'
 
 
+class Number(Enum):
+    integer_1 = 1
+
+
 class Pet(BaseModel):
     id: int
     name: str
     tag: Optional[str] = None
     kind: Optional[Kind] = None
     type: Optional[Type] = None
+    number: Number
 
 
 class Pets(BaseModel):
diff --git a/tests/data/openapi/enum_models.yaml b/tests/data/openapi/enum_models.yaml
--- a/tests/data/openapi/enum_models.yaml
+++ b/tests/data/openapi/enum_models.yaml
@@ -51,6 +51,7 @@ components:
       required:
         - id
         - name
+        - number
       properties:
         id:
           type: integer
@@ -65,6 +66,10 @@ components:
         type:
           type: string
           enum: [ 'animal' ]
+        number:
+          type: integer
+          enum: [ 1 ]
+
     Pets:
       type: array
       items:
diff --git a/tests/parser/test_openapi.py b/tests/parser/test_openapi.py
--- a/tests/parser/test_openapi.py
+++ b/tests/parser/test_openapi.py
@@ -2,6 +2,7 @@
 from pathlib import Path
 from typing import List, Optional
 
+import pydantic
 import pytest
 
 from datamodel_code_generator import PythonVersion
@@ -378,6 +379,9 @@ class UnknownTypeNumber(Enum):
     )
 
 
+@pytest.mark.skipif(
+    pydantic.VERSION < '1.9.0', reason='Require Pydantic version 1.9.0 or later '
+)
 def test_openapi_parser_parse_enum_models():
     parser = OpenAPIParser(
         Path(DATA_PATH / 'enum_models.yaml').read_text(),
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -5,6 +5,7 @@
 from unittest.mock import call
 
 import isort
+import pydantic
 import pytest
 from _pytest.capture import CaptureFixture
 from _pytest.tmpdir import TempdirFactory
@@ -1824,6 +1825,9 @@ def test_main_jsonschema_multiple_files_json_pointer():
         main()
 
 
+@pytest.mark.skipif(
+    pydantic.VERSION < '1.9.0', reason='Require Pydantic version 1.9.0 or later '
+)
 @freeze_time('2019-07-26')
 def test_main_openapi_enum_models_as_literal_one():
     with TemporaryDirectory() as output_dir:
@@ -1853,6 +1857,9 @@ def test_main_openapi_enum_models_as_literal_one():
         main()
 
 
+@pytest.mark.skipif(
+    pydantic.VERSION < '1.9.0', reason='Require Pydantic version 1.9.0 or later '
+)
 @freeze_time('2019-07-26')
 def test_main_openapi_enum_models_as_literal_all():
     with TemporaryDirectory() as output_dir:
@@ -1882,6 +1889,9 @@ def test_main_openapi_enum_models_as_literal_all():
         main()
 
 
+@pytest.mark.skipif(
+    pydantic.VERSION < '1.9.0', reason='Require Pydantic version 1.9.0 or later '
+)
 @freeze_time('2019-07-26')
 def test_main_openapi_enum_models_as_literal_py37(capsys):
     with TemporaryDirectory() as output_dir: