Replies: 14 comments 1 reply
-
I think that's pretty typical for REST api's and isn't something I would expect FastAPI to prevent. Example |
Beta Was this translation helpful? Give feedback.
-
I suppose that's true but given fastapi's focus on parameter value validation I had expected it to also validate the parameter keys as well. I think the fact that the parameters are defined as part of the function signature led me to assume you would get an error just as if you called a regular python function with an invalid keyword argument. Are there situations where you would not want to assert that all parameter keys are valid? |
Beta Was this translation helpful? Give feedback.
-
You can try this one: from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/")
def root(myparam: int = Query(...)):
return {'myparam': myparam} I can't understand that why set a default value for must required field? |
Beta Was this translation helpful? Give feedback.
-
Here is a patch that I think implements what I am looking for: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 43ab4a0..577961f 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -35,7 +35,7 @@ from fastapi.utils import (
)
from pydantic import BaseConfig, BaseModel, create_model
from pydantic.error_wrappers import ErrorWrapper
-from pydantic.errors import MissingError
+from pydantic.errors import MissingError, ExtraError
from pydantic.utils import lenient_issubclass
from starlette.background import BackgroundTasks
from starlette.concurrency import run_in_threadpool
@@ -529,6 +529,12 @@ async def solve_dependencies(
values.update(header_values)
values.update(cookie_values)
errors += path_errors + query_errors + header_errors + cookie_errors
+
+ dependant_names = {param.name for param in dependant.query_params}
+ for param in request.query_params:
+ if param not in dependant_names:
+ errors.append(ErrorWrapper(ExtraError(), loc=("query", param)))
+
if dependant.body_params:
(
body_values, from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root(myparam: int = 0):
return {'myparam': myparam}
|
Beta Was this translation helpful? Give feedback.
-
If need do this one, I think need a switch. |
Beta Was this translation helpful? Give feedback.
-
I'm not sure I fully understand, are you saying that there are existing users of fastapi who are depending on fastapi not checking for extra parameters? I believe what you are suggesting is that fastapi users need to be able to disable this check because maybe clients, which they don't control, are sending extra params? I agree that there should be a way to disable this check but I would argue that fastapi should be strict about the api spec by default to catch bugs quicker. A potential way to turn off this check could be a keyword argument in the decorator: @app.get("/", allow_extra_fields=True)
def root(myparam: int = 0):
return {'myparam': myparam} |
Beta Was this translation helpful? Give feedback.
-
In many cases, extra parameters are allowed.
And for me, the extra parameters is ok. I think need more people to discuss this feature. @app.get("/", disable_extra_fields=True)
def root(myparam: int = 0):
return {'myparam': myparam} |
Beta Was this translation helpful? Give feedback.
-
I agree. I see no reason to check extra parameters by default. The
In fact, I don't quite understand what errors can be caused by the fact that you accept exrta parameters in the query string. Especially if you checked your query parameters with |
Beta Was this translation helpful? Give feedback.
-
The word "extra" is maybe not the best word to use, "unknown" would be better. I'm trying to catch people misspelling the optional query parameters on accident (which actually happened and burned a lot of time). As another example: @app.get("/members")
def members(exclude_vip: bool = False):
....
It might not be obvious from the response that your parameter was ignore (maybe there are thousands of members and only a handful of vip members so either way you get back a huge list) The intention of my patch is to catch these kind of bugs by default, just like we catch value type errors by default. If someone is sending truly extra params then I would argue that is the non-default exceptional case. |
Beta Was this translation helpful? Give feedback.
-
I agree there should be an option to only accept defined parameters. I've written many APIs and what generally happens is that a researcher will misspell a parameter in their code and will get incorrect results because they believe the parameter is being interpreted and used by the API and not silently dropped. This can cause disastrous consequences for researchers. Imagine if a parameter named "exclude_false_positives" was a boolean value with a default of false and a researcher passed "exclude_fals_positives=true" and the API did not throw an error and the researcher used the data believing false positive had been excluded. I get that it's nice for an API to be as flexible as possible, but if it gets too flexible like accepting unknown parameters, it can lead to confusion for the end users. Also, an end-user usually always has an intention behind using a parameter. If they include something your API doesn't support, it's clearer to throw and error and then they can e-mail / contact you about it. Maybe you forgot to support the parameter? Maybe the end-user doesn't understand your documentation and you need to improve it? The only valid reason I can think of off the top of my head for adding parameters that the user knows doesn't exist is to cache-bust. Just make it an initialization switch with a default to allow unknown parameters so it's backwards compatible? |
Beta Was this translation helpful? Give feedback.
-
Thanks for the discussion here everyone. FastAPI is based on OpenAPI, which is based on JSON Schema. That by default allows extra values and defines validation for the ones provided. So forbidding extra parameters wouldn't be acceptable as a default. There are also many systems that add additional query parameters being used or not, e.g. Google Analytics, Facebook, etc. So I don't think it would work well for many use cases. But on the other side, I see this could be useful in some cases (like yours), so I think we could have a path operation decorator parameter If you want, you could create a PR with that 🤓 🚀 |
Beta Was this translation helpful? Give feedback.
-
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues. |
Beta Was this translation helpful? Give feedback.
-
I had this requirement come up. I used a custom router to do a quick validation. Not as tidy as @mortbauer's pull request, but it works in a pinch. class CheckUnknownQueryParamsRouter(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
known_params = [param.name for param in self.dependant.query_params]
async def custom_route_handler(request: Request) -> Response:
unknown_params = [qp for qp in request.query_params if qp not in known_params]
if any(unknown_params):
raise HTTPException(
status_code=400,
detail=f'Unknown request parameters(s): {", ".join(unknown_params)}'
)
return await original_route_handler(request)
return custom_route_handler
app = FastAPI(
title='TestApp',
version='x.y.z',
description='Provides a dummy endpoint for testing',
)
app.router.route_class = CheckUnknownQueryParamsRouter |
Beta Was this translation helpful? Give feedback.
-
Thanks for the example @jenden! I modified it to prevent missing aliased parameters, and to use FastAPI's built-in IMO this solution can exist as a standalone extension of the base Here's my solution: from collections.abc import Callable, Coroutine
from typing import Any
from fastapi import Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
class StrictRoute(APIRoute):
"""
A custom APIRoute that raises a RequestValidationError if any unknown query parameters are provided.
This implementation is analogous to strict Pydantic model fields (extra = 'forbid'), and can be used in conjunction for consistent validation behavior across the API.
This is useful for ensuring that the API is not being used in an unexpected/incorrect way unintentionally.
Before passing the request on to the original route handler, this custom route handler checks whether any query parameters are provided that are not defined in the route's query parameters.
"""
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
original_route_handler = super().get_route_handler()
known_params = [param.alias for param in self.dependant.query_params]
async def custom_route_handler(request: Request) -> Response:
unknown_params = [
qp for qp in request.query_params if qp not in known_params
]
if any(unknown_params):
raise RequestValidationError(
errors=[
{
"loc": ["query", param],
"msg": f"Unknown query parameter in request: {param}",
"type": "value_error",
"input": f"{param}={request.query_params[param]}",
"ctx": {"available_parameters": known_params},
}
for param in unknown_params
]
)
return await original_route_handler(request)
return custom_route_handler |
Beta Was this translation helpful? Give feedback.
-
How can I catch misspelled or unknown query parameters? For instance:
It seems bad to silently accept the unknown
param
parameter. The caller wouldn't be aware that they misspelled the parameter and that it remained at its default value.Beta Was this translation helpful? Give feedback.
All reactions