How can I do case-insensitive URLs in FastAPI?
The easiest way I can think of would be to update the path regexs for the routes.
For example:
import re
from starlette.routing import Route
from starlette.testclient import TestClient
from fastapi import FastAPI
app = FastAPI()
@app.get("/endpoint")
async def endpoint() -> str:
return "success"
for route in app.router.routes:
if isinstance(route, Route):
route.path_regex = re.compile(route.path_regex.pattern, re.IGNORECASE)
print(TestClient(app).get("/endpoint").json())
# success
print(TestClient(app).get("/ENDPOINT").json())
# success
Let us know if that doesn't work for you.
@dmontagu Thanks. But it only changes the path part. The query string is still case-sensitive. How can we achieved this for entire URL except scheme and domain part.
Hmm, that might be more challenging. Currently I don't think FastAPI exposes (or even has) any functionality related to transforming the query parameter keys prior to injection.
You can always add request: Request as an argument of the endpoint and read the raw query parameters from there; that would allow you to convert the parameters to lowercase before retrieving. This is obviously much less ergonomic than the usual dependency injection though.
I think it might be possible to achieve this by using a custom Request subclass (as described in these docs: https://fastapi.tiangolo.com/tutorial/custom-request-and-route/) that overrides the way that the query parameters map is populated. If you attempt this though, you'll probably need to read through some Starlette and FastAPI source code to figure out how the query parameters map is built and used.
In general, I would recommend trying not to rely on case insensitivity of the query string if possible.
I'd be interested if anyone else has a better solution here.
Well, the problem is that it comes in an immutable dict called query_params from Starlette.
I think the most convenient solution to your problem is the middleware, which will be convert path and params to lower case. This example works with
from fastapi import FastAPI
from starlette.requests import Request
app = FastAPI()
DECODE_FORMAT = "latin-1"
@app.get("/test")
async def test(request: Request):
print(request.query_params)
return request.query_params
@app.middleware("http")
async def case_sens_middleware(request: Request, call_next):
raw_query_str = request.scope["query_string"].decode(DECODE_FORMAT).lower()
request.scope["query_string"] = raw_query_str.encode(DECODE_FORMAT)
path = request.scope["path"].lower()
request.scope["path"] = path
response = await call_next(request)
return response
UPD: See also regex validation for Path and Query
Thanks @Slyfoxy. I think that this will work for me.
Thanks for the help here everyone! Clever trick @Slyfoxy :fox_face: :rocket:
I think that should solve your use case, right @shivshankardayal ? If so, you can close the issue.
For posterity, here's how to import a middleware from another module:
lower_case_middleware.py
from starlette.requests import Request
class LowerCaseMiddleware:
def __init__(self) -> None:
self.DECODE_FORMAT = "latin-1"
async def __call__(self, request: Request, call_next):
raw = request.scope["query_string"].decode(self.DECODE_FORMAT).lower()
request.scope["query_string"] = raw.encode(self.DECODE_FORMAT)
path = request.scope["path"].lower()
request.scope["path"] = path
response = await call_next(request)
return response
main.py
...
from lower_case_middleware import LowerCaseMiddleware
app = FastAPI()
my_middleware = LowerCaseMiddleware()
app.middleware("http")(my_middleware)
Most helpful comment
I think the most convenient solution to your problem is the middleware, which will be convert path and params to lower case. This example works with
UPD: See also regex validation for Path and Query