Upload Multipart/Form Data to WordPress REST API with Python - python

I have an API I am building out that will allow me to upload media to my WordPress website using their new API. I have searched all over the internet and SO for a solution to this problem, but all of the answers seem to be using an older WordPress API so I have not had much success in finding an answer.
I have the following code written out in a function to send a multipart/form-data request to the API:
img = open(image_path, 'rb')
file_name = os.path.basename(image)
print('Compressing and sending data...')
NEW_HEADER = {
'Authorization': f'BEARER {WORDPRESS_ACCESS_TOKEN}',
'Content-Disposition': f'attachment; filename={file_name}',
'Content-Type': 'multipart/form-data'
}
response = requests.post(
url=WORDPRESS_MEDIA_URL,
files={'media': img.read()},
headers=NEW_HEADER
)
print(response.request)
print(response.text)
return response
Which returns me:
{"media":[],"errors":[]}
<Response [200]>
OK
So, it sends something to the endpoint, but it's return an empty array and empty error box. Again, I can't seem to find any solution for the new WP, so forgive me if the Python seems a little hacky and put-together, I've just been putting together what appears to work.
Further Reading Since Original Posting
I pinged the API with Postman and it returns data as expected:
{"media":[{"id":"xxxx","date":"2022-11-03T20:06:54-05:00","parent":0,"link":"https:mywebsite","title":"mypicture","caption":"","description":"",...}
So, Since the post request seems to not be the issue, I tried to debug my request in Postman and my local console to see what was being sent:
{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Authorization': 'BEARER QnkXS43KhiqO7Oa!9G!zN2gnz90hHhZEribF2ddZu0h8&5V0p5M69^OwDb9E6H%B', 'Content-Type': 'multipart/form-data', 'Content-Length': '6'}
media=
And I see that the media returns empty from my local console. In Postman, the media is returned as undefined (though I'm not sure if that's just Postman being unable to parse the image in a JSON format in the console/debugger.
This leads me to believe that either the way I am accessing/calling the directory with the image is wrong, or the way I am trying to encode it to the request.
Even Further Reading Since Posting
I have since circumnavigated any concerns about directory issues by copying the photos from an external directory to that of one inside the project folder.
Since then, it appears that the current way I am sending the data returns the same response as if I am sending nothing at all:
response = requests.post(
url=WORDPRESS_MEDIA_URL, # there is no data sent in this request.
headers=NEW_HEADER
)
Which still returns me:
{"media":[],"errors":[]}
<Response [200]>
OK
This leads me to believe that the way I am sending the data is incorrect, as WordPress is receiving my request, but is not getting the information that it needs.
So, doing some further reading and research, I come across requests-toolbelt, which is to help sending multipart/form-data:
data = MultipartEncoder(fields={'media': (file_name, img, 'image/jpeg')})
new_header = {
'Authorization': f'BEARER {WORDPRESS_ACCESS_TOKEN}',
'Content-Type': data.content_type,
'Content-Disposition': 'form-data; name="media"; filename="%s"' % name_img,
}
response = requests.post(WORDPRESS_MEDIA_URL, data=data, headers=new_header)
Now, when I send a request with this data, I get this response:
{"error":"unsupported_mime_type","message":"File type unknown"}
<Response [400]>
Bad Request
So sending the request with the MultipartEncoder returns an error of an unsupported MIME type. So while it isn't a blank 200 response, it gives me reason to think that perhaps I am using the MultipartEncoder wrong, or I am not making the proper adjustments to the picture I am trying to upload.
Any insight into this would be greatly appreciated.

Related

How to send a 'multipart/form-data' with requests, without files in Python

I want to send a 'multipart/form-data' dictionary data without files
headers = {'key':'value', 'Content-Type':'multipart/form-data'}
data = {'api_key':'api_value', ... }
response = request.post('API_URL', headers=headers, data=data)
but API response is:
FileUploadException: the request was rejected because no multipart boundary was found
so I changed 'multipart/form-data' -> 'multipart/form-data; boundary=____sample'
This code also didn't work. It didn't recognize the parameters.
How can I send 'multipart/form-data' requests without files?
I'm not entirely sure how python works with requests. You can try this accepted answers or any which is suitable
python requests - POST Multipart/form-data without filename in HTTP request

Python Requests library post interpreted by Django as GET for some reason

I'm trying to make a post request to our app's api for some regression testing, but for some reason when I make the request like so through requests, it's logged and interpreted as a GET request.
CODE:
requests.post({HTTP_PROTOCOL}://{APP_URL}/api/route/',
headers={'Authorization': 'Bearer ' + access_token},
json=DATA)
LOG:
[Thu Jul 11 19:17:30 2019] GET /api/route/ => generated 2 bytes in 64 msecs (HTTP/1.1 200) 5 headers in 162 bytes (1 switches on core 1)
When I make the request through Postman, however, the request works totally fine and comes back with the created object in JSON, and in the logs it's recorded as a POST.
The backend is currently written in Django, using Django Rest Framework for the REST API. Here is the Route in our urls.py file:
url(r"^api/route/$", DataListView.as_view())
And I know that the DataListView works, because Postman works totally fine with it.
I've had similar problems where it wouldn't work because I was posting to a route without the slash, and that's not the case here. I know I'm posting to the route with the trailing slash, as you can see for yourselves.
QUESTION: How do I get this to work? And why would it be working in Postman, but not using the requests library?
EDIT 1:
Here's the headers in the request if that gives any clues:
{
'User-Agent': 'python-requests/2.22.0',
'Accept-Encoding': 'gzip, deflate',
'Accept': '*/*',
'Connection': 'keep-alive',
'Authorization': 'Bearer <access-token>'
}
EDIT 2:
I just did this:
print(resp.request.method)
and it printed out GET... No idea why. When I did it do my local server though, it printed out POST. Could it have something to do with the fact that I'm posting to a https:// URL? This is bizzare.
From this, it looks like maybe it's getting a 301 or 302 redirect that's causing this.
I ended up doing what #IanStapletonCordasco suggests for debugging in the question that I linked to and found out my HTTP_PROTOCOL was being set to "http" instead of "https", which would result in a proxy redirect from nginx that was changing the POST to a GET. I changed that in my config file where that variable is set, and it worked.
Something else to note, for people that are having a similar problem, look at the URL you're posting to. If you have strict trailing slash checking turned on in Django (which I'm pretty sure is on by default), and you're posting to a URL without the trailing slash, it'll come back with a 30x redirect response that will cause a similar thing to happen. Try adding the slash and it should work.

Post a string as multipart/form-data using requests

I am posting to an API that seems to insist on receiving XML data as multipart/form-data with the name (file name?) xml. It works in postman but I can't get it to work using Python's requests. This is my Python code (based on https://stackoverflow.com/a/24443309/1011724):
requests.post(callpro_url,
files={'xml':('data.xml',result)},
verify=False).text
where result is a string containing XML. If I try this code I get the response:
xml post field is empty
which is the response this API give if you don't use the multipart/form-data header.
If I generate code from the working postman post I get something like this (slightly redacted):
import requests
url = "https://blablabla.blablab.com/blabla/api.php"
querystring = {"mode":"import","hash":"redacted-hash","xml":"\"xml\""}
payload = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"xml\"\r\n\r\n<?xml version=\"1.0\" ?>\n<importdata>\n --redacted-XML-- \n</importdata>\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
headers = {
'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
'cache-control': "no-cache",
'postman-token': "8d3ec8ee-784e-3a65-5240-cf1a9534d1c4"
}
response = requests.request("POST", url, data=payload, headers=headers, params=querystring)
print(response.text)
Executing this code in Python, it gives the correct response.
Note that the params=querystring part in the postman code is taken care of in the URL in my code.
I'm confused by the payload in the postman code. It adds things like Content-Disposition and name in the string. I assume that I can put this stuff in the tuple in the files parameter but I'm not sure how to to do it. I've tried files={'xml':('data.xml',result,'form-data')} for example and also files={'xml':('data.csv',result)} and {'xml':('xml',result)}.
Also, the postman code explicitly defines the header as
'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
whereas the requests documentation say that I should never explicitly define the headers in that way. However the postman code works and my code does not.
Any suggestions?
I don't know if this is worth answering or if I should delete the question but what was needed was to name the file "xml" not xml. so this works:
requests.post(callpro_url,
files={'xml':('"xml"',result)},
verify=False).text

oAuth verification for using Twitter API with Python

I'm trying to get the oauth request_token for Twitter as described here, making a call to "oauth/request_token": https://dev.twitter.com/docs/auth/implementing-sign-twitter
I'm generating the params using the encode_params function in here: https://github.com/sixohsix/twitter/blob/master/twitter/oauth.py
I then wrap the returned string in a dict with they key "Authorization" and dump it into the Headers of the Post request I'm making using the python request library. Here's the two lines I use to create the request.
ep = "OAuth " + auth.encode_params(baseUrl, method, params)
response = requests.post(baseUrl+method, headers={ "Authorization" : ep})
The eventual header looks like this (consumer_key modified):
{'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, compress',
'Authorization': 'OAuth oauth_callback=http%253A%252F%252Fec2-54-244-189-248.us-west-2.compute.amazonaws.com%252Fct%252Ftwitter_login_handler%252F&oauth_consumer_key=xxx&oauth_nonce=14937468581358710045&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1366568033&oauth_version=1.0&oauth_signature=kiYucZzPY%2FXy2WyJliJ6YcggVzQ%3D',
'Content-Length': '0',
'User-Agent': 'python-requests/1.2.0 CPython/2.7.3 Linux/3.5.0-21-generic'}
However, I'm still getting a 401 response that says: 'Failed to validate oauth signature and token'
Any idea what I'm doing wrong? Any help would really be appreciated.
I can't speak to the script you reference, but if you're willing to try another library as the author of rauth I can recommend it. Here's a working Twitter example. Hope that helps.
I ended up using python-oauth2. Their instructions were a little out of date, so I updated them and submitted a pull request. As of right now, it's not been accepted, but here's a link to the forked repo with the updated instructions.
Hopefully this helps someone else...
From the code referenced by maxcountryman - it has a comment that I had not found elsewhere till then :
# Get a real consumer key & secret from https://dev.twitter.com/apps/new
That helped me progress a lot...
Cheers, Ian
.

Google Data API authentication

I am trying to get my Django app (NOT using Google app engine) retrieve data from Google Contacts using Google Contacts Data API. Going through authentication documentation as well as Data API Python client docs
First step (AuthSubRequest) which is getting the single-use token works fine. The next step(AuthSubSessionToken), which is upgrade single-use token to a session token. The python API call UpgradeToSessionToken() simply didn't work for me it gave me NonAuthSubToken exception:
gd_client = gdata.contacts.service.ContactsService()
gd_client.auth_token = authsub_token
gd_client.UpgradeToSessionToken()
As an alternative I want to get it working by "manually" constructing the HTTP request:
url = 'https://www.google.com/accounts/AuthSubSessionToken'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'AuthSub token=' + authsub_token,
'User-Agent': 'Python/2.6.1',
'Host': 'https://www.google.com',
'Accept': 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2',
'Connection': 'keep-alive',
}
req = urllib2.Request(url, None, headers)
response = urllib2.urlopen(req)
this gives me a different error:
HTTP Error 302: The HTTP server returned a redirect error that would lead to an infinite loop. The last 30x error message was: Moved Temporarily
What am I doing wrong here? I'd appreciate help/advice/suggestions with either of the methods I am trying to use: Python API call (UpgradeToSessionToken) or manually constructing HTTP request with urllib2.
According to the 2.0 documentation here there is a python example set...
Running the sample code
A full working sample client, containing all the sample code shown in this document, is available in the Python client library distribution, under the directory samples/contacts/contacts_example.py.
The sample client performs several operations on contacts to demonstrate the use of the Contacts Data API.
Hopefully it will point you in the right direction.
I had a similar issue recently. Mine got fixed by setting "secure" to "true".
next = 'http://www.coolcalendarsite.com/welcome.pyc'
scope = 'http://www.google.com/calendar/feeds/'
secure = True
session = True
calendar_service = gdata.calendar.service.CalendarService()
There are four different ways to authenticate. Is it really that important for you to use AuthSub? If you can't get AuthSub to work, then consider the ClientLogin approach. I had no trouble getting that to work.

Categories

Resources