Skip to content
Advertisement

405 method not allowed when using ‘put’, but success with ‘get’, why?

I’m learning FastAPI from the official documentation.

When I try running the first example from https://fastapi.tiangolo.com/tutorial/body-multiple-params/ and pasted the URL http://127.0.0.1:8000/items/35 in the browser, my server sends a message

405 Method not allowed

Example code from the link is like below,

from typing import Optional

from fastapi import FastAPI, Path
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
    q: Optional[str] = None,
    item: Optional[Item] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    if item:
        results.update({"item": item})
    return results

I understand q and item parameters are optional in this example, so think it can respond with only item_id variable, but it fails.

But if I changed the method to get, which means modified the code with @app.put("/items/{item_id}"), it works.

I want to know what makes this difference.

Advertisement

Answer

The main mistake is this:

pasted URL http://127.0.0.1:8000/items/35 in browser, my server sends a message 405 Method not allowed.

Pasting the URL in the browser’s address bar sends a GET request, but your route is defined to be called with a PUT request (as explained in the FastAPI tutorial on creating a path operation),

@app.put("/items/{item_id}")
async def update_item(
    ...

…so the path ("/items/{item_id}") matches but the supported HTTP method does not match (PUT != GET). Hence, the 405 Method Not Allowed error (“the server knows the request method, but the target resource doesn’t support this method“).

You can see this GET request by opening up your browser’s developer console and checking the networking tab (I’m using Firefox in the following screenshot, but the feature should be available in Chrome, Safari, and other browsers):

screenshot of Firefox's developer console

The simplest way to make a proper PUT request is to use FastAPI’s auto-generated interactive API docs. If you are following the tutorial, it should be at http://127.0.0.1:8000/docs. Again, using the developer console you should see it uses the correct HTTP method:

screenshot of swagger docs + developer console

There are a number of alternatives to the interactive API docs.

From the API docs, you can see that each API can be called using the curl command-line utility:

screenshot of curl command in swagger docs

If you are on Linux or Mac, then you can copy-paste that exact command on a terminal/console to call your API (take note of the -X PUT option to specify the correct HTTP method).

~$ curl -i -X 'PUT'                                                                'http://127.0.0.1:8000/items/35' 
  -H 'accept: application/json' 
  -H 'Content-Type: application/json' 
  -d '{
  "name": "string",
  "description": "string",
  "price": 0,
  "tax": 0
}'
HTTP/1.1 200 OK
date: Sun, 09 Jan 2022 06:47:33 GMT
server: uvicorn
content-length: 84
content-type: application/json

{"item_id":35,"item":{"name":"string","description":"string","price":0.0,"tax":0.0}}

If you looking for a more programmatic solution, take a look at the requests library or the asynchronous httpx library (which is the same one recommended by FastAPI in its tutorial for testing your routes):

In [24]: import httpx

In [25]: body = {"name":"XYZ", "description":"This is a test.", "price":1.23, "tax":None}

In [26]: r = httpx.put("http://127.0.0.1:8000/items/35", json=body)

In [27]: r.status_code, r.json()
Out[27]: 
(200,
 {'item_id': 35,
  'item': {'name': 'XYZ',
   'description': 'This is a test.',
   'price': 1.23,
   'tax': None}})

In [28]: r = httpx.put("http://127.0.0.1:8000/items/35?q=abc", json=body)

In [29]: r.status_code, r.json()
Out[29]: 
(200,
 {'item_id': 35,
  'q': 'abc',
  'item': {'name': 'XYZ',
   'description': 'This is a test.',
   'price': 1.23,
   'tax': None}})

There are also desktop apps that allow you to make these requests using a “nice” UI, such as Postman and Insomnia.

Basically, my point is to stop pasting your API URLs on the browser address bar and instead familiarize yourself with the other ways of making a proper request.


I understand q and item parameters are optional in this example, so think it can respond with only item_id variable, but it fails.

This isn’t related to the 405 error.

But if I changed the method to get, which means modified the code with @app.put("/items/{item_id}"), it works.

Yes, since now it will match the browser’s GET request. But this is not the solution, especially since that part of the tutorial is talking about updating data (update_item) and a GET request is not the appropriate method for that.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement