Repository: litestar-org/litestar

Issue:
Bug: OpenAPI schema error when using `pydantic.SecretStr` with a `pydantic.Field`
Reported by seels in Discord: <#1212255742954971207>:

Hey, all. I'm getting the following error when using a `SecretStr` attribute (from Pydantic) and accessing `/schema/openapi.json`: `type object 'SecretStr' has no attribute 'item_type'`. I've got `SecretStr: str` in my `type_encoders`, but that doesn't seem to help.

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

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



Discussion:
MCVE:
```python
from litestar import post
from pydantic import BaseModel, Field, SecretStr


class Something(BaseModel):
    password: SecretStr = Field(min_length=8)


@post()
async def something(data: Something) -> Something:
    return data


from litestar.testing import create_test_client

with create_test_client([something]) as client:
    print(client.get('schema/openapi.json').json())
```
This only happens when `password` is set to a `Field`. It works fine when it's just `password: SecretStr`.

Resolving patch:
diff --git a/litestar/contrib/pydantic/pydantic_schema_plugin.py b/litestar/contrib/pydantic/pydantic_schema_plugin.py
--- a/litestar/contrib/pydantic/pydantic_schema_plugin.py
+++ b/litestar/contrib/pydantic/pydantic_schema_plugin.py
@@ -142,6 +142,8 @@
 if pydantic_v2 is not None:  # pragma: no cover
     PYDANTIC_TYPE_MAP.update(
         {
+            pydantic_v2.SecretStr: Schema(type=OpenAPIType.STRING),
+            pydantic_v2.SecretBytes: Schema(type=OpenAPIType.STRING),
             pydantic_v2.ByteSize: Schema(type=OpenAPIType.INTEGER),
             pydantic_v2.EmailStr: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL),
             pydantic_v2.IPvAnyAddress: Schema(


Test patch:
diff --git a/tests/unit/test_contrib/test_pydantic/test_schema_plugin.py b/tests/unit/test_contrib/test_pydantic/test_schema_plugin.py
--- a/tests/unit/test_contrib/test_pydantic/test_schema_plugin.py
+++ b/tests/unit/test_contrib/test_pydantic/test_schema_plugin.py
@@ -8,6 +8,7 @@
 from pydantic.v1.generics import GenericModel
 from typing_extensions import Annotated
 
+from litestar._openapi.schema_generation import SchemaCreator
 from litestar.contrib.pydantic.pydantic_schema_plugin import PydanticSchemaPlugin
 from litestar.openapi.spec import OpenAPIType
 from litestar.openapi.spec.schema import Schema
@@ -65,3 +66,17 @@ def test_schema_generation_with_generic_classes(model: Type[Union[PydanticV1Gene
 )
 def test_is_pydantic_constrained_field(constrained: Any) -> None:
     PydanticSchemaPlugin.is_constrained_field(FieldDefinition.from_annotation(constrained))
+
+
+def test_v2_constrained_secrets() -> None:
+    # https://github.com/litestar-org/litestar/issues/3148
+    class Model(pydantic_v2.BaseModel):
+        string: pydantic_v2.SecretStr = pydantic_v2.Field(min_length=1)
+        bytes_: pydantic_v2.SecretBytes = pydantic_v2.Field(min_length=1)
+
+    schema = PydanticSchemaPlugin.for_pydantic_model(
+        FieldDefinition.from_annotation(Model), schema_creator=SchemaCreator(plugins=[PydanticSchemaPlugin()])
+    )
+    assert schema.properties
+    assert schema.properties["string"] == Schema(min_length=1, type=OpenAPIType.STRING)
+    assert schema.properties["bytes_"] == Schema(min_length=1, type=OpenAPIType.STRING)