New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Typehints instead of response_model #2296
Comments
I don't know if it was only a decision or if there's an issue on the suggested approach. My input here will be related to MyPy: from typing import Dict, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Potato(BaseModel):
name: str
color: str
@app.get("/")
def get_potato() -> Potato:
return {"name": "best potato", "color": "purple"} Doing as above, MyPy will get sad: test.py:14: error: Incompatible return value type (got "Dict[str, str]", expected "Potato")
Found 1 error in 1 file (checked 1 source file) Maybe a "FastAPI MyPy plugin" can be created to allow it (in case the suggestion is accepted and if is possible to create a plugin like that, I've never tried). |
No, i want use annotations instead of response_model. i want fastapi to understand typehints |
I understand what you said... I want that too. 👀 |
i think to do something like this: from typing import get_type_hints
@app.get("/")
async def root() -> dict:
return {"hello": "world"}
hints = get_type_hints(root)
if "return" in hints:
response_model = hints["return"]
# or
# response_model = hints.get("return") |
There is already a library that allows you to do this: https://fastapi-utils.davidmontague.xyz/user-guide/inferring-router/ |
thx! @tiangolo , how about do it in APIRoter by default? |
That would break existing usage patterns, as right now you can, for example, return a dictionary, and have fastapi validate and transform it into a pydantic model. If the pattern was changed this would no longer be possible without breaking type validation |
@peach-lasagna There were multiple discussions and implementation of this feature. PR #1436, #875, and issue #101 they all about this. Basically, I was an author of #1436 and after reading discussion thread #101, I understood the main idea why this feature isn't implemented. In my opinion, the best solution to this problem will be to implement this feature but not to break the existing code base.
# response model - Dict[str, List[str]]
@app.get("/", response_model=Dict[str, List[str]])
def read_root():
return {"Hello": ["tom", "hesus"]}
# response model - Dict[str, List[str]]
@app.get("/")
def read_root() -> Dict[str, List[str]]:
return {"Hello": ["tom", "hesus"]}
# response model - Dict[str, List[str]]
@app.get("/", response_model=Dict[str, List[str]])
def read_root() -> Dict[str, Set[str]]:
return {"Hello": {"tom", "hesus"}} In case if we accept the proposal above we can simply reopen and merge PR #1436 because it has already implemented the logic described above. I really like this idea, and a lot of peoples want this feature. |
This would make FastAPI's usage of type annotations much more natural, especially for new users familiar with how type annotations work outside of FastAPI. As a new user, it was very surprising to need to put the return type into |
@johnthagen Totally agree with you, I faced the same problem when have started working with FastAPI. @tiangolo What do you think about this feature? We can implement this feature fully backward compatible. |
I really like this feature and have implemented it locally as well and even with support for multiple different responses. If Im honest though I think this should be a non-backwards compatible change. I feel like (even with the way FastAPI is currently) if you allow too many different ways to specify responses it can allow code that is less clear and I think FastAPI should converge on a single way to specify responses (while it is still < @app.post('/api/do_stuff', status_code=200, response_model=Y, responses={200: {'model': Z}})
def do_stuff(body: Any) -> X:
return {} If I was just starting to work with FastAPI I would be confused as to what this is actually going to try to return. Would it be @app.post('/api/do_stuff', default_status_code=200)
def do_stuff(body: Any) -> X:
return {} which is pretty clear that it returns a @app.post('/api/do_stuff', default_status_code=200)
def do_stuff(body: Any) -> {200: X, 404: Y, 400: {'model': Z, 'description': 'this is a Z'}}:
return {} which is effective enough to say it returns |
@uriyyo Looking at #1436, it looks like it implements this in a backwards-compatible way, would it be worthwhile to re-open that? It seems like the sum of the critique of this feature is that it's not compatible with the current |
@antonagestam Yes, it's fully backward compatible. I can reopen it, but I am not sure that it will be merged. We should hear the opinion of @tiangolo. More details from previous discussion #875 |
@uriyyo Cool, I think it could be worthwhile to have an open and fresh PR to make the case? @tiangolo mentions confusion as a reason to not implement this, perhaps that can be alleviated by improving documentation? As a seasoned Python developer I don't really see that this should cause confusion. The annotated return type of the function needs to be accurate, and isn't always the same type as what will be advertised in the API schema. But, when it is, I think wording along those lines in documentation should alleviate the confusion? |
Thanks for the discussion everyone! Yep, as some have said, this is particularly important when returning something different than what you want FastAPI to send in the response. Maybe you are returning a dictionary and you want it documented with a Pydantic model. Or maybe you want to return an object directly from the database, and you want it serialized to JSON and filtered by Pydantic, for example, filtering sensitive data, which is precisely explained in the docs about Nevertheless, I should also document that the simplest way to document path operations is with And I agree that if there's no @uriyyo, would you like to re-open your PR, please? 🙏 🚀 |
For reference for others (took me a little bit to find) the PR @tiangolo is mentioning is: |
@tiangolo I have re-opened PR, please, take a look. |
Any is good until you set Mypy in strict mode. |
This was added in #1436 (thanks @uriyyo!) 🚀 It's available in FastAPI You can read the new docs here: https://fastapi.tiangolo.com/tutorial/response-model/ 🤓 |
Hi, this new feature released in Example code @app.get("/")
async def route() -> Response:
return Response(xxx) FastAPI reads the return type annotation when Lines 358 to 359 in 69bd7d8
for workaround, remove the return type annotation or set the it could be better to add a assertion if annotation is |
The new feature also sortof breaks my code :) |
If the type annotation is incorrect, isn't this feature identifying a bug for you that you can now fix? |
Yes, that's what I meant with "sortof breaks my code". I am actually quite happy that this happened, already fixed on my side. |
As a matter of fact, this breaks for any non- |
Another small real world example that fails the new return type annotation checking: from starlette.responses import RedirectResponse
@app.get("/", include_in_schema=False)
async def docs_redirect() -> RedirectResponse:
return RedirectResponse(url="/docs")
|
Thanks everyone for the additional reports! 🍰 These simple use cases with a single And there's a lot of new docs with details here: https://fastapi.tiangolo.com/tutorial/response-model/#other-return-type-annotations This is part of FastAPI 0.89.1 just released 🎉 |
Confirmed that 0.89.1 fixed our issue. Thanks! |
Quite happy to see this change! One snag I noticed when upgrading (aside from some StacktraceTraceback (most recent call last):
File "<string>", line 1, in <module>
File ".../api.py", line 87, in <module>
def get_amplicon_structure_from_benchling(benchling_entity_id: str) -> BarcodeSchema:
File "...venv/lib/python3.10/site-packages/fastapi/routing.py", line 638, in decorator
self.add_api_route(
File "...venv/lib/python3.10/site-packages/fastapi/routing.py", line 577, in add_api_route
route = route_class(
File "...venv/lib/python3.10/site-packages/fastapi/routing.py", line 417, in __init__
] = create_cloned_field(self.response_field)
File "...venv/lib/python3.10/site-packages/fastapi/utils.py", line 117, in create_cloned_field
use_type = create_model(original_type.__name__, __base__=original_type)
File "pydantic/main.py", line 1027, in pydantic.main.create_model
File "pydantic/main.py", line 283, in pydantic.main.ModelMetaclass.__new__
File "...venv/lib/python3.10/abc.py", line 106, in __new__
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
File ".../some_models.py", line 87, in __init_subclass__
raise ValueError(f"Duplicate class with feature_class={name}: {dup}")
ValueError: Duplicate class with feature_class=... This is a pretty esoteric edge case so fine if it's not worth fixing (the well documented |
First check
Example
Now:
The solution you would like
I want to do it through typehints:
The text was updated successfully, but these errors were encountered: