Repository: strawberry-graphql/strawberry

Issue:
With pydantic v2 a field marked as Optional[conint] throws an exception
## Description

I experienced this issue with `Optional[conint]`, but I suspect this will happen with any builtin pydantic field whose type ends up being an `Annotated` type.  I was able to reproduce this in the existing tests within `tests/experimental/pydantic/test_fields.py`, test method `test_conint`.  Just replace `field: pydantic.conint(lt=100)` with `field: Optional[pydantic.conint(lt=100)]`.  The exception is copied below in the additional context.

At least in my case I can work around this by just creating a validator for my field instead of using `conint`.  I only have one field that uses it.  However, it's definitely not ideal.

Example code below to see the error:

```python
class Model(pydantic.BaseModel):
    field: Optional[pydantic.conint(lt=100)]

@strawberry.experimental.pydantic.type(model=Model)
class Type:
    field: strawberry.auto
```

## System Information

 - Operating system: Mac OS
 - Strawberry version:  Need to use a new enough version for pydantic v2 support.

## Additional Context

I'm guessing we need to add some additional logic inside `replace_types_recursively` that handles the `Annotated` type more gracefully?  One thing that was interesting was when I was debugging, the `Annotated` type only comes in with `Optional`. If I just directly use `conint`, the type comes in as just an `int`.  I don't have a lot of experience with python type code so I was afraid to try and put in a fix.

<!-- Add any other relevant information about the problem here. -->
```
tests/experimental/pydantic/test_fields.py:92 (test_conint)
@needs_pydantic_v2
    def test_conint():
        class Model(pydantic.BaseModel):
            field: Optional[pydantic.conint(lt=100)]
    
        @strawberry.experimental.pydantic.type(Model)
>       class Type:

test_fields.py:99: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../strawberry/experimental/pydantic/object_type.py:183: in wrap
    all_model_fields: List[DataclassCreationFields] = [
../../../strawberry/experimental/pydantic/object_type.py:184: in <listcomp>
    _build_dataclass_creation_fields(
../../../strawberry/experimental/pydantic/object_type.py:67: in _build_dataclass_creation_fields
    get_type_for_field(field, is_input)
../../../strawberry/experimental/pydantic/object_type.py:49: in get_type_for_field
    replaced_type = replace_types_recursively(outer_type, is_input)
../../../strawberry/experimental/pydantic/fields.py:172: in replace_types_recursively
    converted = tuple(
../../../strawberry/experimental/pydantic/fields.py:173: in <genexpr>
    replace_types_recursively(t, is_input=is_input) for t in get_args(replaced_type)
../../../strawberry/experimental/pydantic/fields.py:180: in replace_types_recursively
    replaced_type = replaced_type.copy_with(converted)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = typing.Annotated[int, None, Interval(gt=None, ge=None, lt=100, le=None), None]
params = (<class 'int'>, None, Interval(gt=None, ge=None, lt=100, le=None), None)

    def copy_with(self, params):
>       assert len(params) == 1
E       AssertionError
```

<!-- POLAR PLEDGE BADGE START -->
## Upvote & Fund

- We're using [Polar.sh](https://polar.sh/strawberry-graphql) so you can upvote and help fund this issue.
- We receive the funding once the issue is completed & confirmed by you.
- Thank you in advance for helping prioritize & fund our backlog.

<a href="https://polar.sh/strawberry-graphql/strawberry/issues/3107">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/strawberry-graphql/strawberry/issues/3107/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/strawberry-graphql/strawberry/issues/3107/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:
can you add a tiny example code and the full error? 
 
hopefully this is easy to fix :D
@patrick91 Is what I added what you were looking for?  I was just copy/pasting the output from the terminal for the `pytest` run.  As I mentioned above it should be really easy to reproduce in the existing test in `test_fields.py`.
> @patrick91 Is what I added what you were looking for? I was just copy/pasting the output from the terminal for the `pytest` run. As I mentioned above it should be really easy to reproduce in the existing test in `test_fields.py`.

yes! thanks :)

Resolving patch:
diff --git a/strawberry/experimental/pydantic/fields.py b/strawberry/experimental/pydantic/fields.py
--- a/strawberry/experimental/pydantic/fields.py
+++ b/strawberry/experimental/pydantic/fields.py
@@ -1,6 +1,7 @@
 import builtins
 from decimal import Decimal
 from typing import Any, Dict, List, Optional, Type, Union
+from typing_extensions import Annotated
 from uuid import UUID
 
 import pydantic
@@ -42,6 +43,7 @@
     else:
         raise
 
+
 ATTR_TO_TYPE_MAP = {
     "NoneStr": Optional[str],
     "NoneBytes": Optional[bytes],
@@ -178,6 +180,11 @@ def replace_types_recursively(type_: Any, is_input: bool) -> Any:
         return TypingGenericAlias(origin, converted)
     if isinstance(replaced_type, TypingUnionType):
         return Union[converted]
+
+    # TODO: investigate if we could move the check for annotated to the top
+    if origin is Annotated and converted:
+        converted = (converted[0],)
+
     replaced_type = replaced_type.copy_with(converted)
 
     if isinstance(replaced_type, StrawberryObjectDefinition):


Test patch:
diff --git a/tests/experimental/pydantic/test_basic.py b/tests/experimental/pydantic/test_basic.py
--- a/tests/experimental/pydantic/test_basic.py
+++ b/tests/experimental/pydantic/test_basic.py
@@ -1,6 +1,7 @@
 import dataclasses
 from enum import Enum
 from typing import Any, List, Optional, Union
+from typing_extensions import Annotated
 
 import pydantic
 import pytest
@@ -914,3 +915,42 @@ class UserType:
 
     assert field2.python_name == "public"
     assert not field2.metadata
+
+
+def test_annotated():
+    class User(pydantic.BaseModel):
+        a: Annotated[int, "metadata"]
+
+    @strawberry.experimental.pydantic.input(User, all_fields=True)
+    class UserType:
+        pass
+
+    definition: StrawberryObjectDefinition = UserType.__strawberry_definition__
+    assert definition.name == "UserType"
+
+    [field] = definition.fields
+    assert field.python_name == "a"
+    assert field.type is int
+
+
+def test_nested_annotated():
+    class User(pydantic.BaseModel):
+        a: Optional[Annotated[int, "metadata"]]
+        b: Optional[List[Annotated[int, "metadata"]]]
+
+    @strawberry.experimental.pydantic.input(User, all_fields=True)
+    class UserType:
+        pass
+
+    definition: StrawberryObjectDefinition = UserType.__strawberry_definition__
+    assert definition.name == "UserType"
+
+    [field_a, field_b] = definition.fields
+    assert field_a.python_name == "a"
+    assert isinstance(field_a.type, StrawberryOptional)
+    assert field_a.type.of_type is int
+
+    assert field_b.python_name == "b"
+    assert isinstance(field_b.type, StrawberryOptional)
+    assert isinstance(field_b.type.of_type, StrawberryList)
+    assert field_b.type.of_type.of_type is int