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
by vinta python
102379 NOASSERTION
A curated list of awesome Python frameworks, libraries, software and resources
by tiangolo python
44054 MIT
FastAPI framework, high performance, easy to learn, fast to code, ready for production
by tiangolo python
7183 MIT
SQL databases in Python, designed for simplicity, compatibility, and robustness.
by encode python
2763 BSD-3-Clause
Async database support for Python. 🗄
by ml-tooling python
2593 MIT
🪄 Turns your machine learning code into microservices with web API, interactive GUI, and more.
by python-gino python
2337 NOASSERTION
GINO Is Not ORM - a Python asyncio ORM on SQLAlchemy core.
by fastapi-users python
1841 MIT
Ready-to-use and customizable users management for FastAPI
by codingforentrepreneurs html
1811 MIT
Learn Python for the next 30 (or so) Days.
by nsidnev python
1784 MIT
Backend logic implementation for https://github.com/gothinkster/realworld with awesome FastAPI
Trending New libraries in Fastapi
by tiangolo python
7183 MIT
SQL databases in Python, designed for simplicity, compatibility, and robustness.
by ml-tooling python
2593 MIT
🪄 Turns your machine learning code into microservices with web API, interactive GUI, and more.
by ExpDev07 python
1530 GPL-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!
by ml-tooling python
1488 CC-BY-SA-4.0
🏆 A ranked list of awesome python libraries for web development. Updated weekly.
by ebhy python
1193 Apache-2.0
Deploy a ML inference service on a budget in less than 10 lines of code.
by collerek python
902 MIT
python async orm with fastapi in mind and pydantic validation
by fastapi-admin python
858 Apache-2.0
A fast admin dashboard based on FastAPI and TortoiseORM with tabler ui, inspired by Django admin
by dmontagu python
746 MIT
Reusable utilities for FastAPI
by ycd python
640 MIT
:rocket: CLI tool for FastAPI. Generating new FastAPI projects & boilerplates made easy.
Top Authors in Fastapi
1
13 Libraries
1097
2
11 Libraries
396
3
9 Libraries
290
4
7 Libraries
93
5
6 Libraries
54057
6
5 Libraries
1071
7
4 Libraries
980
8
4 Libraries
1889
9
4 Libraries
4386
10
4 Libraries
425
1
13 Libraries
1097
2
11 Libraries
396
3
9 Libraries
290
4
7 Libraries
93
5
6 Libraries
54057
6
5 Libraries
1071
7
4 Libraries
980
8
4 Libraries
1889
9
4 Libraries
4386
10
4 Libraries
425
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
"422 Unprocessable Entity" 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:191class 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__ = "user_attribute"
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__ = "user_attribute"
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__ = "user_attribute"
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("/attributes/", 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__ = "user_attribute"
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("/attributes/", 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 -> 0
22 value is not a valid dict (type=type_error.dict)
23response -> 1
24 value is not a valid dict (type=type_error.dict)
25response -> 2
26 value is not a valid dict (type=type_error.dict)
27response -> 3
28 value is not a valid dict (type=type_error.dict)
29response -> 4
30 value is not a valid dict (type=type_error.dict)
31response -> 5
32 value is not a valid dict (type=type_error.dict)
33response -> 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:19SQLAlchemy 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__ = "user_attribute"
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("/attributes/", 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 -> 0
22 value is not a valid dict (type=type_error.dict)
23response -> 1
24 value is not a valid dict (type=type_error.dict)
25response -> 2
26 value is not a valid dict (type=type_error.dict)
27response -> 3
28 value is not a valid dict (type=type_error.dict)
29response -> 4
30 value is not a valid dict (type=type_error.dict)
31response -> 5
32 value is not a valid dict (type=type_error.dict)
33response -> 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__ = "user_attribute"
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("/attributes/", 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 -> 0
22 value is not a valid dict (type=type_error.dict)
23response -> 1
24 value is not a valid dict (type=type_error.dict)
25response -> 2
26 value is not a valid dict (type=type_error.dict)
27response -> 3
28 value is not a valid dict (type=type_error.dict)
29response -> 4
30 value is not a valid dict (type=type_error.dict)
31response -> 5
32 value is not a valid dict (type=type_error.dict)
33response -> 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
).
QUESTION
Conditional call of a FastApi Model
Asked 2022-Mar-20 at 10:36I 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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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:00There 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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51
Finally you can create your routes through a route factory:
1
2{
3 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51def generate_language_specific_router(language: str, ...) -> APIRouter:
52 router = APIRouter(prefix=language)
53 MySelectedModel: MyModelABC = my_class_factory[language]
54
55 @router.post("/")
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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51def generate_language_specific_router(language: str, ...) -> APIRouter:
52 router = APIRouter(prefix=language)
53 MySelectedModel: MyModelABC = my_class_factory[language]
54
55 @router.post("/")
56 def post_something(my_model_data: MySelectedModel):
57 # My internal logic
58 return router
59dictionnary_of_babel = {
60 "word1": {
61 "en": "word1",
62 "fr": "mot1",
63 },
64 "word2": {
65 "en": "word2",
66 },
67 "Drinking Water Composition": {
68 "en": "Drinking Water Composition",
69 "fr": "Composition de l'eau de boisson",
70 },
71}
72
73my_arbitrary_object = {
74 "attribute1": "word1",
75 "attribute2": "word2",
76 "attribute3": "Drinking Water Composition",
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81 if "fr" in dictionnary_of_babel[english_sentence].keys():
82 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["fr"]
83 else:
84 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["en"] # ou sans "en"
85
86expected_translated_object = {
87 "attribute1": "mot1",
88 "attribute2": "word2",
89 "attribute3": "Composition de l'eau de boisson",
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 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51def generate_language_specific_router(language: str, ...) -> APIRouter:
52 router = APIRouter(prefix=language)
53 MySelectedModel: MyModelABC = my_class_factory[language]
54
55 @router.post("/")
56 def post_something(my_model_data: MySelectedModel):
57 # My internal logic
58 return router
59dictionnary_of_babel = {
60 "word1": {
61 "en": "word1",
62 "fr": "mot1",
63 },
64 "word2": {
65 "en": "word2",
66 },
67 "Drinking Water Composition": {
68 "en": "Drinking Water Composition",
69 "fr": "Composition de l'eau de boisson",
70 },
71}
72
73my_arbitrary_object = {
74 "attribute1": "word1",
75 "attribute2": "word2",
76 "attribute3": "Drinking Water Composition",
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81 if "fr" in dictionnary_of_babel[english_sentence].keys():
82 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["fr"]
83 else:
84 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["en"] # ou sans "en"
85
86expected_translated_object = {
87 "attribute1": "mot1",
88 "attribute2": "word2",
89 "attribute3": "Composition de l'eau de boisson",
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: "sentence"
95
96# internationalized
97my_attribute_internationalized: {
98 sentence: {
99 original_lang: "sentence"
100 lang1: "sentence_lang1",
101 lang2: "sentence_lang2",
102 }
103}
104
A simple tactic to generalize string translation is to define an anonymous function _()
that embeds the translation like:
1
2{
3 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51def generate_language_specific_router(language: str, ...) -> APIRouter:
52 router = APIRouter(prefix=language)
53 MySelectedModel: MyModelABC = my_class_factory[language]
54
55 @router.post("/")
56 def post_something(my_model_data: MySelectedModel):
57 # My internal logic
58 return router
59dictionnary_of_babel = {
60 "word1": {
61 "en": "word1",
62 "fr": "mot1",
63 },
64 "word2": {
65 "en": "word2",
66 },
67 "Drinking Water Composition": {
68 "en": "Drinking Water Composition",
69 "fr": "Composition de l'eau de boisson",
70 },
71}
72
73my_arbitrary_object = {
74 "attribute1": "word1",
75 "attribute2": "word2",
76 "attribute3": "Drinking Water Composition",
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81 if "fr" in dictionnary_of_babel[english_sentence].keys():
82 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["fr"]
83 else:
84 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["en"] # ou sans "en"
85
86expected_translated_object = {
87 "attribute1": "mot1",
88 "attribute2": "word2",
89 "attribute3": "Composition de l'eau de boisson",
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: "sentence"
95
96# internationalized
97my_attribute_internationalized: {
98 sentence: {
99 original_lang: "sentence"
100 lang1: "sentence_lang1",
101 lang2: "sentence_lang2",
102 }
103}
104CURRENT_MODULE_LANG = "fr"
105
106def _(original_string: str) -> str:
107 """Switch from original_string to translation"""
108 return dictionnary_of_babel[original_string][CURRENT_MODULE_LANG]
109
Then call it everywhere a translation is needed:
1
2{
3 "_id": xxxxxxx,
4 "en": {
5 "title": "Drinking Water Composition",
6 "description": "Drinking water composition expressed in... with pesticides.",
7 "category": "Water",
8 "tags": ["water","pesticides"]
9 },
10 "fr": {
11 "title": "Composition de l'eau de boisson",
12 "description": "Composition de l'eau de boisson exprimée en... présence de pesticides....",
13 "category": "Eau",
14 "tags": ["eau","pesticides"]
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("?lang=fr", response_description="Add a dataset")
27async def create_dataset(request:Request, dataset: DatasetFR = Body(...), lang:str="fr"):
28 ...
29 return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_dataset)
30
31@router.post("?lang=en", response_description="Add a dataset")
32async def create_dataset(request:Request, dataset: DatasetEN = Body(...), lang:str="en"):
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 "fr": MyModelFr,
49 "en": MyModelEn,
50}
51def generate_language_specific_router(language: str, ...) -> APIRouter:
52 router = APIRouter(prefix=language)
53 MySelectedModel: MyModelABC = my_class_factory[language]
54
55 @router.post("/")
56 def post_something(my_model_data: MySelectedModel):
57 # My internal logic
58 return router
59dictionnary_of_babel = {
60 "word1": {
61 "en": "word1",
62 "fr": "mot1",
63 },
64 "word2": {
65 "en": "word2",
66 },
67 "Drinking Water Composition": {
68 "en": "Drinking Water Composition",
69 "fr": "Composition de l'eau de boisson",
70 },
71}
72
73my_arbitrary_object = {
74 "attribute1": "word1",
75 "attribute2": "word2",
76 "attribute3": "Drinking Water Composition",
77}
78
79my_translated_object = {}
80for attribute, english_sentence in my_arbitrary_object.items():
81 if "fr" in dictionnary_of_babel[english_sentence].keys():
82 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["fr"]
83 else:
84 my_translated_object[attribute] = dictionnary_of_babel[english_sentence]["en"] # ou sans "en"
85
86expected_translated_object = {
87 "attribute1": "mot1",
88 "attribute2": "word2",
89 "attribute3": "Composition de l'eau de boisson",
90}
91
92assert expected_translated_object == my_translated_object
93# normal:
94my_attribute: "sentence"
95
96# internationalized
97my_attribute_internationalized: {
98 sentence: {
99 original_lang: "sentence"
100 lang1: "sentence_lang1",
101 lang2: "sentence_lang2",
102 }
103}
104CURRENT_MODULE_LANG = "fr"
105
106def _(original_string: str) -> str:
107 """Switch from original_string to translation"""
108 return dictionnary_of_babel[original_string][CURRENT_MODULE_LANG]
109>>> print(_("word 1"))
110"mot 1"
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)!
QUESTION
fastapi (starlette) RedirectResponse redirect to post instead get method
Asked 2022-Mar-18 at 10:58I 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="/event")
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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38<form action="{{ url_for('event_create')}}" method="POST">
39...
40</form>
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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38<form action="{{ url_for('event_create')}}" method="POST">
39...
40</form>
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:22When 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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38<form action="{{ url_for('event_create')}}" method="POST">
39...
40</form>
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.
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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38<form action="{{ url_for('event_create')}}" method="POST">
39...
40</form>
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("""
58 <html>
59 <form action="/event/create" method="POST">
60 <button>Send request</button>
61 </form>
62 </html>
63 """)
64
65@router.post('/create')
66async def event_create(
67 request: Request
68):
69 event = {"id": 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'<html>oi pk={pk}</html>'
80
81app = FastAPI(title='Test API')
82
83app.include_router(router, prefix="/event")
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="/event")
35127.0.0.1:37772 - "POST /event/22 HTTP/1.1" 405 Method Not Allowed
36redirect_url = request.url_for('get_event', **{'pk': event['id']}, status_code=status.HTTP_302_FOUND)
37starlette.routing.NoMatchFound
38<form action="{{ url_for('event_create')}}" method="POST">
39...
40</form>
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("""
58 <html>
59 <form action="/event/create" method="POST">
60 <button>Send request</button>
61 </form>
62 </html>
63 """)
64
65@router.post('/create')
66async def event_create(
67 request: Request
68):
69 event = {"id": 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'<html>oi pk={pk}</html>'
80
81app = FastAPI(title='Test API')
82
83app.include_router(router, prefix="/event")
84uvicorn --reload --host 0.0.0.0 --port 3000 example:app
85
Then, point your browser to: http://localhost:3000/event/form
QUESTION
uvicorn [fastapi] python run both HTTP and HTTPS
Asked 2022-Mar-01 at 20:49I'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("main:app", port=80, host='0.0.0.0', reload = True, reload_dirs = ["html_files"])
3
To run the port with HTTPS, I do the following,
1if __name__ == '__main__':
2 uvicorn.run("main:app", port=80, host='0.0.0.0', reload = True, reload_dirs = ["html_files"])
3if __name__ == '__main__':
4 uvicorn.run("main:app", port=443, host='0.0.0.0', reload = True, reload_dirs = ["html_files"], ssl_keyfile="/etc/letsencrypt/live/my_domain/privkey.pem", ssl_certfile="/etc/letsencrypt/live/my_domain/fullchain.pem")
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:23Run a subprocess to return a redirect response from one port to another.
main.py:
1if __name__ == '__main__':
2 uvicorn.run("main:app", port=80, host='0.0.0.0', reload = True, reload_dirs = ["html_files"])
3if __name__ == '__main__':
4 uvicorn.run("main:app", port=443, host='0.0.0.0', reload = True, reload_dirs = ["html_files"], ssl_keyfile="/etc/letsencrypt/live/my_domain/privkey.pem", ssl_certfile="/etc/letsencrypt/live/my_domain/fullchain.pem")
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("main:app", port=80, host='0.0.0.0', reload = True, reload_dirs = ["html_files"])
3if __name__ == '__main__':
4 uvicorn.run("main:app", port=443, host='0.0.0.0', reload = True, reload_dirs = ["html_files"], ssl_keyfile="/etc/letsencrypt/live/my_domain/privkey.pem", ssl_certfile="/etc/letsencrypt/live/my_domain/fullchain.pem")
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
QUESTION
how to render a json from a dataframe in fastAPI
Asked 2022-Feb-23 at 09:03I 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"{csv_file_name[:-4]}.json"
4
5 # transforms the csv file into json file
6 pd.read_csv(csv_file_name ,sep=",").to_json(json_file_name)
7
8 with open(json_file_name, "r") as f:
9 json_data = json.load(f)
10
11 return json_data
12
13@app.get("/questions")
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:03The 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 intoJSON
, using thejsonable_encoder
. Thus, to avoid that extra work, you could still use to_json() method, but instead of parsing theJSON
string, put it in aResponse
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"{csv_file_name[:-4]}.json"
4
5 # transforms the csv file into json file
6 pd.read_csv(csv_file_name ,sep=",").to_json(json_file_name)
7
8 with open(json_file_name, "r") as f:
9 json_data = json.load(f)
10
11 return json_data
12
13@app.get("/questions")
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("file.csv")
26app = FastAPI()
27
28def parse_csv(df):
29 res = df.to_json(orient="records")
30 parsed = json.loads(res)
31 return parsed
32
33@app.get("/questions")
34def load_questions():
35 return Response(df.to_json(orient="records"), media_type="application/json") # Option 1 (Updated 2): Return as `JSON`
36 #return df.to_dict(orient="records") # 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="file.csv", filename="file.csv") # Option 4: Return as File
41
QUESTION
FastAPI - Pydantic - Value Error Raises Internal Server Error
Asked 2022-Jan-14 at 12:44I 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 """
14 check if input rank is within range
15 """
16 if not 0 < v < 1000001:
17
18 raise ValueError("Rank Value Must be within range (0,1000000)")
19 #raise HTTPException(status_code=400, detail="Rank Value Error") - 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 """
14 check if input rank is within range
15 """
16 if not 0 < v < 1000001:
17
18 raise ValueError("Rank Value Must be within range (0,1000000)")
19 #raise HTTPException(status_code=400, detail="Rank Value Error") - 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 - "GET /info/?rank=-1 HTTP/1.1" 500 Internal Server Error
30ERROR: Exception in ASGI application
31Traceback (most recent call last):
32 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
33 result = await app(self.scope, self.receive, self.send)
34 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
35 return await self.app(scope, receive, send)
36 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
37 await super().__call__(scope, receive, send)
38 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
39 await self.middleware_stack(scope, receive, send)
40 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
41 raise exc from None
42 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
43 await self.app(scope, receive, _send)
44 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
45 raise exc from None
46 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
47 await self.app(scope, receive, sender)
48 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
49 await route.handle(scope, receive, send)
50 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
51 await self.app(scope, receive, send)
52 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
53 response = await func(request)
54 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
55 dependency_overrides_provider=dependency_overrides_provider,
56 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
57 solved = await run_in_threadpool(call, **sub_values)
58 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
59 return await loop.run_in_executor(None, func, *args)
60 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
61 result = self.fn(*self.args, **self.kwargs)
62 File "pydantic/main.py", 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 "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
69 result = await app(self.scope, self.receive, self.send)
70 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
71 return await self.app(scope, receive, send)
72 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
73 await super().__call__(scope, receive, send)
74 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
75 await self.middleware_stack(scope, receive, send)
76 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
77 raise exc from None
78 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
79 await self.app(scope, receive, _send)
80 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
81 raise exc from None
82 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
83 await self.app(scope, receive, sender)
84 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
85 await route.handle(scope, receive, send)
86 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
87 await self.app(scope, receive, send)
88 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
89 response = await func(request)
90 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
91 dependency_overrides_provider=dependency_overrides_provider,
92 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
93 solved = await run_in_threadpool(call, **sub_values)
94 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
95 return await loop.run_in_executor(None, func, *args)
96 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
97 result = self.fn(*self.args, **self.kwargs)
98 File "pydantic/main.py", 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:48If 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 """
14 check if input rank is within range
15 """
16 if not 0 < v < 1000001:
17
18 raise ValueError("Rank Value Must be within range (0,1000000)")
19 #raise HTTPException(status_code=400, detail="Rank Value Error") - 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 - "GET /info/?rank=-1 HTTP/1.1" 500 Internal Server Error
30ERROR: Exception in ASGI application
31Traceback (most recent call last):
32 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
33 result = await app(self.scope, self.receive, self.send)
34 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
35 return await self.app(scope, receive, send)
36 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
37 await super().__call__(scope, receive, send)
38 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
39 await self.middleware_stack(scope, receive, send)
40 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
41 raise exc from None
42 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
43 await self.app(scope, receive, _send)
44 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
45 raise exc from None
46 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
47 await self.app(scope, receive, sender)
48 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
49 await route.handle(scope, receive, send)
50 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
51 await self.app(scope, receive, send)
52 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
53 response = await func(request)
54 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
55 dependency_overrides_provider=dependency_overrides_provider,
56 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
57 solved = await run_in_threadpool(call, **sub_values)
58 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
59 return await loop.run_in_executor(None, func, *args)
60 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
61 result = self.fn(*self.args, **self.kwargs)
62 File "pydantic/main.py", 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 "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
69 result = await app(self.scope, self.receive, self.send)
70 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
71 return await self.app(scope, receive, send)
72 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
73 await super().__call__(scope, receive, send)
74 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
75 await self.middleware_stack(scope, receive, send)
76 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
77 raise exc from None
78 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
79 await self.app(scope, receive, _send)
80 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
81 raise exc from None
82 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
83 await self.app(scope, receive, sender)
84 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
85 await route.handle(scope, receive, send)
86 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
87 await self.app(scope, receive, send)
88 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
89 response = await func(request)
90 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
91 dependency_overrides_provider=dependency_overrides_provider,
92 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
93 solved = await run_in_threadpool(call, **sub_values)
94 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
95 return await loop.run_in_executor(None, func, *args)
96 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
97 result = self.fn(*self.args, **self.kwargs)
98 File "pydantic/main.py", 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={"message": 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 """
14 check if input rank is within range
15 """
16 if not 0 < v < 1000001:
17
18 raise ValueError("Rank Value Must be within range (0,1000000)")
19 #raise HTTPException(status_code=400, detail="Rank Value Error") - 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 - "GET /info/?rank=-1 HTTP/1.1" 500 Internal Server Error
30ERROR: Exception in ASGI application
31Traceback (most recent call last):
32 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
33 result = await app(self.scope, self.receive, self.send)
34 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
35 return await self.app(scope, receive, send)
36 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
37 await super().__call__(scope, receive, send)
38 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
39 await self.middleware_stack(scope, receive, send)
40 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
41 raise exc from None
42 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
43 await self.app(scope, receive, _send)
44 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
45 raise exc from None
46 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
47 await self.app(scope, receive, sender)
48 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
49 await route.handle(scope, receive, send)
50 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
51 await self.app(scope, receive, send)
52 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
53 response = await func(request)
54 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
55 dependency_overrides_provider=dependency_overrides_provider,
56 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
57 solved = await run_in_threadpool(call, **sub_values)
58 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
59 return await loop.run_in_executor(None, func, *args)
60 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
61 result = self.fn(*self.args, **self.kwargs)
62 File "pydantic/main.py", 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 "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
69 result = await app(self.scope, self.receive, self.send)
70 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
71 return await self.app(scope, receive, send)
72 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
73 await super().__call__(scope, receive, send)
74 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
75 await self.middleware_stack(scope, receive, send)
76 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
77 raise exc from None
78 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
79 await self.app(scope, receive, _send)
80 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
81 raise exc from None
82 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
83 await self.app(scope, receive, sender)
84 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
85 await route.handle(scope, receive, send)
86 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
87 await self.app(scope, receive, send)
88 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
89 response = await func(request)
90 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
91 dependency_overrides_provider=dependency_overrides_provider,
92 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
93 solved = await run_in_threadpool(call, **sub_values)
94 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
95 return await loop.run_in_executor(None, func, *args)
96 File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
97 result = self.fn(*self.args, **self.kwargs)
98 File "pydantic/main.py", 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={"message": str(exc)},
111 )
112{
113 "message": "Value Must be within range (0,1000000)"
114}
115
QUESTION
Kill a python subprocess that does not return
Asked 2022-Jan-06 at 13:00TLDR 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("/command/{command}")
2async def run_command(command: str):
3 subprocess.run([command], shell=True, timeout=10)
4 return {"run command"}
5
and to kill it
1@app.put("/command/{command}")
2async def run_command(command: str):
3 subprocess.run([command], shell=True, timeout=10)
4 return {"run command"}
5@app.get("/stop")
6async def stop():
7 proc.kill()
8 return{"Stop"}
9
I am new to fastapi so I would be grateful for any help
ANSWER
Answered 2022-Jan-06 at 13:00It'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("/command/{command}")
2async def run_command(command: str):
3 subprocess.run([command], shell=True, timeout=10)
4 return {"run command"}
5@app.get("/stop")
6async def stop():
7 proc.kill()
8 return{"Stop"}
9import asyncio
10
11process = None
12@app.get("/command/{command}")
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 {"run command"}
19
20@app.get("/stop")
21async def stop():
22 process.kill()
23 return {"Stop"}
24
Or with Popen
1@app.put("/command/{command}")
2async def run_command(command: str):
3 subprocess.run([command], shell=True, timeout=10)
4 return {"run command"}
5@app.get("/stop")
6async def stop():
7 proc.kill()
8 return{"Stop"}
9import asyncio
10
11process = None
12@app.get("/command/{command}")
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 {"run command"}
19
20@app.get("/stop")
21async def stop():
22 process.kill()
23 return {"Stop"}
24from subprocess import Popen
25
26process = None
27@app.get("/command/{command}")
28async def run_command(command: str):
29 global process
30 process = Popen([command]) # something long running
31 return {"run command"}
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("/command/{command}")
2async def run_command(command: str):
3 subprocess.run([command], shell=True, timeout=10)
4 return {"run command"}
5@app.get("/stop")
6async def stop():
7 proc.kill()
8 return{"Stop"}
9import asyncio
10
11process = None
12@app.get("/command/{command}")
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 {"run command"}
19
20@app.get("/stop")
21async def stop():
22 process.kill()
23 return {"Stop"}
24from subprocess import Popen
25
26process = None
27@app.get("/command/{command}")
28async def run_command(command: str):
29 global process
30 process = Popen([command]) # something long running
31 return {"run command"}
32@app.get("/command/{command}")
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 {"run command"}
43
44@app.get("/stop")
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 {"Stop"}
52
QUESTION
"422 Unprocessable Entity" error when making POST request with both attributes and key using FastAPI
Asked 2021-Dec-19 at 11:55I 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36
What is the mistake?
ANSWER
Answered 2021-Dec-19 at 11:55Let'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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46
Your code
1from typing import Optional
2from fastapi import FastAPI
3from pydantic import BaseModel
4
5app = FastAPI()
6
7fake_db = {
8 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51
is doing multiple things wrong:
- It is passing in arguments to both the
data
and thejson
parameter, which is wrong because you can't have 2 different request bodies. You either pass indata
orjson
, but not both. Thedata
is typically used for form-encoded inputs from HTML forms, whilejson
is for raw JSON objects. See the requests docs on "More complicated POST requests". - The requests library will simply drop the
json
argument because:Note, the
json
parameter is ignored if eitherdata
orfiles
is passed. - It is passing-in the plain string
"Baz"
to thejson
parameter, which is not a valid JSON object. - 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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 keyHere, 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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
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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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 "item": {
99 "id": "baz",
100 "title": "A test title",
101 "description": "A test description",
102 },
103 "key": "Baz",
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 theitem
anduser
.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
usingBody
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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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 "item": {
99 "id": "baz",
100 "title": "A test title",
101 "description": "A test description",
102 },
103 "key": "Baz",
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("/items/", 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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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 "item": {
99 "id": "baz",
100 "title": "A test title",
101 "description": "A test description",
102 },
103 "key": "Baz",
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("/items/", 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 "/items/",
120 json={
121 "item": {
122 "id": "baz",
123 "title": "A test title",
124 "description": "A test description",
125 },
126 "key": "Baz",
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 "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
9 "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
10}
11
12class Item(BaseModel):
13 id: str
14 title: str
15 description: Optional[str] = None
16
17@app.post("/items/", 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 "/items/",
29 {"id": "baz", "title": "A test title", "description": "A test description"},
30 "Baz"
31 )
32 return response.json()
33
34print(test_create_item())
35{"id": "baz", "title": "A test title", "description": "A test description"}
36def post(self, url, data=None, json=None, **kwargs):
37 r"""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 """
46response = client.post(
47 "/items/",
48 {"id": "baz", "title": "A test title", "description": "A test description"},
49 "Baz",
50)
51def test_create_item():
52 response = client.post(
53 "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
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("/items/", 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 "/items/",
85 json={
86 "key": "Baz",
87 "id": "baz",
88 "title": "A test title",
89 "description": "A test description",
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 "item": {
99 "id": "baz",
100 "title": "A test title",
101 "description": "A test description",
102 },
103 "key": "Baz",
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("/items/", 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 "/items/",
120 json={
121 "item": {
122 "id": "baz",
123 "title": "A test title",
124 "description": "A test description",
125 },
126 "key": "Baz",
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
QUESTION
FastAPI responding slowly when calling through other Python app, but fast in cURL
Asked 2021-Nov-27 at 22:15I 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("/")
7async def root():
8 return {"message": "Hello World"}
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("/")
7async def root():
8 return {"message": "Hello World"}
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("/")
7async def root():
8 return {"message": "Hello World"}
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 "@curl-format.txt" -o /dev/null -X 'GET' 'http://localhost:8000/'
16
This prints:
1from fastapi import FastAPI
2import uvicorn
3
4app = FastAPI()
5
6@app.get("/")
7async def root():
8 return {"message": "Hello World"}
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 "@curl-format.txt" -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("/")
7async def root():
8 return {"message": "Hello World"}
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 "@curl-format.txt" -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 "@curl-format.txt" -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.
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:15Try 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…
QUESTION
How to apply transaction logic in FastAPI RealWorld example app?
Asked 2021-Nov-20 at 02:01I 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:01nsidnev/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
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) -> ArticleInResponse:
33
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