fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

Get the StringConstraints directly from the Identifier type

Open baderdean opened this issue 2 years ago • 6 comments

Problem: FastAPI does not validate custom string build using Annotated[str, StringConstraints(...)]

Temporary solution My solution is to annotate the constrained type with Path again, then it seems to work:

from typing import Annotated

from fastapi import FastAPI, Path
from pydantic import StringConstraints

# In my case this is imported from a lib with pydantic but without fastapi,
# so it can't be changed to Annotated[str, Path(pattern=r"^[a-fA-F0-9]+$")]
Identifier = Annotated[str, StringConstraints(pattern=r"^[a-fA-F0-9]+$")]


app = FastAPI()


@app.get("/{some_id}")
async def get_something(some_id: Annotated[Identifier, Path()]):
    return some_id

Feature request Would be nice if fastapi would be able to get the constraint directly from the Identifier type without being required to add Annotated[Identifier, Path()].

Originally posted by @JasperJuergensen in https://github.com/tiangolo/fastapi/discussions/10105#discussioncomment-6901418

baderdean avatar Sep 25 '23 19:09 baderdean

What the OP wants is this:

from typing import Annotated

from fastapi import FastAPI, Path
from pydantic import StringConstraints

Identifier = Annotated[str, StringConstraints(pattern=r"^[a-fA-F0-9]+$")]

app = FastAPI()

@app.get("/{some_id}")
async def get_something(some_id: Identifier):
    return some_id

A similar feature request is to use pydantic.Field.

Kludex avatar Sep 26 '23 03:09 Kludex

It is because missing the matadata when none of Annotated args is FieldInfo or Depends I've commit a fix pr #10356 . Hope it's work

Dragon-GCS avatar Sep 30 '23 12:09 Dragon-GCS

Isn't it pretty much the same as #10259 and #10109?

ramnes avatar Nov 09 '23 15:11 ramnes

Seems it was fixed somewhere:

Updated: use code from this comment, not workaround

from typing import Annotated

from fastapi import FastAPI
from pydantic import StringConstraints

Identifier = Annotated[str, StringConstraints(pattern=r"^[a-fA-F0-9]+$")]

app = FastAPI()

@app.get("/{some_id}")
async def get_something(some_id: Identifier):
    return some_id


# TESTS

from fastapi.testclient import TestClient

client = TestClient(app)


def test_ok():
    resp = client.get("/ad")
    assert resp.status_code == 200, resp.json()


def test_invalid():
    resp = client.get("/a-d")
    assert resp.status_code == 422
    assert resp.json() == {
        "detail": [
            {
                "type": "string_pattern_mismatch",
                "loc": ["path", "some_id"],
                "msg": "String should match pattern '^[a-fA-F0-9]+$'",
                "input": "a-d",
                "ctx": {"pattern": "^[a-fA-F0-9]+$"},
            }
        ]
    }


def test_schema():
    resp = client.get("/openapi.json")
    schema = resp.json()

    assert schema["paths"]["/{some_id}"]["get"]["parameters"][0] == {
        "name": "some_id",
        "in": "path",
        "required": True,
        "schema": {"type": "string", "pattern": "^[a-fA-F0-9]+$", "title": "Some Id"},
    }

YuriiMotov avatar Jun 11 '25 19:06 YuriiMotov

What about pydantic.Field, as per the first comment here?

Kludex avatar Jun 11 '25 20:06 Kludex

Sorry, I used wrong code in my previous comment 🤦 Updated code to test desirable solution instead of workaround.

What about pydantic.Field, as per the first comment here?

Not sure I understand this.. Could you give an example?

If you mean Identifier = Annotated[str, Field(pattern=r"^[a-fA-F0-9]+$")] then it works

YuriiMotov avatar Jun 12 '25 04:06 YuriiMotov

As @YuriiMotov says, this seems to have been fixed, I tried with:

from typing import Annotated

from fastapi import FastAPI
from pydantic import Field, StringConstraints

Identifier = Annotated[str, Field(pattern=r"^[a-fA-F0-9]+$")]
Identifier2 = Annotated[str, StringConstraints(pattern=r"^[a-fA-F0-9]+$")]

app = FastAPI()


@app.get("/{some_id}")
async def get_something(some_id: Identifier):
    return some_id


@app.get("/other/{some_id}")
async def get_other(some_id: Identifier2):
    return some_id

And both endpoints, with Field and StringConstraints are working and validating the data. 🚀

I understand this is what was wanted, so I'll close this one now. ☕

tiangolo avatar Jun 17 '25 11:06 tiangolo