10

I have a file called main.py in which I put a POST call with only one input parameter (integer). Simplified code is given below:

from fastapi import FastAPI app = FastAPI() @app.post("/do_something/") async def do_something(process_id: int): # some code return {"process_id": process_id} 

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

from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_do_something(): response = client.post( "/do_something/", json={ "process_id": 16 } ) return response.json() print(test_do_something()) 

I get:

{'detail': [{'loc': ['query', 'process_id'], 'msg': 'field required', 'type': 'value_error.missing'}]} 

I can't figure out what the mistake is. It is necessary that it remains a POST call.

2 Answers 2

13

The error, basically, says that the required query parameter process_id is missing. The reason for that error is that you send a POST request with request body, i.e., JSON payload; however, your endpoint expects a query parameter.

To receive the data in JSON format instead, one needs to create a Pydantic BaseModel—as shown below—and send the data from the client in the same way you already do.

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): process_id: int @app.post("/do_something") async def do_something(item: Item): return item 

Test the above as shown in your question:

def test_do_something(): response = client.post("/do_something", json={"process_id": 16}) return response.json() 

If, however, you had to pass a query parameter, then you would create an endpoint in the same way you did, that is:

@app.post("/do_something") async def do_something(process_id: int): return {"process_id": process_id} 

but on client side, you would need to add the parameter to the URL itself, as described in the documentation (e.g., "/do_something?process_id=16"), or use the params attribute and as shown below:

def test_do_something(): response = client.post("/do_something", params={"process_id": 16}) return response.json() 

Update

Alternatively, another way to pass JSON data when having a single body parameter is to use Body(..., embed=True), as shown below:

@app.post("/do_something") def do_something(process_id: int = Body(..., embed=True)): return process_id 

For more details and options on how to post JSON data in FastAPI, please have a look at this answer and this answer.

Sign up to request clarification or add additional context in comments.

2 Comments

Is it possible to do this without creating a Pydantic model? Normally, if I have multiple parameters, I use, for instance: async def do_something_else(items: List[Dict], process_id: int = Body(...)) in the definition and client.post("/do_something_else/", json={"process_id": ..., "items": [{...}, {...}]}) for the call.
But Body(...) doesn't work if I have only one numeric parameter, as in the example in my question.
0

If you want to pass a query parameter in a post request, you can do so by passing it as params=. In this case, we define the endpoint exactly as in the OP. A working code:

from fastapi import FastAPI from fastapi.testclient import TestClient app = FastAPI() @app.post("/do_something/") async def do_something(process_id: int): # <--- same as in OP # some code return {"process_id": process_id} client = TestClient(app) response = client.post( "/do_something/", params={"process_id": 16} # <--- different here ) print(response.json()) 

Another option is to change how we extract data in the endpoint. Instead of type-hinting for an integer, type hint for a dictionary and get the response_id from the received dict. In this case, we make the post request exactly as in the OP. A working code:

from fastapi import FastAPI from fastapi.testclient import TestClient app = FastAPI() @app.post("/do_something/") async def do_something(item: dict[str, int]): # <--- expect a dict process_id = item.get("process_id") # <--- get process_id # some code return {"process_id": process_id} client = TestClient(app) response = client.post( "/do_something/", json={"process_id": 16} # <--- same as in OP ) print(response.json()) 

Of course, we can replace dict[str, int] above with a TypedDict sub-class as well.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.