Explore all Fastapi open source software, libraries, packages, source code, cloud functions and APIs.

Popular New Releases in Fastapi

fastapi

0.75.2

sqlmodel

0.0.6

databases

Revert backward incompatible changes

opyrator

0.0.11

fastapi-users

Popular Libraries in Fastapi

awesome-python

by vinta doticonpythondoticon

star image 102379 doticonNOASSERTION

A curated list of awesome Python frameworks, libraries, software and resources

fastapi

by tiangolo doticonpythondoticon

star image 44054 doticonMIT

FastAPI framework, high performance, easy to learn, fast to code, ready for production

sqlmodel

by tiangolo doticonpythondoticon

star image 7183 doticonMIT

SQL databases in Python, designed for simplicity, compatibility, and robustness.

databases

by encode doticonpythondoticon

star image 2763 doticonBSD-3-Clause

Async database support for Python. 🗄

opyrator

by ml-tooling doticonpythondoticon

star image 2593 doticonMIT

🪄 Turns your machine learning code into microservices with web API, interactive GUI, and more.

gino

by python-gino doticonpythondoticon

star image 2337 doticonNOASSERTION

GINO Is Not ORM - a Python asyncio ORM on SQLAlchemy core.

fastapi-users

by fastapi-users doticonpythondoticon

star image 1841 doticonMIT

Ready-to-use and customizable users management for FastAPI

30-Days-of-Python

by codingforentrepreneurs doticonhtmldoticon

star image 1811 doticonMIT

Learn Python for the next 30 (or so) Days.

fastapi-realworld-example-app

by nsidnev doticonpythondoticon

star image 1784 doticonMIT

Backend logic implementation for https://github.com/gothinkster/realworld with awesome FastAPI

Trending New libraries in Fastapi

sqlmodel

by tiangolo doticonpythondoticon

star image 7183 doticonMIT

SQL databases in Python, designed for simplicity, compatibility, and robustness.

opyrator

by ml-tooling doticonpythondoticon

star image 2593 doticonMIT

🪄 Turns your machine learning code into microservices with web API, interactive GUI, and more.

coronavirus-tracker-api

by ExpDev07 doticonpythondoticon

star image 1530 doticonGPL-3.0

🦠 A simple and fast (< 200ms) API for tracking the global coronavirus (COVID-19, SARS-CoV-2) outbreak. It's written in python using the 🔥 FastAPI framework. Supports multiple sources!

best-of-web-python

by ml-tooling doticonpythondoticon

star image 1488 doticonCC-BY-SA-4.0

🏆 A ranked list of awesome python libraries for web development. Updated weekly.

budgetml

by ebhy doticonpythondoticon

star image 1193 doticonApache-2.0

Deploy a ML inference service on a budget in less than 10 lines of code.

ormar

by collerek doticonpythondoticon

star image 902 doticonMIT

python async orm with fastapi in mind and pydantic validation

fastapi-admin

by fastapi-admin doticonpythondoticon

star image 858 doticonApache-2.0

A fast admin dashboard based on FastAPI and TortoiseORM with tabler ui, inspired by Django admin

fastapi-utils

by dmontagu doticonpythondoticon

star image 746 doticonMIT

Reusable utilities for FastAPI

manage-fastapi

by ycd doticonpythondoticon

star image 640 doticonMIT

:rocket: CLI tool for FastAPI. Generating new FastAPI projects & boilerplates made easy.

Top Authors in Fastapi

1

jordaneremieff

13 Libraries

star icon1097

2

Kludex

11 Libraries

star icon396

3

testdrivenio

9 Libraries

star icon290

4

DJWOMS

7 Libraries

star icon93

5

tiangolo

6 Libraries

star icon54057

6

florimondmanca

5 Libraries

star icon1071

7

dmontagu

4 Libraries

star icon980

8

codingforentrepreneurs

4 Libraries

star icon1889

9

encode

4 Libraries

star icon4386

10

sabuhish

4 Libraries

star icon425

1

13 Libraries

star icon1097

2

11 Libraries

star icon396

3

9 Libraries

star icon290

4

7 Libraries

star icon93

5

6 Libraries

star icon54057

6

5 Libraries

star icon1071

7

4 Libraries

star icon980

8

4 Libraries

star icon1889

9

4 Libraries

star icon4386

10

4 Libraries

star icon425

Trending Kits in Fastapi

No Trending Kits are available at this moment for Fastapi

Trending Discussions on Fastapi

FastAPI - GET request results in typeerror (value is not a valid dict)

Conditional call of a FastApi Model

fastapi (starlette) RedirectResponse redirect to post instead get method

uvicorn [fastapi] python run both HTTP and HTTPS

how to render a json from a dataframe in fastAPI

FastAPI - Pydantic - Value Error Raises Internal Server Error

Kill a python subprocess that does not return

&quot;422 Unprocessable Entity&quot; error when making POST request with both attributes and key using FastAPI

FastAPI responding slowly when calling through other Python app, but fast in cURL

How to apply transaction logic in FastAPI RealWorld example app?

QUESTION

FastAPI - GET request results in typeerror (value is not a valid dict)

Asked 2022-Mar-23 at 22:19

this is my database schema.

enter image description here

I defined my Schema like this:

from pydantic import BaseModel

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6

This is my model:

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13

In a crud.py I define a get_attributes method.

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13def get_attributes(db: Session, skip: int = 0, limit: int = 100):
14    return db.query(models.Userattribute).offset(skip).limit(limit).all()
15

This is my GET endpoint:

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13def get_attributes(db: Session, skip: int = 0, limit: int = 100):
14    return db.query(models.Userattribute).offset(skip).limit(limit).all()
15@app.get(&quot;/attributes/&quot;, response_model=List[schemas.Userattribute])
16def read_attributes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
17    users = crud.get_attributes(db, skip=skip, limit=limit)
18    print(users)
19    return users
20

The connection to the database seems to work, but a problem is the datatype:

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13def get_attributes(db: Session, skip: int = 0, limit: int = 100):
14    return db.query(models.Userattribute).offset(skip).limit(limit).all()
15@app.get(&quot;/attributes/&quot;, response_model=List[schemas.Userattribute])
16def read_attributes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
17    users = crud.get_attributes(db, skip=skip, limit=limit)
18    print(users)
19    return users
20pydantic.error_wrappers.ValidationError: 7 validation errors for Userattribute
21response -&gt; 0
22  value is not a valid dict (type=type_error.dict)
23response -&gt; 1
24  value is not a valid dict (type=type_error.dict)
25response -&gt; 2
26  value is not a valid dict (type=type_error.dict)
27response -&gt; 3
28  value is not a valid dict (type=type_error.dict)
29response -&gt; 4
30  value is not a valid dict (type=type_error.dict)
31response -&gt; 5
32  value is not a valid dict (type=type_error.dict)
33response -&gt; 6
34  value is not a valid dict (type=type_error.dict)
35

Why does FASTApi expect a dictionary here? I don´t really understand it, since I am not able to even print the response. How can I fix this?

ANSWER

Answered 2022-Mar-23 at 22:19

SQLAlchemy does not return a dictionary, which is what pydantic expects by default. You can configure your model to also support loading from standard orm parameters (i.e. attributes on the object instead of dictionary lookups):

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13def get_attributes(db: Session, skip: int = 0, limit: int = 100):
14    return db.query(models.Userattribute).offset(skip).limit(limit).all()
15@app.get(&quot;/attributes/&quot;, response_model=List[schemas.Userattribute])
16def read_attributes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
17    users = crud.get_attributes(db, skip=skip, limit=limit)
18    print(users)
19    return users
20pydantic.error_wrappers.ValidationError: 7 validation errors for Userattribute
21response -&gt; 0
22  value is not a valid dict (type=type_error.dict)
23response -&gt; 1
24  value is not a valid dict (type=type_error.dict)
25response -&gt; 2
26  value is not a valid dict (type=type_error.dict)
27response -&gt; 3
28  value is not a valid dict (type=type_error.dict)
29response -&gt; 4
30  value is not a valid dict (type=type_error.dict)
31response -&gt; 5
32  value is not a valid dict (type=type_error.dict)
33response -&gt; 6
34  value is not a valid dict (type=type_error.dict)
35class Userattribute(BaseModel):
36    name: str
37    value: str
38    user_id: str
39    id: str
40
41    class Config:
42        orm_mode = True
43

You can also attach a debugger right before the call to return to see what's being returned.

Since this answer has become slightly popular, I'd like to also mention that you can make orm_mode = True the default for your schema classes by having a common parent class that inherits from BaseModel:

1class Userattribute(BaseModel):
2    name: str
3    value: str
4    user_id: str
5    id: str
6class Userattribute(Base):
7    __tablename__ = &quot;user_attribute&quot;
8
9    name = Column(String)
10    value = Column(String)
11    user_id = Column(String)
12    id = Column(String, primary_key=True, index=True)
13def get_attributes(db: Session, skip: int = 0, limit: int = 100):
14    return db.query(models.Userattribute).offset(skip).limit(limit).all()
15@app.get(&quot;/attributes/&quot;, response_model=List[schemas.Userattribute])
16def read_attributes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
17    users = crud.get_attributes(db, skip=skip, limit=limit)
18    print(users)
19    return users
20pydantic.error_wrappers.ValidationError: 7 validation errors for Userattribute
21response -&gt; 0
22  value is not a valid dict (type=type_error.dict)
23response -&gt; 1
24  value is not a valid dict (type=type_error.dict)
25response -&gt; 2
26  value is not a valid dict (type=type_error.dict)
27response -&gt; 3
28  value is not a valid dict (type=type_error.dict)
29response -&gt; 4
30  value is not a valid dict (type=type_error.dict)
31response -&gt; 5
32  value is not a valid dict (type=type_error.dict)
33response -&gt; 6
34  value is not a valid dict (type=type_error.dict)
35class Userattribute(BaseModel):
36    name: str
37    value: str
38    user_id: str
39    id: str
40
41    class Config:
42        orm_mode = True
43class OurBaseModel(BaseModel):
44    class Config:
45        orm_mode = True
46
47
48class Userattribute(OurBaseModel):
49    name: str
50    value: str
51    user_id: str
52    id: str
53

This is useful if you want to support orm_mode for most of your classes (and for those where you don't, inherit from the regular BaseModel).

Source https://stackoverflow.com/questions/69504352

QUESTION

Conditional call of a FastApi Model

Asked 2022-Mar-20 at 10:36

I have a multilang FastApi connected to MongoDB. My document in MongoDB is duplicated in the two languages available and structured this way (simplified example):

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18

I therefore implemented two models DatasetFR and DatasetEN, each one make references with specific external Models (Enum) for category and tags in each lang .

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25

In the routes definition I forced the language parameter to declare the corresponding Model and get the corresponding validation.

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36

But this seems to be in contradiction with the DRY principle. So, I wonder here if someone knows an elegant solution to: - given the parameter lang, dynamically call the corresponding model.

Or if we can create a Parent Model Dataset that takes the lang argument and retrieve the child model Dataset.

This would incredibly ease building my API routes and the call of my models and mathematically divide by two the writing...

ANSWER

Answered 2022-Feb-22 at 08:00

There are 2 parts to the answer (API call and data structure)

for the API call, you could separate them into 2 routes like /api/v1/fr/... and /api/v1/en/... (separating ressource representation!) and play with fastapi.APIRouter to declare the same route twice but changing for each route the validation schema by the one you want to use.

you could start by declaring a common BaseModel as an ABC as well as an ABCEnum.

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47

Then you can select the accurate Model for the routes through a class factory:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51

Finally you can create your routes through a route factory:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51def generate_language_specific_router(language: str, ...) -&gt; APIRouter:
52    router = APIRouter(prefix=language)
53    MySelectedModel: MyModelABC = my_class_factory[language]
54
55    @router.post(&quot;/&quot;)
56    def post_something(my_model_data: MySelectedModel):
57        # My internal logic
58    return router
59

About the second part (internal computation and data storage), internationalisation is often done through hashmaps.

The standard python library gettext could be investigated

Otherwise, the original language can be explicitely used as the key/hash and then map translations to it (also including the original language if you want to have consistency in your calls).

It can look like:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51def generate_language_specific_router(language: str, ...) -&gt; APIRouter:
52    router = APIRouter(prefix=language)
53    MySelectedModel: MyModelABC = my_class_factory[language]
54
55    @router.post(&quot;/&quot;)
56    def post_something(my_model_data: MySelectedModel):
57        # My internal logic
58    return router
59dictionnary_of_babel = {
60    &quot;word1&quot;: {
61        &quot;en&quot;: &quot;word1&quot;,
62        &quot;fr&quot;: &quot;mot1&quot;,
63    },
64    &quot;word2&quot;: {
65        &quot;en&quot;: &quot;word2&quot;,
66    },
67    &quot;Drinking Water Composition&quot;: {
68        &quot;en&quot;: &quot;Drinking Water Composition&quot;,
69        &quot;fr&quot;: &quot;Composition de l'eau de boisson&quot;,
70    },
71}
72
73my_arbitrary_object = {
74    &quot;attribute1&quot;: &quot;word1&quot;,
75    &quot;attribute2&quot;: &quot;word2&quot;,
76    &quot;attribute3&quot;: &quot;Drinking Water Composition&quot;,
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81    if &quot;fr&quot; in dictionnary_of_babel[english_sentence].keys():
82        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;fr&quot;]
83    else:
84        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;en&quot;]  # ou sans &quot;en&quot;
85
86expected_translated_object = {
87    &quot;attribute1&quot;: &quot;mot1&quot;,
88    &quot;attribute2&quot;: &quot;word2&quot;,
89    &quot;attribute3&quot;: &quot;Composition de l'eau de boisson&quot;,
90}
91
92assert expected_translated_object == my_translated_object
93

This code should run as is

A proposal for mongoDB representation, if we don't want to have a separate table for translations, could be a data structure such as:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51def generate_language_specific_router(language: str, ...) -&gt; APIRouter:
52    router = APIRouter(prefix=language)
53    MySelectedModel: MyModelABC = my_class_factory[language]
54
55    @router.post(&quot;/&quot;)
56    def post_something(my_model_data: MySelectedModel):
57        # My internal logic
58    return router
59dictionnary_of_babel = {
60    &quot;word1&quot;: {
61        &quot;en&quot;: &quot;word1&quot;,
62        &quot;fr&quot;: &quot;mot1&quot;,
63    },
64    &quot;word2&quot;: {
65        &quot;en&quot;: &quot;word2&quot;,
66    },
67    &quot;Drinking Water Composition&quot;: {
68        &quot;en&quot;: &quot;Drinking Water Composition&quot;,
69        &quot;fr&quot;: &quot;Composition de l'eau de boisson&quot;,
70    },
71}
72
73my_arbitrary_object = {
74    &quot;attribute1&quot;: &quot;word1&quot;,
75    &quot;attribute2&quot;: &quot;word2&quot;,
76    &quot;attribute3&quot;: &quot;Drinking Water Composition&quot;,
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81    if &quot;fr&quot; in dictionnary_of_babel[english_sentence].keys():
82        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;fr&quot;]
83    else:
84        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;en&quot;]  # ou sans &quot;en&quot;
85
86expected_translated_object = {
87    &quot;attribute1&quot;: &quot;mot1&quot;,
88    &quot;attribute2&quot;: &quot;word2&quot;,
89    &quot;attribute3&quot;: &quot;Composition de l'eau de boisson&quot;,
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: &quot;sentence&quot;
95
96# internationalized
97my_attribute_internationalized: {
98    sentence: {
99        original_lang: &quot;sentence&quot;
100        lang1: &quot;sentence_lang1&quot;,
101        lang2: &quot;sentence_lang2&quot;,
102    }
103}
104

A simple tactic to generalize string translation is to define an anonymous function _() that embeds the translation like:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51def generate_language_specific_router(language: str, ...) -&gt; APIRouter:
52    router = APIRouter(prefix=language)
53    MySelectedModel: MyModelABC = my_class_factory[language]
54
55    @router.post(&quot;/&quot;)
56    def post_something(my_model_data: MySelectedModel):
57        # My internal logic
58    return router
59dictionnary_of_babel = {
60    &quot;word1&quot;: {
61        &quot;en&quot;: &quot;word1&quot;,
62        &quot;fr&quot;: &quot;mot1&quot;,
63    },
64    &quot;word2&quot;: {
65        &quot;en&quot;: &quot;word2&quot;,
66    },
67    &quot;Drinking Water Composition&quot;: {
68        &quot;en&quot;: &quot;Drinking Water Composition&quot;,
69        &quot;fr&quot;: &quot;Composition de l'eau de boisson&quot;,
70    },
71}
72
73my_arbitrary_object = {
74    &quot;attribute1&quot;: &quot;word1&quot;,
75    &quot;attribute2&quot;: &quot;word2&quot;,
76    &quot;attribute3&quot;: &quot;Drinking Water Composition&quot;,
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81    if &quot;fr&quot; in dictionnary_of_babel[english_sentence].keys():
82        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;fr&quot;]
83    else:
84        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;en&quot;]  # ou sans &quot;en&quot;
85
86expected_translated_object = {
87    &quot;attribute1&quot;: &quot;mot1&quot;,
88    &quot;attribute2&quot;: &quot;word2&quot;,
89    &quot;attribute3&quot;: &quot;Composition de l'eau de boisson&quot;,
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: &quot;sentence&quot;
95
96# internationalized
97my_attribute_internationalized: {
98    sentence: {
99        original_lang: &quot;sentence&quot;
100        lang1: &quot;sentence_lang1&quot;,
101        lang2: &quot;sentence_lang2&quot;,
102    }
103}
104CURRENT_MODULE_LANG = &quot;fr&quot;
105
106def _(original_string: str) -&gt; str:
107    &quot;&quot;&quot;Switch from original_string to translation&quot;&quot;&quot;
108    return dictionnary_of_babel[original_string][CURRENT_MODULE_LANG]
109

Then call it everywhere a translation is needed:

1
2{
3  &quot;_id&quot;: xxxxxxx,
4  &quot;en&quot;: { 
5          &quot;title&quot;: &quot;Drinking Water Composition&quot;,
6          &quot;description&quot;: &quot;Drinking water composition expressed in... with pesticides.&quot;,
7          &quot;category&quot;: &quot;Water&quot;, 
8          &quot;tags&quot;: [&quot;water&quot;,&quot;pesticides&quot;] 
9         },
10  &quot;fr&quot;: { 
11          &quot;title&quot;: &quot;Composition de l'eau de boisson&quot;,
12          &quot;description&quot;: &quot;Composition de l'eau de boisson exprimée en... présence de pesticides....&quot;,
13          &quot;category&quot;: &quot;Eau&quot;, 
14          &quot;tags&quot;: [&quot;eau&quot;,&quot;pesticides&quot;] 
15         },  
16}
17
18class DatasetFR(BaseModel):
19    title:str
20    description: str
21    category: CategoryFR
22    tags: Optional[List[TagsFR]]
23
24# same for DatasetEN chnaging the lang tag to EN 
25
26@router.post(&quot;?lang=fr&quot;, response_description=&quot;Add a dataset&quot;)
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str=&quot;fr&quot;):
28    ...
29    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post(&quot;?lang=en&quot;, response_description=&quot;Add a dataset&quot;)
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str=&quot;en&quot;):
33    ...
34    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
35
36from abc import ABC
37from pydantic import BaseModel
38
39class MyModelABC(ABC, BaseModel):
40    attribute1: MyEnumABC
41
42class MyModelFr(MyModelABC):
43    attribute1: MyEnumFR
44
45class MyModelEn(MyModelABC):
46    attribute1: MyEnumEn
47my_class_factory: dict[str, MyModelABC] = {
48    &quot;fr&quot;: MyModelFr,
49    &quot;en&quot;: MyModelEn, 
50}
51def generate_language_specific_router(language: str, ...) -&gt; APIRouter:
52    router = APIRouter(prefix=language)
53    MySelectedModel: MyModelABC = my_class_factory[language]
54
55    @router.post(&quot;/&quot;)
56    def post_something(my_model_data: MySelectedModel):
57        # My internal logic
58    return router
59dictionnary_of_babel = {
60    &quot;word1&quot;: {
61        &quot;en&quot;: &quot;word1&quot;,
62        &quot;fr&quot;: &quot;mot1&quot;,
63    },
64    &quot;word2&quot;: {
65        &quot;en&quot;: &quot;word2&quot;,
66    },
67    &quot;Drinking Water Composition&quot;: {
68        &quot;en&quot;: &quot;Drinking Water Composition&quot;,
69        &quot;fr&quot;: &quot;Composition de l'eau de boisson&quot;,
70    },
71}
72
73my_arbitrary_object = {
74    &quot;attribute1&quot;: &quot;word1&quot;,
75    &quot;attribute2&quot;: &quot;word2&quot;,
76    &quot;attribute3&quot;: &quot;Drinking Water Composition&quot;,
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81    if &quot;fr&quot; in dictionnary_of_babel[english_sentence].keys():
82        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;fr&quot;]
83    else:
84        my_translated_object[attribute] = dictionnary_of_babel[english_sentence][&quot;en&quot;]  # ou sans &quot;en&quot;
85
86expected_translated_object = {
87    &quot;attribute1&quot;: &quot;mot1&quot;,
88    &quot;attribute2&quot;: &quot;word2&quot;,
89    &quot;attribute3&quot;: &quot;Composition de l'eau de boisson&quot;,
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: &quot;sentence&quot;
95
96# internationalized
97my_attribute_internationalized: {
98    sentence: {
99        original_lang: &quot;sentence&quot;
100        lang1: &quot;sentence_lang1&quot;,
101        lang2: &quot;sentence_lang2&quot;,
102    }
103}
104CURRENT_MODULE_LANG = &quot;fr&quot;
105
106def _(original_string: str) -&gt; str:
107    &quot;&quot;&quot;Switch from original_string to translation&quot;&quot;&quot;
108    return dictionnary_of_babel[original_string][CURRENT_MODULE_LANG]
109&gt;&gt;&gt; print(_(&quot;word 1&quot;))
110&quot;mot 1&quot;
111

You can find a reference to this practice in the django documentation about internationalization-in-python-code.

For static translation (for example a website or a documentation), you can use .po files and editors like poedit (See the french translation of python docs for a practical usecase)!

Source https://stackoverflow.com/questions/71162915

QUESTION

fastapi (starlette) RedirectResponse redirect to post instead get method

Asked 2022-Mar-18 at 10:58

I have encountered strange redirect behaviour after returning a RedirectResponse object

events.py

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31

routers.py

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35

this code returns the result

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36

OK, I see that for some reason a POST request is called instead of a GET request. I search for an explanation and find that the RedirectResponse object defaults to code 307 and calls POST link

I follow the advice and add a status

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37

And get

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38

for the experiment, I'm changing @router.get('/{pk}', response_model=EventSingle) to @router.post('/{pk}', response_model=EventSingle)

and the redirect completes successfully, but the post request doesn't suit me here. What am I doing wrong?

UPD

html form for running event/create logic

base.html

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38&lt;form action=&quot;{{ url_for('event_create')}}&quot; method=&quot;POST&quot;&gt;
39...
40&lt;/form&gt;
41

base_view.py

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38&lt;form action=&quot;{{ url_for('event_create')}}&quot; method=&quot;POST&quot;&gt;
39...
40&lt;/form&gt;
41@router.get('/', response_class=HTMLResponse)
42async def main_page(request: Request,
43                    activity_service: ActivityService = Depends()):
44    activity = await activity_service.get()
45    return templates.TemplateResponse('base.html', context={'request': request,
46                                                            'activities': activity})
47

ANSWER

Answered 2022-Jan-19 at 20:22

When you want to redirect to a GET after a POST, the best practice is to redirect with a 303 status code, so just update your code to:

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38&lt;form action=&quot;{{ url_for('event_create')}}&quot; method=&quot;POST&quot;&gt;
39...
40&lt;/form&gt;
41@router.get('/', response_class=HTMLResponse)
42async def main_page(request: Request,
43                    activity_service: ActivityService = Depends()):
44    activity = await activity_service.get()
45    return templates.TemplateResponse('base.html', context={'request': request,
46                                                            'activities': activity})
47    # ...
48    return RedirectResponse(redirect_url, status_code=303)
49

As you've noticed, redirecting with 307 keeps the HTTP method and body.

Fully working example:
1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38&lt;form action=&quot;{{ url_for('event_create')}}&quot; method=&quot;POST&quot;&gt;
39...
40&lt;/form&gt;
41@router.get('/', response_class=HTMLResponse)
42async def main_page(request: Request,
43                    activity_service: ActivityService = Depends()):
44    activity = await activity_service.get()
45    return templates.TemplateResponse('base.html', context={'request': request,
46                                                            'activities': activity})
47    # ...
48    return RedirectResponse(redirect_url, status_code=303)
49from fastapi import FastAPI, APIRouter, Request
50from fastapi.responses import RedirectResponse, HTMLResponse
51
52
53router = APIRouter()
54
55@router.get('/form')
56def form():
57    return HTMLResponse(&quot;&quot;&quot;
58    &lt;html&gt;
59    &lt;form action=&quot;/event/create&quot; method=&quot;POST&quot;&gt;
60    &lt;button&gt;Send request&lt;/button&gt;
61    &lt;/form&gt;
62    &lt;/html&gt;
63    &quot;&quot;&quot;)
64
65@router.post('/create')
66async def event_create(
67        request: Request
68):
69    event = {&quot;id&quot;: 123}
70    redirect_url = request.url_for('get_event', **{'pk': event['id']})
71    return RedirectResponse(redirect_url, status_code=303)
72
73
74@router.get('/{pk}')
75async def get_event(
76        request: Request,
77        pk: int,
78):
79    return f'&lt;html&gt;oi pk={pk}&lt;/html&gt;'
80
81app = FastAPI(title='Test API')
82
83app.include_router(router, prefix=&quot;/event&quot;)
84

To run, install pip install fastapi uvicorn and run with:

1router = APIRouter()
2
3@router.post('/create', response_model=EventBase)
4async def event_create(
5        request: Request,
6        user_id: str = Depends(get_current_user),
7        service: EventsService = Depends(),
8        form: EventForm = Depends(EventForm.as_form)
9):
10    event = await service.post(
11       ...
12   )
13    redirect_url = request.url_for('get_event', **{'pk': event['id']})
14    return RedirectResponse(redirect_url)
15
16
17@router.get('/{pk}', response_model=EventSingle)
18async def get_event(
19        request: Request,
20        pk: int,
21        service: EventsService = Depends()
22):
23    ....some logic....
24    return templates.TemplateResponse(
25        'event.html',
26        context=
27        {
28            ...
29        }
30    )
31api_router = APIRouter()
32
33...
34api_router.include_router(events.router, prefix=&quot;/event&quot;)
35127.0.0.1:37772 - &quot;POST /event/22 HTTP/1.1&quot; 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38&lt;form action=&quot;{{ url_for('event_create')}}&quot; method=&quot;POST&quot;&gt;
39...
40&lt;/form&gt;
41@router.get('/', response_class=HTMLResponse)
42async def main_page(request: Request,
43                    activity_service: ActivityService = Depends()):
44    activity = await activity_service.get()
45    return templates.TemplateResponse('base.html', context={'request': request,
46                                                            'activities': activity})
47    # ...
48    return RedirectResponse(redirect_url, status_code=303)
49from fastapi import FastAPI, APIRouter, Request
50from fastapi.responses import RedirectResponse, HTMLResponse
51
52
53router = APIRouter()
54
55@router.get('/form')
56def form():
57    return HTMLResponse(&quot;&quot;&quot;
58    &lt;html&gt;
59    &lt;form action=&quot;/event/create&quot; method=&quot;POST&quot;&gt;
60    &lt;button&gt;Send request&lt;/button&gt;
61    &lt;/form&gt;
62    &lt;/html&gt;
63    &quot;&quot;&quot;)
64
65@router.post('/create')
66async def event_create(
67        request: Request
68):
69    event = {&quot;id&quot;: 123}
70    redirect_url = request.url_for('get_event', **{'pk': event['id']})
71    return RedirectResponse(redirect_url, status_code=303)
72
73
74@router.get('/{pk}')
75async def get_event(
76        request: Request,
77        pk: int,
78):
79    return f'&lt;html&gt;oi pk={pk}&lt;/html&gt;'
80
81app = FastAPI(title='Test API')
82
83app.include_router(router, prefix=&quot;/event&quot;)
84uvicorn --reload --host 0.0.0.0 --port 3000 example:app
85

Then, point your browser to: http://localhost:3000/event/form

Source https://stackoverflow.com/questions/70773879

QUESTION

uvicorn [fastapi] python run both HTTP and HTTPS

Asked 2022-Mar-01 at 20:49

I'm trying to run a fastapi app with SSL.

I am running the app with uvicorn.

I can run the server on port 80 with HTTP,

1if __name__ == '__main__':
2    uvicorn.run(&quot;main:app&quot;, port=80, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;])
3

To run the port with HTTPS, I do the following,

1if __name__ == '__main__':
2    uvicorn.run(&quot;main:app&quot;, port=80, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;])
3if __name__ == '__main__':
4    uvicorn.run(&quot;main:app&quot;, port=443, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;], ssl_keyfile=&quot;/etc/letsencrypt/live/my_domain/privkey.pem&quot;, ssl_certfile=&quot;/etc/letsencrypt/live/my_domain/fullchain.pem&quot;)
5

How can I run both or simply integrate https redirect?

N.B: This is a setup on a server where I don't want to use nginx, I know how to use nginx to implement https redirect.

ANSWER

Answered 2021-Oct-03 at 16:23

Run a subprocess to return a redirect response from one port to another.

main.py:

1if __name__ == '__main__':
2    uvicorn.run(&quot;main:app&quot;, port=80, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;])
3if __name__ == '__main__':
4    uvicorn.run(&quot;main:app&quot;, port=443, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;], ssl_keyfile=&quot;/etc/letsencrypt/live/my_domain/privkey.pem&quot;, ssl_certfile=&quot;/etc/letsencrypt/live/my_domain/fullchain.pem&quot;)
5if __name__ == '__main__':
6    Popen(['python', '-m', 'https_redirect'])  # Add this
7    uvicorn.run(
8        'main:app', port=443, host='0.0.0.0',
9        reload=True, reload_dirs=['html_files'],
10        ssl_keyfile='/etc/letsencrypt/live/my_domain/privkey.pem',
11        ssl_certfile='/etc/letsencrypt/live/my_domain/fullchain.pem')
12

https_redirect.py:

1if __name__ == '__main__':
2    uvicorn.run(&quot;main:app&quot;, port=80, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;])
3if __name__ == '__main__':
4    uvicorn.run(&quot;main:app&quot;, port=443, host='0.0.0.0', reload = True, reload_dirs = [&quot;html_files&quot;], ssl_keyfile=&quot;/etc/letsencrypt/live/my_domain/privkey.pem&quot;, ssl_certfile=&quot;/etc/letsencrypt/live/my_domain/fullchain.pem&quot;)
5if __name__ == '__main__':
6    Popen(['python', '-m', 'https_redirect'])  # Add this
7    uvicorn.run(
8        'main:app', port=443, host='0.0.0.0',
9        reload=True, reload_dirs=['html_files'],
10        ssl_keyfile='/etc/letsencrypt/live/my_domain/privkey.pem',
11        ssl_certfile='/etc/letsencrypt/live/my_domain/fullchain.pem')
12import uvicorn
13from fastapi import FastAPI
14from starlette.requests import Request
15from starlette.responses import RedirectResponse
16
17app = FastAPI()
18
19
20@app.route('/{_:path}')
21async def https_redirect(request: Request):
22    return RedirectResponse(request.url.replace(scheme='https'))
23
24if __name__ == '__main__':
25    uvicorn.run('https_redirect:app', port=80, host='0.0.0.0')
26

Source https://stackoverflow.com/questions/69138537

QUESTION

how to render a json from a dataframe in fastAPI

Asked 2022-Feb-23 at 09:03

I have a csv file that i want to render in a fastAPI app. I only managed to render te csv in json format like following:

1def transform_question_format(csv_file_name):
2
3    json_file_name = f&quot;{csv_file_name[:-4]}.json&quot;
4
5    # transforms the csv file into json file
6    pd.read_csv(csv_file_name ,sep=&quot;,&quot;).to_json(json_file_name)
7
8    with open(json_file_name, &quot;r&quot;) as f:
9        json_data = json.load(f)
10
11    return json_data
12
13@app.get(&quot;/questions&quot;)
14def load_questions():
15
16    question_json = transform_question_format(question_csv_filename)
17
18    return question_json
19

When i tried directly pd.read_csv(csv_file_name ,sep=",").to_json(json_file_name) in return it does work as it returns a string. How should I proceed ? I believe this is not the good way to do it.

ANSWER

Answered 2022-Feb-23 at 09:03

The below shows four different ways to return the data from a csv file.

Option 1 is to get the file data as JSON and parse it into a dict. You can optionally change the orientation of the data using the orient parameter in the to_json() method.

  • Update 1: Using to_dict() method might be a better option, as there is no need for parsing the JSON string.
  • Update 2: When using to_dict() method and returning the dict, FastAPI, behind the scenes, automatically converts that return value into JSON, using the jsonable_encoder. Thus, to avoid that extra work, you could still use to_json() method, but instead of parsing the JSON string, put it in a Response and return it directly, as shown in the example below.

Option 2 is to return the data in string form, using to_string() method.

Option 3 is to return the data as an HTML table, using to_html() method.

Option 4 is to return the file as is using FastAPI's FileResponse.

1def transform_question_format(csv_file_name):
2
3    json_file_name = f&quot;{csv_file_name[:-4]}.json&quot;
4
5    # transforms the csv file into json file
6    pd.read_csv(csv_file_name ,sep=&quot;,&quot;).to_json(json_file_name)
7
8    with open(json_file_name, &quot;r&quot;) as f:
9        json_data = json.load(f)
10
11    return json_data
12
13@app.get(&quot;/questions&quot;)
14def load_questions():
15
16    question_json = transform_question_format(question_csv_filename)
17
18    return question_json
19from fastapi import FastAPI, Response
20from fastapi.responses import FileResponse
21from fastapi.responses import HTMLResponse
22import pandas as pd
23import json
24
25df = pd.read_csv(&quot;file.csv&quot;)
26app = FastAPI()
27
28def parse_csv(df):
29    res = df.to_json(orient=&quot;records&quot;)
30    parsed = json.loads(res)
31    return parsed
32    
33@app.get(&quot;/questions&quot;)
34def load_questions():
35    return Response(df.to_json(orient=&quot;records&quot;), media_type=&quot;application/json&quot;)  # Option 1 (Updated 2): Return as `JSON`
36    #return df.to_dict(orient=&quot;records&quot;)  # Option 1 (Updated 1): Return as dict (encoded to JSON behind the scenes)
37    #return parse_csv(df)  # Option 1: Parse the JSON string and return as dict (encoded to JSON behind the scenes)
38    #return df.to_string()  # Option 2: Return as string
39    #return HTMLResponse(content=df.to_html(), status_code=200)  # Option 3: Return as HTML Table
40    #return FileResponse(path=&quot;file.csv&quot;, filename=&quot;file.csv&quot;)   # Option 4: Return as File
41

Source https://stackoverflow.com/questions/71203579

QUESTION

FastAPI - Pydantic - Value Error Raises Internal Server Error

Asked 2022-Jan-14 at 12:44

I am using FastAPI with Pydantic.

My problem - I need to raise ValueError using Pydantic

1from fastapi import FastAPI
2from pydantic import BaseModel, validator
3from fastapi import Depends, HTTPException
4
5app = FastAPI()
6
7class RankInput(BaseModel):
8
9    rank: int
10
11    @validator('rank')
12    def check_if_value_in_range(cls, v):
13        &quot;&quot;&quot;
14        check if input rank is within range
15        &quot;&quot;&quot;
16        if not 0 &lt; v &lt; 1000001:
17
18            raise ValueError(&quot;Rank Value Must be within range (0,1000000)&quot;)
19            #raise HTTPException(status_code=400, detail=&quot;Rank Value Error&quot;) - this works But I am looking for a solution using ValueError
20        return v
21
22def get_info_by_rank(rank):
23    return rank
24
25@app.get('/rank/{rank}')
26async def get_rank(value: RankInput = Depends()):
27    result = get_info_by_rank(value.rank)
28    return result
29

this piece of code gives Internal Server Error when a ValueError is raised

1from fastapi import FastAPI
2from pydantic import BaseModel, validator
3from fastapi import Depends, HTTPException
4
5app = FastAPI()
6
7class RankInput(BaseModel):
8
9    rank: int
10
11    @validator('rank')
12    def check_if_value_in_range(cls, v):
13        &quot;&quot;&quot;
14        check if input rank is within range
15        &quot;&quot;&quot;
16        if not 0 &lt; v &lt; 1000001:
17
18            raise ValueError(&quot;Rank Value Must be within range (0,1000000)&quot;)
19            #raise HTTPException(status_code=400, detail=&quot;Rank Value Error&quot;) - this works But I am looking for a solution using ValueError
20        return v
21
22def get_info_by_rank(rank):
23    return rank
24
25@app.get('/rank/{rank}')
26async def get_rank(value: RankInput = Depends()):
27    result = get_info_by_rank(value.rank)
28    return result
29INFO:     127.0.0.1:59427 - &quot;GET /info/?rank=-1 HTTP/1.1&quot; 500 Internal Server Error
30ERROR:    Exception in ASGI application
31Traceback (most recent call last):
32  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
33    result = await app(self.scope, self.receive, self.send)
34  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
35    return await self.app(scope, receive, send)
36  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
37    await super().__call__(scope, receive, send)
38  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
39    await self.middleware_stack(scope, receive, send)
40  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
41    raise exc from None
42  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
43    await self.app(scope, receive, _send)
44  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
45    raise exc from None
46  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
47    await self.app(scope, receive, sender)
48  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
49    await route.handle(scope, receive, send)
50  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
51    await self.app(scope, receive, send)
52  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
53    response = await func(request)
54  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
55    dependency_overrides_provider=dependency_overrides_provider,
56  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
57    solved = await run_in_threadpool(call, **sub_values)
58  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
59    return await loop.run_in_executor(None, func, *args)
60  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
61    result = self.fn(*self.args, **self.kwargs)
62  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
63pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
64rank
65  ValueError() takes no keyword arguments (type=type_error)
66ERROR:uvicorn.error:Exception in ASGI application
67Traceback (most recent call last):
68  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
69    result = await app(self.scope, self.receive, self.send)
70  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
71    return await self.app(scope, receive, send)
72  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
73    await super().__call__(scope, receive, send)
74  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
75    await self.middleware_stack(scope, receive, send)
76  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
77    raise exc from None
78  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
79    await self.app(scope, receive, _send)
80  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
81    raise exc from None
82  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
83    await self.app(scope, receive, sender)
84  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
85    await route.handle(scope, receive, send)
86  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
87    await self.app(scope, receive, send)
88  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
89    response = await func(request)
90  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
91    dependency_overrides_provider=dependency_overrides_provider,
92  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
93    solved = await run_in_threadpool(call, **sub_values)
94  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
95    return await loop.run_in_executor(None, func, *args)
96  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
97    result = self.fn(*self.args, **self.kwargs)
98  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
99pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
100rank
101  ValueError() takes no keyword arguments (type=type_error)
102

I also checked https://github.com/tiangolo/fastapi/issues/2180.

But I was not able to figure out a solution.

What I need to do is Raise ValueError with a Custom Status Code.

Note - I know I can get the Job Done by raising HTTPException.

But I am looking for a solution using ValueError

Could you tell me where I am going wrong?

Have Also Posted this Issue on Github - https://github.com/tiangolo/fastapi/issues/3761

ANSWER

Answered 2021-Aug-25 at 04:48

If you're not raising an HTTPException then normally any other uncaught exception will generate a 500 response (an Internal Server Error). If your intent is to respond with some other custom error message and HTTP status when raising a particular exception - say, ValueError - then you can use add a global exception handler to your app:

1from fastapi import FastAPI
2from pydantic import BaseModel, validator
3from fastapi import Depends, HTTPException
4
5app = FastAPI()
6
7class RankInput(BaseModel):
8
9    rank: int
10
11    @validator('rank')
12    def check_if_value_in_range(cls, v):
13        &quot;&quot;&quot;
14        check if input rank is within range
15        &quot;&quot;&quot;
16        if not 0 &lt; v &lt; 1000001:
17
18            raise ValueError(&quot;Rank Value Must be within range (0,1000000)&quot;)
19            #raise HTTPException(status_code=400, detail=&quot;Rank Value Error&quot;) - this works But I am looking for a solution using ValueError
20        return v
21
22def get_info_by_rank(rank):
23    return rank
24
25@app.get('/rank/{rank}')
26async def get_rank(value: RankInput = Depends()):
27    result = get_info_by_rank(value.rank)
28    return result
29INFO:     127.0.0.1:59427 - &quot;GET /info/?rank=-1 HTTP/1.1&quot; 500 Internal Server Error
30ERROR:    Exception in ASGI application
31Traceback (most recent call last):
32  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
33    result = await app(self.scope, self.receive, self.send)
34  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
35    return await self.app(scope, receive, send)
36  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
37    await super().__call__(scope, receive, send)
38  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
39    await self.middleware_stack(scope, receive, send)
40  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
41    raise exc from None
42  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
43    await self.app(scope, receive, _send)
44  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
45    raise exc from None
46  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
47    await self.app(scope, receive, sender)
48  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
49    await route.handle(scope, receive, send)
50  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
51    await self.app(scope, receive, send)
52  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
53    response = await func(request)
54  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
55    dependency_overrides_provider=dependency_overrides_provider,
56  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
57    solved = await run_in_threadpool(call, **sub_values)
58  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
59    return await loop.run_in_executor(None, func, *args)
60  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
61    result = self.fn(*self.args, **self.kwargs)
62  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
63pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
64rank
65  ValueError() takes no keyword arguments (type=type_error)
66ERROR:uvicorn.error:Exception in ASGI application
67Traceback (most recent call last):
68  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
69    result = await app(self.scope, self.receive, self.send)
70  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
71    return await self.app(scope, receive, send)
72  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
73    await super().__call__(scope, receive, send)
74  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
75    await self.middleware_stack(scope, receive, send)
76  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
77    raise exc from None
78  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
79    await self.app(scope, receive, _send)
80  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
81    raise exc from None
82  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
83    await self.app(scope, receive, sender)
84  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
85    await route.handle(scope, receive, send)
86  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
87    await self.app(scope, receive, send)
88  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
89    response = await func(request)
90  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
91    dependency_overrides_provider=dependency_overrides_provider,
92  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
93    solved = await run_in_threadpool(call, **sub_values)
94  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
95    return await loop.run_in_executor(None, func, *args)
96  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
97    result = self.fn(*self.args, **self.kwargs)
98  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
99pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
100rank
101  ValueError() takes no keyword arguments (type=type_error)
102from fastapi import FastAPI, Request
103from fastapi.responses import JSONResponse
104
105
106@app.exception_handler(ValueError)
107async def value_error_exception_handler(request: Request, exc: ValueError):
108    return JSONResponse(
109        status_code=400,
110        content={&quot;message&quot;: str(exc)},
111    )
112

This will give a 400 response (or you can change the status code to whatever you like) like this:

1from fastapi import FastAPI
2from pydantic import BaseModel, validator
3from fastapi import Depends, HTTPException
4
5app = FastAPI()
6
7class RankInput(BaseModel):
8
9    rank: int
10
11    @validator('rank')
12    def check_if_value_in_range(cls, v):
13        &quot;&quot;&quot;
14        check if input rank is within range
15        &quot;&quot;&quot;
16        if not 0 &lt; v &lt; 1000001:
17
18            raise ValueError(&quot;Rank Value Must be within range (0,1000000)&quot;)
19            #raise HTTPException(status_code=400, detail=&quot;Rank Value Error&quot;) - this works But I am looking for a solution using ValueError
20        return v
21
22def get_info_by_rank(rank):
23    return rank
24
25@app.get('/rank/{rank}')
26async def get_rank(value: RankInput = Depends()):
27    result = get_info_by_rank(value.rank)
28    return result
29INFO:     127.0.0.1:59427 - &quot;GET /info/?rank=-1 HTTP/1.1&quot; 500 Internal Server Error
30ERROR:    Exception in ASGI application
31Traceback (most recent call last):
32  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
33    result = await app(self.scope, self.receive, self.send)
34  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
35    return await self.app(scope, receive, send)
36  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
37    await super().__call__(scope, receive, send)
38  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
39    await self.middleware_stack(scope, receive, send)
40  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
41    raise exc from None
42  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
43    await self.app(scope, receive, _send)
44  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
45    raise exc from None
46  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
47    await self.app(scope, receive, sender)
48  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
49    await route.handle(scope, receive, send)
50  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
51    await self.app(scope, receive, send)
52  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
53    response = await func(request)
54  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
55    dependency_overrides_provider=dependency_overrides_provider,
56  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
57    solved = await run_in_threadpool(call, **sub_values)
58  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
59    return await loop.run_in_executor(None, func, *args)
60  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
61    result = self.fn(*self.args, **self.kwargs)
62  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
63pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
64rank
65  ValueError() takes no keyword arguments (type=type_error)
66ERROR:uvicorn.error:Exception in ASGI application
67Traceback (most recent call last):
68  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py&quot;, line 396, in run_asgi
69    result = await app(self.scope, self.receive, self.send)
70  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py&quot;, line 45, in __call__
71    return await self.app(scope, receive, send)
72  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py&quot;, line 199, in __call__
73    await super().__call__(scope, receive, send)
74  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py&quot;, line 111, in __call__
75    await self.middleware_stack(scope, receive, send)
76  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 181, in __call__
77    raise exc from None
78  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py&quot;, line 159, in __call__
79    await self.app(scope, receive, _send)
80  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 82, in __call__
81    raise exc from None
82  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py&quot;, line 71, in __call__
83    await self.app(scope, receive, sender)
84  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 566, in __call__
85    await route.handle(scope, receive, send)
86  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 227, in handle
87    await self.app(scope, receive, send)
88  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py&quot;, line 41, in app
89    response = await func(request)
90  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py&quot;, line 195, in app
91    dependency_overrides_provider=dependency_overrides_provider,
92  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py&quot;, line 550, in solve_dependencies
93    solved = await run_in_threadpool(call, **sub_values)
94  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py&quot;, line 34, in run_in_threadpool
95    return await loop.run_in_executor(None, func, *args)
96  File &quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py&quot;, line 57, in run
97    result = self.fn(*self.args, **self.kwargs)
98  File &quot;pydantic/main.py&quot;, line 400, in pydantic.main.BaseModel.__init__
99pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
100rank
101  ValueError() takes no keyword arguments (type=type_error)
102from fastapi import FastAPI, Request
103from fastapi.responses import JSONResponse
104
105
106@app.exception_handler(ValueError)
107async def value_error_exception_handler(request: Request, exc: ValueError):
108    return JSONResponse(
109        status_code=400,
110        content={&quot;message&quot;: str(exc)},
111    )
112{
113    &quot;message&quot;: &quot;Value Must be within range (0,1000000)&quot;
114}
115

Source https://stackoverflow.com/questions/68914523

QUESTION

Kill a python subprocess that does not return

Asked 2022-Jan-06 at 13:00

TLDR I want to kill a subprocess like top while it is still running
I am using Fastapi to run a command on input. For example if I enter top my program runs the command but since it does not return, at the moment I have to use a time delay then kill/terminate it. However I want to be able to kill it while it is still running. However at the moment it won't run my kill command until the time runs out. Here is the current code for running a process:

1@app.put(&quot;/command/{command}&quot;)
2async def run_command(command: str):
3    subprocess.run([command], shell=True, timeout=10)
4    return {&quot;run command&quot;}
5

and to kill it

1@app.put(&quot;/command/{command}&quot;)
2async def run_command(command: str):
3    subprocess.run([command], shell=True, timeout=10)
4    return {&quot;run command&quot;}
5@app.get(&quot;/stop&quot;)
6async def stop():
7    proc.kill()
8    return{&quot;Stop&quot;}
9

I am new to fastapi so I would be grateful for any help

ANSWER

Answered 2022-Jan-06 at 13:00

It's because subprocess.run is blocking itself - you need to run shell command in background e.g. if you have asnycio loop already on, you could use subprocesses

1@app.put(&quot;/command/{command}&quot;)
2async def run_command(command: str):
3    subprocess.run([command], shell=True, timeout=10)
4    return {&quot;run command&quot;}
5@app.get(&quot;/stop&quot;)
6async def stop():
7    proc.kill()
8    return{&quot;Stop&quot;}
9import asyncio
10
11process = None
12@app.get(&quot;/command/{command}&quot;)
13async def run_command(command: str):
14    global process
15    process = await asyncio.create_subprocess_exec(
16        command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
17    )
18    return {&quot;run command&quot;}
19
20@app.get(&quot;/stop&quot;)
21async def stop():
22    process.kill()
23    return {&quot;Stop&quot;}
24

Or with Popen

1@app.put(&quot;/command/{command}&quot;)
2async def run_command(command: str):
3    subprocess.run([command], shell=True, timeout=10)
4    return {&quot;run command&quot;}
5@app.get(&quot;/stop&quot;)
6async def stop():
7    proc.kill()
8    return{&quot;Stop&quot;}
9import asyncio
10
11process = None
12@app.get(&quot;/command/{command}&quot;)
13async def run_command(command: str):
14    global process
15    process = await asyncio.create_subprocess_exec(
16        command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
17    )
18    return {&quot;run command&quot;}
19
20@app.get(&quot;/stop&quot;)
21async def stop():
22    process.kill()
23    return {&quot;Stop&quot;}
24from subprocess import Popen
25
26process = None
27@app.get(&quot;/command/{command}&quot;)
28async def run_command(command: str):
29    global process
30    process = Popen([command])  # something long running
31    return {&quot;run command&quot;}
32

Adding the timeout option can be tricky as you do not want to wait until it completes (where you could indeed use wait_for function) but rather kill process after specific time. As far I know the best option would be to schedule other process which is responsible for killing main one. The code with asyncio could look like that:

1@app.put(&quot;/command/{command}&quot;)
2async def run_command(command: str):
3    subprocess.run([command], shell=True, timeout=10)
4    return {&quot;run command&quot;}
5@app.get(&quot;/stop&quot;)
6async def stop():
7    proc.kill()
8    return{&quot;Stop&quot;}
9import asyncio
10
11process = None
12@app.get(&quot;/command/{command}&quot;)
13async def run_command(command: str):
14    global process
15    process = await asyncio.create_subprocess_exec(
16        command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
17    )
18    return {&quot;run command&quot;}
19
20@app.get(&quot;/stop&quot;)
21async def stop():
22    process.kill()
23    return {&quot;Stop&quot;}
24from subprocess import Popen
25
26process = None
27@app.get(&quot;/command/{command}&quot;)
28async def run_command(command: str):
29    global process
30    process = Popen([command])  # something long running
31    return {&quot;run command&quot;}
32@app.get(&quot;/command/{command}&quot;)
33async def run_command(command: str):
34    global process
35    process = await asyncio.create_subprocess_exec(
36        command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
37    )
38    loop = asyncio.get_event_loop()
39    # We schedule here stop coroutine execution that runs after TIMEOUT seconds
40    loop.call_later(TIMEOUT, asyncio.create_task, stop(process.pid))
41
42    return {&quot;run command&quot;}
43
44@app.get(&quot;/stop&quot;)
45async def stop(pid=None):
46    global process
47    # We need to make sure we won't kill different process
48    if process and (pid is None or process.pid == pid):
49        process.kill()
50        process = None
51    return {&quot;Stop&quot;}
52

Source https://stackoverflow.com/questions/70489306

QUESTION

&quot;422 Unprocessable Entity&quot; error when making POST request with both attributes and key using FastAPI

Asked 2021-Dec-19 at 11:55

I have a file called main.py as follows:

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21

Now, if I run the code for the test, saved in the file test_main.py

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35

I don't get the desired result, that is

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36

What is the mistake?

ANSWER

Answered 2021-Dec-19 at 11:55

Let's start by explaining what you are doing wrong.


FastAPI's TestClient is just a re-export of Starlette's TestClient which is a subclass of requests.Session. The requests library's post method has the following signature:

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46

Your code

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51

is doing multiple things wrong:

  1. It is passing in arguments to both the data and the json parameter, which is wrong because you can't have 2 different request bodies. You either pass in data or json, but not both. The data is typically used for form-encoded inputs from HTML forms, while json is for raw JSON objects. See the requests docs on "More complicated POST requests".
  2. The requests library will simply drop the json argument because:

    Note, the json parameter is ignored if either data or files is passed.

  3. It is passing-in the plain string "Baz" to the json parameter, which is not a valid JSON object.
  4. The data parameter expects form-encoded data.

The full error returned by FastAPI in the response is:

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66

The 1st error says key is missing from the query, meaning the route parameter key value "Baz" was not in the request body and FastAPI tried to look for it from the query parameters (see the FastAPI docs on Request body + path + query parameters).

The 2nd error is from point #4 I listed above about data not being properly form-encoded (that error does go away when you wrap the dict value in json.dumps, but that's not important nor is it part of the solution).

You said in a comment that you were trying to do the same thing as in the FastAPI Testing Tutorial. The difference of that tutorial from your code is that was POSTing all the attributes of the Item object in 1 body, and that it was using the json= parameter of .post.


Now on the solutions!

Solution #1: Have a separate class for POSTing the item attributes with a key

Here, you'll need 2 classes, one with a key attribute that you use for the POST request body (let's call it NewItem), and your current one Item for the internal DB and for the response model. Your route function will then have just 1 parameter (new_item) and you can just get the key from that object.

main.py

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82

For the test .post code, use json= to pass all the fields in 1 dictionary.

test_main.py

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94

Output

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94id='baz' title='A test title' description='A test description'
95200 OK
96{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
97
Solution #2: Have 2 body parts, 1 for the item attributes and 1 for the key

You can structure the POSTed body like this instead:

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94id='baz' title='A test title' description='A test description'
95200 OK
96{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
97{
98    &quot;item&quot;: {
99        &quot;id&quot;: &quot;baz&quot;,
100        &quot;title&quot;: &quot;A test title&quot;,
101        &quot;description&quot;: &quot;A test description&quot;,
102    },
103    &quot;key&quot;: &quot;Baz&quot;,
104},
105

where you have the Item attributes in a nested dict and then have a simple key-value pair in the same level as item. FastAPI can handle this, see the docs on Singular values in body, which fits your example quite nicely:

For example, extending the previous model, you could decide that you want to have another key importance in the same body, besides the item and user.

If you declare it as is, because it is a singular value, FastAPI will assume that it is a query parameter.

But you can instruct FastAPI to treat it as another body key using Body

Note the parts I emphasized, about telling FastAPI to look for key in the same body. It is important here that the parameter names item and key match the ones in the request body.

main.py

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94id='baz' title='A test title' description='A test description'
95200 OK
96{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
97{
98    &quot;item&quot;: {
99        &quot;id&quot;: &quot;baz&quot;,
100        &quot;title&quot;: &quot;A test title&quot;,
101        &quot;description&quot;: &quot;A test description&quot;,
102    },
103    &quot;key&quot;: &quot;Baz&quot;,
104},
105from fastapi import Body, FastAPI
106
107class Item(BaseModel):
108    id: str
109    title: str
110    description: Optional[str] = None
111
112@app.post(&quot;/items/&quot;, response_model=Item)
113async def create_item(item: Item, key: str = Body(...)):
114    print(item)
115    fake_db[key] = item
116    return item
117

Again, for making the .post request, use json= to pass the entire dictionary.

test_main.py

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94id='baz' title='A test title' description='A test description'
95200 OK
96{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
97{
98    &quot;item&quot;: {
99        &quot;id&quot;: &quot;baz&quot;,
100        &quot;title&quot;: &quot;A test title&quot;,
101        &quot;description&quot;: &quot;A test description&quot;,
102    },
103    &quot;key&quot;: &quot;Baz&quot;,
104},
105from fastapi import Body, FastAPI
106
107class Item(BaseModel):
108    id: str
109    title: str
110    description: Optional[str] = None
111
112@app.post(&quot;/items/&quot;, response_model=Item)
113async def create_item(item: Item, key: str = Body(...)):
114    print(item)
115    fake_db[key] = item
116    return item
117def test_create_item():
118    response = client.post(
119        &quot;/items/&quot;,
120        json={
121            &quot;item&quot;: {
122                &quot;id&quot;: &quot;baz&quot;,
123                &quot;title&quot;: &quot;A test title&quot;,
124                &quot;description&quot;: &quot;A test description&quot;,
125            },
126            &quot;key&quot;: &quot;Baz&quot;,
127        },
128    )
129    print(response.status_code, response.reason)
130    return response.json()
131

Output

1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8    &quot;Foo&quot;: {&quot;id&quot;: &quot;foo&quot;, &quot;title&quot;: &quot;Foo&quot;, &quot;description&quot;: &quot;There goes my hero&quot;},
9    &quot;Bar&quot;: {&quot;id&quot;: &quot;bar&quot;, &quot;title&quot;: &quot;Bar&quot;, &quot;description&quot;: &quot;The bartenders&quot;},
10}
11
12class Item(BaseModel):
13    id: str
14    title: str
15    description: Optional[str] = None
16
17@app.post(&quot;/items/&quot;, response_model=Item)
18async def create_item(item: Item, key: str):
19    fake_db[key] = item
20    return item
21from fastapi.testclient import TestClient
22from main import app
23
24client = TestClient(app)
25
26def test_create_item():
27    response = client.post(
28        &quot;/items/&quot;,
29        {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
30        &quot;Baz&quot;
31    )
32    return response.json()
33
34print(test_create_item())
35{&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}
36def post(self, url, data=None, json=None, **kwargs):
37    r&quot;&quot;&quot;Sends a POST request. Returns :class:`Response` object.
38
39    :param url: URL for the new :class:`Request` object.
40    :param data: (optional) Dictionary, list of tuples, bytes, or file-like
41        object to send in the body of the :class:`Request`.
42    :param json: (optional) json to send in the body of the :class:`Request`.
43    :param \*\*kwargs: Optional arguments that ``request`` takes.
44    :rtype: requests.Response
45    &quot;&quot;&quot;
46response = client.post(
47    &quot;/items/&quot;,
48    {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;},
49    &quot;Baz&quot;,
50)
51def test_create_item():
52    response = client.post(
53        &quot;/items/&quot;, {&quot;id&quot;: &quot;baz&quot;, &quot;title&quot;: &quot;A test title&quot;, &quot;description&quot;: &quot;A test description&quot;}, &quot;Baz&quot;
54    )
55    print(response.status_code)
56    print(response.reason)
57    return response.json()
58
59# 422 Unprocessable Entity
60# {'detail': [{'loc': ['query', 'key'],
61#              'msg': 'field required',
62#             'type': 'value_error.missing'},
63#             {'loc': ['body'],
64#              'msg': 'value is not a valid dict',
65#             'type': 'type_error.dict'}]}
66class Item(BaseModel):
67    id: str
68    title: str
69    description: Optional[str] = None
70
71class NewItem(Item):
72    key: str
73
74@app.post(&quot;/items/&quot;, response_model=Item)
75async def create_item(new_item: NewItem):
76    # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
77    # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
78    item = Item(**new_item.dict())
79    print(item)
80    fake_db[new_item.key] = item
81    return item
82def test_create_item():
83    response = client.post(
84        &quot;/items/&quot;,
85        json={
86            &quot;key&quot;: &quot;Baz&quot;,
87            &quot;id&quot;: &quot;baz&quot;,
88            &quot;title&quot;: &quot;A test title&quot;,
89            &quot;description&quot;: &quot;A test description&quot;,
90        },
91    )
92    print(response.status_code, response.reason)
93    return response.json()
94id='baz' title='A test title' description='A test description'
95200 OK
96{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
97{
98    &quot;item&quot;: {
99        &quot;id&quot;: &quot;baz&quot;,
100        &quot;title&quot;: &quot;A test title&quot;,
101        &quot;description&quot;: &quot;A test description&quot;,
102    },
103    &quot;key&quot;: &quot;Baz&quot;,
104},
105from fastapi import Body, FastAPI
106
107class Item(BaseModel):
108    id: str
109    title: str
110    description: Optional[str] = None
111
112@app.post(&quot;/items/&quot;, response_model=Item)
113async def create_item(item: Item, key: str = Body(...)):
114    print(item)
115    fake_db[key] = item
116    return item
117def test_create_item():
118    response = client.post(
119        &quot;/items/&quot;,
120        json={
121            &quot;item&quot;: {
122                &quot;id&quot;: &quot;baz&quot;,
123                &quot;title&quot;: &quot;A test title&quot;,
124                &quot;description&quot;: &quot;A test description&quot;,
125            },
126            &quot;key&quot;: &quot;Baz&quot;,
127        },
128    )
129    print(response.status_code, response.reason)
130    return response.json()
131id='baz' title='A test title' description='A test description'
132200 OK
133{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
134

Source https://stackoverflow.com/questions/70404952

QUESTION

FastAPI responding slowly when calling through other Python app, but fast in cURL

Asked 2021-Nov-27 at 22:15

I have an issue that I can't wrap my head around. I have an API service built using FastAPI, and when I try to call any endpoint from another Python script on my local machine, the response takes 2+ seconds. When I send the same request through cURL or the built-in Swagger docs, the response is nearly instant.

The entire server script is this:

1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get(&quot;/&quot;)
7async def root():
8    return {&quot;message&quot;: &quot;Hello World&quot;}
9
10if __name__ == '__main__':
11    uvicorn.run(app, host='0.0.0.0', port=8000)
12

I then call it from a test script using HTTPX. I also tried with the requests package, and it is the same result.

1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get(&quot;/&quot;)
7async def root():
8    return {&quot;message&quot;: &quot;Hello World&quot;}
9
10if __name__ == '__main__':
11    uvicorn.run(app, host='0.0.0.0', port=8000)
12import httpx
13r = httpx.get('http://localhost:8000/')
14print(r.elapsed)
15

This prints something like: 0:00:02.069705

I then do the same thing using cURL:

1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get(&quot;/&quot;)
7async def root():
8    return {&quot;message&quot;: &quot;Hello World&quot;}
9
10if __name__ == '__main__':
11    uvicorn.run(app, host='0.0.0.0', port=8000)
12import httpx
13r = httpx.get('http://localhost:8000/')
14print(r.elapsed)
15curl -w &quot;@curl-format.txt&quot; -o /dev/null -X 'GET' 'http://localhost:8000/'
16

This prints:

1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get(&quot;/&quot;)
7async def root():
8    return {&quot;message&quot;: &quot;Hello World&quot;}
9
10if __name__ == '__main__':
11    uvicorn.run(app, host='0.0.0.0', port=8000)
12import httpx
13r = httpx.get('http://localhost:8000/')
14print(r.elapsed)
15curl -w &quot;@curl-format.txt&quot; -o /dev/null -X 'GET' 'http://localhost:8000/'
16  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
17                                 Dload  Upload   Total   Spent    Left  Speed
18100    32  100    32    0     0    941      0 --:--:-- --:--:-- --:--:--   969
19     time_namelookup:  0.006436s
20        time_connect:  0.006747s
21     time_appconnect:  0.000000s
22    time_pretransfer:  0.006788s
23       time_redirect:  0.000000s
24  time_starttransfer:  0.034037s
25                     ----------
26          time_total:  0.034093s
27

The issue isn't what the endpoint does, but rather that it doesn't even start executing for 2 seconds. I have a debugger running on the endpoint, and the first line only gets executed after those 2 seconds.

I tried to inspect the request to see whether there are any headers or similar in the request that could slow it down, but nothing. When I try again with the headers generated by HTTPX, it still executes fast:

1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get(&quot;/&quot;)
7async def root():
8    return {&quot;message&quot;: &quot;Hello World&quot;}
9
10if __name__ == '__main__':
11    uvicorn.run(app, host='0.0.0.0', port=8000)
12import httpx
13r = httpx.get('http://localhost:8000/')
14print(r.elapsed)
15curl -w &quot;@curl-format.txt&quot; -o /dev/null -X 'GET' 'http://localhost:8000/'
16  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
17                                 Dload  Upload   Total   Spent    Left  Speed
18100    32  100    32    0     0    941      0 --:--:-- --:--:-- --:--:--   969
19     time_namelookup:  0.006436s
20        time_connect:  0.006747s
21     time_appconnect:  0.000000s
22    time_pretransfer:  0.006788s
23       time_redirect:  0.000000s
24  time_starttransfer:  0.034037s
25                     ----------
26          time_total:  0.034093s
27curl -w &quot;@curl-format.txt&quot; -o /dev/null -X 'GET' \
28  'http://localhost:8000/events' \
29  -H 'accept: */*' \
30  -H 'host: localhost:8000' \
31  -H 'accept-encoding: gzip, deflate' \
32  -H 'connection: keep-alive' \
33  -H 'user-agent: python-httpx/0.20.0'
34

Here is a screenshot of the request in PyCharm, it unfortunately can't be dumped to JSON directly. enter image description here

I'm starting to think that it has something to do with Uvicorn and how it runs the app, but I can't figure out why.

ANSWER

Answered 2021-Nov-27 at 22:15

Try using „127.0.0.1“ instead of „localhost“ to refer to your machine. I once had a similar issue, where the DNS lookup for localhost on windows was taking half a second or longer. I don‘t have an explanation for that behaviour, but at least one other person on SO seems to have struggled with it as well…

Source https://stackoverflow.com/questions/70138815

QUESTION

How to apply transaction logic in FastAPI RealWorld example app?

Asked 2021-Nov-20 at 02:01

I am using nsidnev/fastapi-realworld-example-app.

I need to apply transaction logic to this project.

In one API, I am calling a lot of methods from repositories and doing updating, inserting and deleting operations in many tables. If there is an exception in any of these operations, how can I roll back changes? (Or if everything is correct then commit.)

ANSWER

Answered 2021-Nov-20 at 02:01

nsidnev/fastapi-realworld-example-app is using asyncpg.

There are two ways to use Transactions.

1. async with statement
1async with conn.transaction():
2    await repo_one.update_one(...)
3    await repo_two.insert_two(...)
4    await repo_three.delete_three(...)
5
6    # This automatically rolls back the transaction:
7    raise Exception
8
2. start, rollback, commit statements
1async with conn.transaction():
2    await repo_one.update_one(...)
3    await repo_two.insert_two(...)
4    await repo_three.delete_three(...)
5
6    # This automatically rolls back the transaction:
7    raise Exception
8tx = conn.transaction()
9await tx.start()
10
11try:
12    await repo_one.update_one(...)
13    await repo_two.insert_two(...)
14    await repo_three.delete_three(...)
15except:
16    await tx.rollback()
17    raise
18else:
19    await tx.commit()
20

Getting the connection conn in routes

Inject conn: Connection = Depends(_get_connection_from_pool).

1async with conn.transaction():
2    await repo_one.update_one(...)
3    await repo_two.insert_two(...)
4    await repo_three.delete_three(...)
5
6    # This automatically rolls back the transaction:
7    raise Exception
8tx = conn.transaction()
9await tx.start()
10
11try:
12    await repo_one.update_one(...)
13    await repo_two.insert_two(...)
14    await repo_three.delete_three(...)
15except:
16    await tx.rollback()
17    raise
18else:
19    await tx.commit()
20from asyncpg.connection import Connection
21from fastapi import Depends
22
23from app.api.dependencies.database import _get_connection_from_pool
24
25
26@router.post(
27    ...
28)
29async def create_new_article(
30    ...
31    conn: Connection = Depends(_get_connection_from_pool),  # Add this
32) -&gt; ArticleInResponse:
33

Source https://stackoverflow.com/questions/70002709

Community Discussions contain sources that include Stack Exchange Network

Tutorials and Learning Resources in Fastapi

Tutorials and Learning Resources are not available at this moment for Fastapi

Share this Page

share link

Get latest updates on Fastapi