Repository: pydantic/pydantic

Issue:
Annotated FieldInfo instance is mutated and reused if using type aliases
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.8.2
            pydantic compiled: False
                 install path: /tmp/tmp.pnuPT7tTpf/lib/python3.9/site-packages/pydantic
               python version: 3.9.6 (default, Jun 30 2021, 10:22:16)  [GCC 11.1.0]
                     platform: Linux-5.13.0-rc7-x86_64-with-glibc2.33
     optional deps. installed: ['devtools', 'typing-extensions']

```


Working version
```py
from pydantic import BaseModel, constr, Field
from typing import Annotated

class MyModel(BaseModel):
  a: Annotated[str, Field(max_length=3)] = ''
  b: Annotated[str, Field(max_length=3)]

MyModel(a="123", b='123', c='123')
MyModel(a="1234", b='1234', c='1234')  # ValidationError
```

Failing version:
```py
from pydantic import BaseModel, constr, Field
from typing import Annotated

StrField3 = Annotated[str, Field(max_length=3)]

class MyModel(BaseModel):  # ValueError: `Field` default cannot be set in `Annotated` for 'b'
  a: StrField3 = ''
  b: StrField3
```

The `.default` is set to `''` on the FieldInfo instance at [fields.py:404](https://github.com/samuelcolvin/pydantic/blob/master/pydantic/fields.py#L404).

This can be fixed by copying the `FieldInfo` before mutating it and associating it with ModelField.
```
from copy import copy
...
class ModelField(Representation):
     ...
    @staticmethod
    def _get_field_info(
        field_name: str, annotation: Any, value: Any, config: Type['BaseConfig']
    ) -> Tuple[FieldInfo, Any]:
          ....
          if field_info is not None:
               field_info = copy(field_info)
               ....
```
ValueError raised when Nested Discriminated Unions are used twice
### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

# Bug

The Following code is copied from [doc](https://pydantic-docs.helpmanual.io/usage/types/#nested-discriminated-unions) as is except for the last `Model2`:
```py
from typing import Literal, Union

from typing_extensions import Annotated

from pydantic import BaseModel, Field, ValidationError


class BlackCat(BaseModel):
    pet_type: Literal["cat"]
    color: Literal["black"]
    black_name: str


class WhiteCat(BaseModel):
    pet_type: Literal["cat"]
    color: Literal["white"]
    white_name: str


# Can also be written with a custom root type
#
# class Cat(BaseModel):
#   __root__: Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]

Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator="color")]


class Dog(BaseModel):
    pet_type: Literal["dog"]
    name: str


Pet = Annotated[Union[Cat, Dog], Field(discriminator="pet_type")]


class Model(BaseModel):
    pet: Pet
    n: int

# The above code is copied as is, the following is added
class Model2(BaseModel):
    pet: Pet
    n: int
```

Run the above code, without the last `Model2`, the code runs well, but with `Model2`, the code throws the following error:

```
Traceback (most recent call last):
  File "/Users/stephen/Work/medieco/hub/hub-api/test.py", line 41, in <module>
    class Model2(BaseModel):
  File "pydantic/main.py", line 204, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/fields.py", line 488, in pydantic.fields.ModelField.infer
  File "pydantic/fields.py", line 419, in pydantic.fields.ModelField.__init__
  File "pydantic/fields.py", line 534, in pydantic.fields.ModelField.prepare
  File "pydantic/fields.py", line 599, in pydantic.fields.ModelField._type_analysis
  File "pydantic/fields.py", line 633, in pydantic.fields.ModelField._type_analysis
  File "pydantic/fields.py", line 776, in pydantic.fields.ModelField._create_sub_type
  File "pydantic/fields.py", line 451, in pydantic.fields.ModelField._get_field_info
ValueError: `Field` default cannot be set in `Annotated` for "pet_Annotated[Union[__main__.BlackCat, __main__.WhiteCat], FieldInfo(discriminator='color', extra={})]"
```

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.9.0
            pydantic compiled: True
                 install path: /Users/stephen/work/.venv/lib/python3.9/site-packages/pydantic
               python version: 3.9.0 (default, Dec 20 2020, 11:26:29)  [Clang 12.0.0 (clang-1200.0.32.28)]
                     platform: macOS-12.1-x86_64-i386-64bit
     optional deps. installed: ['dotenv', 'email-validator', 'typing-extensions']
```



Discussion:
+1 ran into this issue, it's a very valid use case and should be an easy fix

This also will happen if you call `ModelField.infer` twice on the same parameter/field, even if you are not using a type alias
Seems like we are also affected by this issue. Any chance to fix this soon?
@PrettyWood any chance the team can get to this bug soon?
This appears related to #3714.

@Jaakkonen - did you try running the unit tests after changing to a copy of the `FieldInfo` object? I got some failures so it might be that this mutation is required in other places.

```
FAILED tests/test_generics.py::test_replace_types - IndexError: tuple index out of range
FAILED tests/test_schema.py::test_unenforced_constraints_schema[kwargs9-type_9] - IndexError: tuple index out of range

Results (36.46s):
    2287 passed
       2 failed
         - tests/test_generics.py:761 test_replace_types
         - tests/test_schema.py:1520 test_unenforced_constraints_schema[kwargs9-type_9]
       7 skipped
make: *** [Makefile:63: test] Error 1
```
@adriangb any chance you could have a go at a fix? 😄 🙏 

I think this is also causing #3714 which in turn in one of the only remaining blockers to v1.9.1.
BTW, I find that the code works with pydantic 1.8.2, so this problem may be introduced via 1.9.0
I also experience the same behavior as @StephenPCG.

```
C:\dev\pydantic-discrimination-union-problem>python -c "import pydantic.utils; print(pydantic.utils.version_info())"  
             pydantic version: 1.9.0
            pydantic compiled: True
                 install path: C:\Users\jol\venv\pydantic-discrimination-union-problem\Lib\site-packages\pydantic
               python version: 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)]
                     platform: Windows-10-10.0.19043-SP0
     optional deps. installed: ['typing-extensions']
```

I have a bigger example ([source](https://github.com/josteinl/pydantic-discrimination-union-problem/tree/pydantic_1.9.0_trial#yet-another-problem)) using FastAPI.

```python
import enum
from typing import List, Union, Literal

from typing_extensions import Annotated
from fastapi import FastAPI
from pydantic import Field, BaseModel
import uvicorn


class CutleryTypeEnum(enum.IntEnum):
    KNIFE = 1
    FORK = 2
    SPOON = 3


class CutleryBase(BaseModel):
    name: str


class Knife(CutleryBase):
    cutlery_type_id: Literal[CutleryTypeEnum.KNIFE]


class Fork(CutleryBase):
    cutlery_type_id: Literal[CutleryTypeEnum.FORK]
    number_of_teeth: int = None


class Spoon(CutleryBase):
    cutlery_type_id: Literal[CutleryTypeEnum.SPOON]
    volume: float = None


Cutlery = Annotated[
    Union[Knife, Fork, Spoon],
    Field(discriminator='cutlery_type_id'),
]

app = FastAPI()


@app.get("/cutlery",
         response_model=List[Cutlery])
async def get_cutlery():
    return [{'cutlery_type_id': CutleryTypeEnum.KNIFE, 'name': 'My sharp knife'},
            {'cutlery_type_id': CutleryTypeEnum.FORK, 'name': 'The three teeth fork'},
            {'cutlery_type_id': CutleryTypeEnum.SPOON, 'name': 'Tea spoon'}]

#
# Enable the second usage of Cutlery result in traceback on startup
#

@app.get("/cutlery2",
         response_model=List[Cutlery])
async def get_cutlery2():
    return [{'cutlery_type_id': CutleryTypeEnum.KNIFE, 'name': 'My sharp knife'},
            {'cutlery_type_id': CutleryTypeEnum.KNIFE, 'name': 'My knife is sharp'},
            {'cutlery_type_id': CutleryTypeEnum.FORK, 'name': 'The three teeth fork'},
            {'cutlery_type_id': CutleryTypeEnum.SPOON, 'name': 'Tea spoon'}]

uvicorn.run(app, host='127.0.0.1', port=80)
```

If I enable the endpoint /cutlery2 I get the following traceback on start-up:

```
python main.py   
Traceback (most recent call last):
  File "C:\dev\pydantic-discrimination-union-problem\main.py", line 64, in <module>
    async def get_cutlery2():
  File "C:\Users\jol\venv\pydantic-discrimination-union-problem\lib\site-packages\fastapi\routing.py", line 582, in decorator
    self.add_api_route(
  File "C:\Users\jol\venv\pydantic-discrimination-union-problem\lib\site-packages\fastapi\routing.py", line 525, in add_api_route
    route = route_class(
  File "C:\Users\jol\venv\pydantic-discrimination-union-problem\lib\site-packages\fastapi\routing.py", line 351, in __init__
    self.response_field = create_response_field(
  File "C:\Users\jol\venv\pydantic-discrimination-union-problem\lib\site-packages\fastapi\utils.py", line 65, in create_response_field
    return response_field(field_info=field_info)
  File "pydantic\fields.py", line 419, in pydantic.fields.ModelField.__init__
  File "pydantic\fields.py", line 534, in pydantic.fields.ModelField.prepare
  File "pydantic\fields.py", line 728, in pydantic.fields.ModelField._type_analysis
  File "pydantic\fields.py", line 776, in pydantic.fields.ModelField._create_sub_type
  File "pydantic\fields.py", line 451, in pydantic.fields.ModelField._get_field_info
ValueError: `Field` default cannot be set in `Annotated` for '_Response_get_cutlery2_cutlery2_get'
```

Replacing my Cutlery class with this seems to work OK:

```python
class Cutlery(BaseModel):
    __root__: Annotated[
        Union[Knife, Fork, Spoon],
        Field(discriminator='cutlery_type_id'),
    ]

```
> BTW, I find that the code works with pydantic 1.8.2, so this problem may be introduced via 1.9.0

Thanks for that.  Some code working last year with 1.8.2 is not working with 1.9.0 and it looks like this is the reason.
Also experiencing this error. In my case the error appears when I use an Annotated field twice in the same model. Using Pydantic v1.9.0.

```
from typing import Literal, Union, List
from typing_extensions import Annotated
from pydantic import BaseModel, Field 

class Cat(BaseModel):
    pet_type: Literal['cat']

class Dog(BaseModel):
    pet_type: Literal['dog']

Pets = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]  # noqa: F821

class Model(BaseModel):
    pets_field1: Pets
    pets_field2: List[Pets]
```
`ValueError: 'Field' default cannot be set in 'Annotated' for 'pets_field2'`
I think the fundamental problem here is the usage of our `Annotated` class inside a `List[]`. The code around this is way too complicated for me to understand.

@havardthom have you tried to define `Pets` as:

```python
class Pets(BaseModel):
    __root__: Annotated[
        Union[Cat, Dog],
        Field(discriminator='pet_type'),
    ]
```
> I think the fundamental problem here is the usage of our `Annotated` class inside a `List[]`. The code around this is way too complicated for me to understand.
> 
> @havardthom have you tried to define `Pets` as:
> 
> ```python
> class Pets(BaseModel):
>     __root__: Annotated[
>         Union[Cat, Dog],
>         Field(discriminator='pet_type'),
>     ]
> ```

Yes I got it working with root model, although I had to refactor parts of my solution to extract the `__root__` object. A pure annotation field would be preferable.
I confirm that the following code runs smoothly in pydantic 1.8.2 but raises an error (as noted) in 1.9.0
```python
from __future__ import annotations

from typing import List
from typing import Literal
from typing import Union

from pydantic import BaseModel
from pydantic import Field
from typing_extensions import Annotated


class A(BaseModel):
    operator: Literal["a"]
    foo: str
    cs: List[C]


class B(BaseModel):
    operator: Literal["b"]
    foo: str
    cs: List[C]


C = Annotated[
    Union[A, B],
    Field(discriminator="operator"),
]

A.update_forward_refs()

# B is not prepared and requires call to update_forward_refs
# but this will yield a ValueError in pydantic 1.9.0
B.update_forward_refs()
# ValueError: `Field` default cannot be set in `Annotated` for '_cs'
````

Using a redefinition of `C` like suggested above (and in the docs)
```python
class C(BaseModel):
  __root__: Annotated[Union[A, B], Field(discriminator="operator")]
```
does make the code run. However, it seems to also require some additional imho rather ugly attribute access
```python
b = B(operator="b", foo="foo", cs=[A(operator="a", foo="bar", cs=[]] )
print( b.cs[0].__root__.bar ) # instead of previously b.cs[0].bar
``` 
Ran into this too, I've tried to see if I can understand what is causing this as `DiscriminatedUnion` is a super useful feature!

Looks like this is a regression that was previously [caught here](https://github.com/samuelcolvin/pydantic/pull/2147/commits/6835179f5c431fa071dd50dfdc31685f2463a23c).

The issue seems to be that the FieldInfo/Annotation is being mutated to set the default, which means on the second time around this is getting tripped up

The regression appears to be introduced here when the `__eq__` was fixed: #2502 (specifically [this line](https://github.com/samuelcolvin/pydantic/blob/master/pydantic/fields.py#L454))

@PrettyWood / @JacobHayes - do you think it would it make sense to have a way of cloning the object here to avoid mutating the object here? Or would it be possible to avoid setting the `default` value here all together? #
I've tested a fix where we do not mutate the original `FieldInfo` object, but this causes failures else where (in tests that do not directly call `_get_field_info`) so the it looks like the copy/new is a no go unfortunately.
Appears to be related to this too #3702

Resolving patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -1,3 +1,4 @@
+import copy
 from collections import Counter as CollectionCounter, defaultdict, deque
 from collections.abc import Hashable as CollectionsHashable, Iterable as CollectionsIterable
 from typing import (
@@ -446,6 +447,7 @@ def _get_field_info(
                 raise ValueError(f'cannot specify multiple `Annotated` `Field`s for {field_name!r}')
             field_info = next(iter(field_infos), None)
             if field_info is not None:
+                field_info = copy.copy(field_info)
                 field_info.update_from_config(field_info_from_config)
                 if field_info.default is not Undefined:
                     raise ValueError(f'`Field` default cannot be set in `Annotated` for {field_name!r}')


Test patch:
diff --git a/tests/test_annotated.py b/tests/test_annotated.py
--- a/tests/test_annotated.py
+++ b/tests/test_annotated.py
@@ -1,3 +1,5 @@
+from typing import List
+
 import pytest
 from typing_extensions import Annotated
 
@@ -132,3 +134,21 @@ class Config:
     assert Foo.schema(by_alias=True)['properties'] == {
         'a': {'title': 'A', 'description': 'descr', 'foobar': 'hello', 'type': 'integer'},
     }
+
+
+def test_annotated_alias() -> None:
+    # https://github.com/samuelcolvin/pydantic/issues/2971
+
+    StrAlias = Annotated[str, Field(max_length=3)]
+    IntAlias = Annotated[int, Field(default_factory=lambda: 2)]
+
+    Nested = Annotated[List[StrAlias], Field(description='foo')]
+
+    class MyModel(BaseModel):
+        a: StrAlias = 'abc'
+        b: StrAlias
+        c: IntAlias
+        d: IntAlias
+        e: Nested
+
+    assert MyModel(b='def', e=['xyz']) == MyModel(a='abc', b='def', c=2, d=2, e=['xyz'])