405 method not allowed when using 'put', but success with 'get', why? - python

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.

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):
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:
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:
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.

Related

Fast API, accept an array in post request

I'm trying to learn how to use the Fast API library.
I'm trying to accept an array in the post using frozenset as the docs states, but it doesn't seem to work.
import logging
from fastapi import FastAPI, BackgroundTasks
from worker.celery_app import celery_app
log = logging.getLogger(__name__)
app = FastAPI()
def celery_on_message(body):
"""
Logs the initiation of the endpoint
"""
log.warn(body)
def background_on_message(task):
"""
logs the function when it is added to queue
"""
log.warn(task.get(on_message=celery_on_message, propagate=False))
#app.get("/")
async def root(stocks: frozenset, background_task: BackgroundTasks):
"""
:param stocks: stocks to be analyzed
:param background_task: initiate the tasks queue
:type background_task: starlette.background.BackgroundTasks
:return:
"""
task_name = None
# set correct task name based on the way you run the example
log.log(level=1, msg=f'{stocks}')
task_name = "app.app.worker.celery_worker.compute_stock_indicators"
task = celery_app.send_task(task_name, args=[stocks])
background_task.add_task(background_on_message, task)
return {"message": "Stocks analyzed"}
When I'm using the swagger docs to send a request:
curl -X GET "http://127.0.0.1:8000/?stocks=wix&stocks=amzn" -H "accept: application/json"
the response is:
{
"detail": [
{
"loc": [
"query",
"stocks"
],
"msg": "value is not a valid frozenset",
"type": "type_error.frozenset"
}
]
}
I think there is a small mistake
You are trying to send a POST request but you only have a GET endpoint
#app.post("/")
this should fix the issue, but also, when using Type to annotate, use typing
from typing import FrozenSet
#app.post("/")
async def root(stocks: FrozenSet, background_task: BackgroundTasks):
Also receiving a Body in GET request is now supported by World Wide Web(W3), but it's also highly discouraged. See W3 Publication about it. OpenAPI includes the information of body in the request, and it 'll be supported by OpenAPI in the next releases, but it's not supported yet but SwaggerUI.

Alamofire 5 (Beta 6): Parameters of PUT Request do not arrive in Flask-Restful

UPDATE
For me the Problem got fixed as soon as I was putting "encoding: URLEncoding(destination: .queryString)" in my request. Maybe this helps somebody else. link
I struggled the whole day to find the problem in my Alamofire PUT Request or the Flask Restful API. Request like GET, DELETE and POST are working fine with Alamofire, except the PUT Request.
When I'm using PUT Requests in combination with Postman and Flask-Restful everything is also working fine. But as soon as I'm trying to achieve the same Result with Alamofire, I'm not getting any parameters in Flask. I tried to illustrate this in the code examples.
So in short my example illustrates the following:
DELETE Request(Same with GET and POST)
Postman: success
Alamofire: success
PUT Request
Postman: success
Alamofire: failure (parameter dictionary empty in Flask-Restful)
Here is my Python Code [API Server]:
from flask import Flask, request, jsonify
from flask_restful import Resource, Api, reqparse
app = Flask(__name__)
api = Api(app)
class Stackoverflow(Resource):
def delete(self):
print(request.args)
if request.args.get('test-key') is None:
return jsonify({"message": "failure"})
else:
return jsonify({"message": "success"})
def put(self):
print(request.args)
if request.args.get('test-key') is None:
return jsonify({"message": "failure"})
else:
return jsonify({"message": "success"})
api.add_resource(Stackoverflow, '/stackoverflow')
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
If I'm using Postman, I get this result (like expected):
Result in Postman
But now I'm trying to do the same with Alamofire in Swift. Same Server, nothing changed.
SWIFT demo Code [IOS APP]:
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view
simplePUTRequest()
simpleDELETERequest()
}
func simplePUTRequest(){
AF.request("http://localhost:5000/stackoverflow", method: .put, parameters: ["test-key":"testvalue"])
.validate(statusCode: 200..<300)
.responseJSON { response in
if let data = response.data {
print("Result PUT Request:")
print(String(data: data, encoding: .utf8)!)
//print(utf8Text)
}else{
}
}
}
func simpleDELETERequest(){
AF.request("http://localhost:5000/stackoverflow", method: .delete, parameters: ["test-key":"testvalue"])
.validate(statusCode: 200..<300)
.responseJSON { response in
if let data = response.data {
print("Result DELETE Request:")
print(String(data: data, encoding: .utf8)!)
//print(utf8Text)
}else{
}
}
}
Xcode Console:
Result PUT Request:
{
"message": "failure"
}
Result DELETE Request:
{
"message": "success"
}
python Console (both Alamofire Requests):
ImmutableMultiDict([])
127.0.0.1 - - [15/Jun/2019 21:17:31] "PUT /stackoverflow HTTP/1.1" 200 -
ImmutableMultiDict([('test-key', 'testvalue')])
127.0.0.1 - - [15/Jun/2019 21:17:31] "DELETE /stackoverflow?test-key=testvalue HTTP/1.1" 200 -
As you can see, I'm getting the success message only while using the DELETE method.
Till now I tried using different encodings like URLEncoding.httpbody and URLEncoding.default, but nothing really helped.
For me it seems like it's a Alamofire/Swift Problem, because in Postman the same request method is working fine.
I would really appreciate your help, because I'm stuck and don't know anything further to do. I hope I didn't misunderstood something essential.
Thank you in advance!
I am currently using the same version AlamoFire, and when I use the PUT method, I use it as follows:
let request = AF.request(url, method: .put, parameters: ["uid": uid],
encoding: JSONEncoding.default, headers: headers)
request.responseJSON(completionHandler: { response in
guard response.error == nil else {
//Handle error
}
if let json = response.value as? [String: Any]
// Handle result.
}
The only difference to your post is that I used the encoding option. You can try to put the option and see what happens.
It looks like your server is expecting your PUT parameters to be URL form encoded into the URL. You may be hitting the version of the request method that uses JSON encoding by default, so adding encoder: URLEncodedFormParameterEncoder.default at the end of your request call should fix that. A future release will make that the default, as it's safe across all request types.
If that's not the issue, I suggest you investigate more closely to see what the differences between the requests may be. Since you control the server you should have easy access to the traffic.

Kivy UrlRequest

My API works fine and I see a 200 status when I test it using Postman. However I'm trying to access it using a Kivy application but I'm seeing a 400 response from the server after some waiting or quitting the app. By the way when testing with Postman I specify header as Content-Type: application/json and in body I see my parameters
{
"search_text": "Hello",
"num_results": 1
}
being sent as raw data.
My code
def search(self, search_text):
header = {'Content-Type':'application/json'}
req = UrlRequest('http://127.0.0.1:5000/search',req_body={"search_text": search_text,"num_results": 1},on_success=Test.got_json,req_headers=header)
print("Search method called")
#staticmethod
def got_json(req,result):
print(result)
Kivy docs say that you don't have to specify a method as this would send a POST request so I've not specified that here
Edit: The code for the server is kind of irrelevant for my issue here so I've removed it
UrlRequest should be passed a str object as request body. You can serialize the request dictionary as a string object by dumping it. Pass this dumped dictionary as the request body to UrlRequest.
import json
req_body=json.dumps({'search_text': search_text, 'num_results': 1})
req = UrlRequest(
'http://127.0.0.1:5000/search',
req_body=req_body,
on_success=Test.got_json,
req_headers=header)
req_body is a string parameter, might be a bit confusing as req_headers is a dict. You can use:
req_body=json.dumps({"search_text": search_text,"num_results": 1})

Launch Jenkins parametrized build from script

I am trying to launch a Jenkins parametrized job from a python script. Due to environment requirements, I can't install python-jenkins. I am using raw requests module.
This job I am trying to launch has three parameters:
string (let's call it payload)
string (let's call it target)
file (a file, optional)
I've searched and search, without any success.
I managed to launch the job with two string parameters by launching:
import requests
url = "http://myjenkins/job/MyJobName/buildWithParameters"
target = "http://10.44.542.62:20000"
payload = "{payload: content}"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
msg = {
'token': 'token',
'payload': [ payload ],
'target': [ target ],
}
r = requests.post(url, headers=headers, data=msg)
However I am unable to send a file and those arguments in single request.
I've tried requests.post file argument and failed.
It turns out it is impossible to send both data and file in a single request via HTTP.
import jenkinsapi
from jenkinsHandler import JenkinsHandler
in your python script
Pass parameters to buildJob() , (like < your JenkinsHandler object name>.buildJob())
JenkinsHandler module has functions like init() , buildJob(), isRunning() which helps in triggering the build
Here is an example:
curl -vvv -X POST http://127.0.0.1:8080/jenkins/job/jobname/build
--form file0='#/tmp/yourfile'
--form json='{"parameter": [{"name":"file", "file":"file0"}]}'
--form json='{"parameter": [{"name":"payload", "value":"123"}]
--form json='{"parameter": [{"name":"target", "value":"456"}]}'

Creating ticket with attachment in Freshdesk

I want to create a ticket with attachment in freshdesk api. I am able to create a ticket without attachment. This is my sample code:
post_dict = {
'helpdesk_ticket': {
'description': "Testing Code sample 3",
'subject': "example7",
'email': "example7#example.com",
'priority': 2,
'status': 2,
},
}
headers = {'Content-Type': 'application/json'}
r = requests.post(FRESHDESK_URL + '/helpdesk/tickets.json',
auth=(FRESHDESK_API_KEY, "X"),
headers=headers,
data=json.dumps(post_dict),
)
raw_input(r.status_code)
raw_input(r.content)
This is for just creating ticket in Freshdesk. Now using the same post_dict i would like to create tickets with attachments. Any suggestions about how I can achieve this using this json request method or any other methods are welcome.
Creating a ticket with an attachment requires a multi-part form submission. Unfortunately that means that the request is very different to a simple request without an attachment.
Each field needs to be written to the request as a separate form with a 'boundary' before it and a character return after it.
Then, each attachment should be written to the request, again with a boundary before the attachment is written and a character return added after.
At the end of the response, a final boundary must be written. This is the same as the string used for the boundary but also includes 2 dashes ( -- ) before and after the boundary to signify it as final. Without the final boundary, FreshDesk gives a 500 internal server error since something changed in their api a few weeks back (it used to accept a non-final boundary at the end).
For Send attachment, the Content-Type must be in multipart/form-data.The sample cURL is may helps you.
curl -v -u yourmail#gmail.com:yourpassword -F
"attachments[]=#/path/to/attachment1.ext" -F
"attachments[]=#/path/to/attachment2.ext" -F "email=example#example.com" -F
"subject=Ticket Title" -F "description=this is a sample ticket" -X POST
'https://yoururl.freshdesk.com/api/v2/tickets'

Categories

Resources