I'm trying to update an existing image from a product in prestashop. I'm using Python and Requests and the following code:
import requests
import io
import mimetypes
from PIL import Image
from StringIO import StringIO
api_key = 'test'
url = "https://.../api/images/products/249/445"
file_name = 't3_6kxvzv.jpg'
fd = io.open(file_name, "rb")
content = fd.read()
fd.close()
def encode_multipart_formdata():
"""Encode files to an http multipart/form-data.
:param files: a sequence of (type, filename, value)
elements for data to be uploaded as files.
:return: headers and body.
"""
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = '\r\n'
L = []
L.append('--' + BOUNDARY)
L.append(
'Content-Disposition: form-data; \
name="%s"; filename="%s"' % ("image", file_name))
L.append('Content-Type: %s' % get_content_type(file_name))
L.append('')
L.append(content)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
headers = {
'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY
}
return headers, body
def get_content_type(file_name):
"""Retrieve filename mimetype.
:param filename: file name.
:return: mimetype.
"""
return mimetypes.guess_type(file_name)[0] or 'application/octet- stream'
header, body = encode_multipart_formdata()
r = requests.put(url, data=body, auth=(api_key,""), headers= header)
# also tried data = content
r = requests.get(url, auth=(api_key,""))
i = Image.open(StringIO(r.content))
i.show()
I tried various PUT and POST requests with
data = content
but getting only a 400 status code.
I then tried to GET the existing image, which works fine.
The api_key has all the necessary setting to allow PUT and POST.
I then tried to read into how prestapyt is solving this problem, however after importing prestapyt I couldn't follow their documentation to add an image to a product using:
prestashop.add("https://...api/images/products/249/445", files[('image',file_name,content)])
produces:
KeyError: ('image', 't3_6kxvzv.jpg', '\xff\xd8\xff\xe0\x00\x10JFI...
I tried then to modify the encode_multipart_formdata and get_content_type functions to produce a similar solution, but cannot get past the 400 status code.
I would very much prefer to use Requests and try to understand how to update a picture to using prestapyt and a turn-key solution.
Thank you for your time!
Documentation I used:
Prestashop http://doc.prestashop.com/display/PS16/Chapter+9+-+Image+management
prestapyt https://github.com/prestapyt/prestapyt
UPDATE:
I was able to use Requests and POST to add an image to a product via:
url_2 = "https:/.../api/images/products/249"
r = requests.post(url_2, data=body, auth=(api_key,""), headers=header)
Still not able to use PUT to change or update an image.
This answer comes a little bit late, but here it is. You're reinventing the wheel, altough it's being interesting seeing how: now I understand how to build a multipart form data from scratch. I tried your code and it fails since youre joining str and bytes in your encode_multipart_formdata function:
L.append(content)
That line will raise a TypeError exception.
Requests can post multipart form data in a very simple way:
files = {'image': ('../imagepath/imagename.jpg', open('../imagepath/imagename.jpg', 'rb'), 'image/jpg')}
body, content_type = requests.models.RequestEncodingMixin._encode_files(files, {})
headers = {
"Content-Type": content_type
}
r=requests.post( url + "images/products/" + str(product_id),data=body, headers=headers)
print(r.text)
This has been tested with Python 3.7 and PrestaShop 1.7.8.2.
Couldn't get PUT to work, so instead used DELETE and POST
import requests
import io
import mimetypes
import xml.etree.ElementTree as ET
import sys
api = ''
urls =["https://.../api/images/products/22",
"https://.../api/images/products/31",
"https://.../api/images/products/37",
"https://.../api/images/products/46",
"https://.../api/images/products/212"]
def encode_multipart_formdata(file_name,content):
"""Encode files to an http multipart/form-data.
:param files: a sequence of (type, filename, value)
elements for data to be uploaded as files.
:return: headers and body.
"""
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = '\r\n'
L = []
L.append('--' + BOUNDARY)
L.append(
'Content-Disposition: form-data; \
name="%s"; filename="%s"' % ("image", file_name))
L.append('Content-Type: %s' % get_content_type(file_name))
L.append('')
L.append(content)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
headers = {
'Content-Type': 'multipart/form-data; boundary=%s' % BOUNDARY
}
return headers, body
def get_content_type(file_name):
"""Retrieve filename mimetype.
:param filename: file name.
:return: mimetype.
"""
return mimetypes.guess_type(file_name)[0] or 'application/octet-stream'
def get_image_url(url):
"""get from a given url the image url"""
r = requests.get(url, auth=(api,""))
tree = ET.fromstring(r.content)
return tree.find("image").find("declination").get("{http://www.w3.org/1999/xlink}href")
def delete_image(url):
"""deletes the image on prestashop given by url"""
url2 = get_image_url(url)
requests.delete(url2, auth=(api,""))
def load_image(file_name):
"""loads image to upload"""
fd = io.open(file_name, "rb")
content = fd.read()
fd.close()
return content, file_name
def upload_image(url, file_name):
"""uploads new image to a given url"""
content, file_name = load_image(file_name)
header, body = encode_multipart_formdata(file_name, content)
requests.post(url, data=body, auth=(api,""), headers=header)
if __name__ == "__main__":
file_name = sys.argv[1]
content, file_name = load_image(file_name)
for i in urls:
delete_image(i)
upload_image(i,file_name)
Workaround works fine, still don't understand why there has to be such a complicated way and why PUT doesn't work.
Related
I have a connection to an organization's mailbox. Using the MS Graph API, the response from the API returns the given message's attachment as content bytes. The issue I'm having is that the attachment is within a .zip. There is only 1 file in the .zip which is of type .txt
So with just the .txt file as the attachment not in the .zip the content-bytes returned is
dGhpcyBpcyBzb21lIHRlc3QgdGV4dA==
Using something like:
def getAttachmentFromMailMessage(mailId, headers):
url = inboxFolderPricingFiles + '/' + mailId + '/' +'attachments'
response = requests.request('GET', url, headers = headers)
content = json.loads(response.text)['value'][0]['contentBytes']
txtString = base64.b64decode(content).decode('utf-8')
return txtString
Returns :
this is some test text
But when I get the content-bytes of the .zip file the API returns
UEsDBAoAAAAAAHZXRlMb8/ygFgAAABYAAAAIAAAAdGVzdC50eHR0aGlzIGlzIHNvbWUgdGVzdCB0ZXh0UEsBAj8ACgAAAAAAdldGUxvz/KAWAAAAFgAAAAgAJAAAAAAAAAAgAAAAAAAAAHRlc3QudHh0CgAgAAAAAAABABgAv57iEEW61wG/nuIQRbrXAcZ34hBFutcBUEsFBgAAAAABAAEAWgAAADwAAAAAAA==
Using the same code as above:
def getAttachmentFromMailMessage(mailId, headers):
url = inboxFolderPricingFiles + '/' + mailId + '/' +'attachments'
response = requests.request('GET', url, headers = headers)
content = json.loads(response.text)['value'][0]['contentBytes']
txtString = base64.b64decode(content).decode('utf-8')
return txtString
I get an error :
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf3 in position 15: invalid continuation byte
Given I don't have a "file-like" object to deal with and instead need to use the returned content-bytes from the API I'm unsure how to make this work...
When the attachment is a zip file, then the bytes you get will be the contents of the zip file. You can wrap those bytes with io.BytesIO to create a file-like object and then pass that file-like object to zipfile.ZipFile to properly read the contents of the zip file. Here is a modify version of your function that should work in this scenario.
import zipfile
def getAttachmentFromMailMessage(mailId, headers):
url = inboxFolderPricingFiles + '/' + mailId + '/' +'attachments'
response = requests.request('GET', url, headers = headers)
content = json.loads(response.text)['value'][0]['contentBytes']
content_bytes = base64.b64decode(content)
fd = io.BytesIO(content_bytes)
with zipfile.ZipFile(fd) as myzip:
first_file = myzip.infolist()[0]
with myzip.open(first_file) as myfile:
txtString = myfile.read().decode("utf-8")
return txtString
I wanted to scrape a few pdfs from a great history crash course I used to read a long time ago. Sadly, the old website is down and I only managed to get the old html code from archive.org
(the links I got work fine, ex: https://drive.google.com/file/d/0BzRJiIvdbSoKcHpGUWJBUDZ2WDA/edit?usp=sharing).
This script is resulting in html files being downloaded, saying
,,We're sorry but your computer or network may be sending automated queries. To protect our users, we can't process your request right now.”
Is there a way to bypass this? I tried putting a few random delays into the code so this might be insufficient or i might be on google's blacklist for now.
(the text.txt file can be found here https://filebin.net/k2qw09embamx05ey )
import requests
import time
import random
def download_file_from_google_drive(id, destination):
URL = "https://docs.google.com/uc?export=download"
session = requests.Session()
response = session.get(URL, params = { 'id' : id }, stream = True)
token = get_confirm_token(response)
time.sleep(random.randrange(1,2))
if token:
params = { 'id' : id, 'confirm' : token }
response = session.get(URL, params = params, stream = True)
save_response_content(response, destination)
def get_confirm_token(response):
for key, value in response.cookies.items():
if key.startswith('download_warning'):
return value
return None
def save_response_content(response, destination):
CHUNK_SIZE = 32768
with open(destination, "wb") as f:
for chunk in response.iter_content(CHUNK_SIZE):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
f = open('text.txt')
long_string = f.readlines()
interesting_strings = []
for item in long_string:
if 'drive.google' in item:
interesting_strings.append(item)
print(interesting_strings)
interesting_strings = interesting_strings[0]
interesting_strings = interesting_strings.split('https://web.archive.org/web/20161219093036/')
links = []
for item in interesting_strings:
if 'drive.google' in item:
idx = item.find('"')
links.append(item[:idx])
cntr = 1
for link in links:
print(link)
fname = './data/History_' + str(cntr)
file_id = link.split('/')[-2]
print('id:', file_id)
destination = fname
download_file_from_google_drive(file_id, destination)
print('Getting file #', str(cntr))
cntr += 1
time.sleep(random.randrange(3,15) + random.random())
Use gdown:
import gdown
file_id = '0BzRJiIvdbSoKcHpGUWJBUDZ2WDA'
filename = 'file.pdf'
url = 'https://drive.google.com/uc?id=' + file_id
gdown.download(url, filename, quiet=False)
This is what i am suppose to do:
List all files in data/feedback folder
Scan all the files, and make a nested dictionary with Title, Name, Date & Feedback (All the files are in Title,Name, Date & Feedback format with each in a different line of file, that’s why using rstrip function)
Post the dictionary in The given url
Following is my code:
#!/usr/bin/env python3
import os
import os.path
import requests
import json
src = '/data/feedback/'
entries = os.listdir(src)
Title, Name, Date, Feedback = 'Title', 'Name', 'Date', 'Feedback'
inputDict = {}
for i in range(len(entries)):
fileName = entries[i]
completeName = os.path.join(src, fileName)
with open(completeName, 'r') as f:
line = f.readlines ()
line tuple = (line[0],line[1],line[2],line[3])
inputDict[fileName] = {}
inputDict[fileName][Title] = line_tuple[0].rstrip()
inputDict[fileName][Name] = line_tuple[1].rstrip()
inputDict[fileName][Date] = line_tuple[2].rstrip()
inputDict[fileName][Feedback] = line_tuple[3].rstrip()
x = requests.get ("http://website.com/feedback")
print (x.status_code)
r = requests.post ("http://Website.com/feedback” , data=inputDict)
print (r.status_code)
After i run it, get gives 200 code but post gives 500 code.
I just want to know if my script is causing the error or not ?
r = requests.post ("http://Website.com/feedback” , data=inputDict)
If your rest api endpoint is expecting json data then the line above is not doing that; it is sending the dictionary inputDict as form-encoded, as though you were submitting a form on an HTML page.
You can either use the json parameter in the post function, which sets the content-type in the headers to application/json:
r = requests.post ("http://Website.com/feedback", json=inputDict)
or set the header manually:
headers = {'Content-type': 'application/json'}
r = requests.post("http://Website.com/feedback", data=json.dumps(inputDict), headers=headers)
I want to send a file through requests library in Python, it accepts file type input. Here is a piece of working code I've tested myself.
FILES = []
f = open('/home/dummy.txt', 'rb')
# f : <type 'file'>
FILES.append(('file', f))
response = requests.post(url = 'https://dummy.com/dummy', headers = {'some_header':'content'}, data={'some_payload':'content'}, files=FILES)
This works just fine, now I have a base64 encoded string I just got from my database
base64_string = "Q3VyaW91cywgYXJlbid0IHlvdT8="
FILES = []
f = convert_base64_to_file(base64_string, "dummy.txt")
# f : <type 'file'>
FILES.append(('file', f))
response = requests.post(url = 'https://dummy.com/dummy', headers = {'some_header':'content'}, data={'some_payload':'content'}, files=FILES)
I need the convert_base64_to_file (which is an imaginary method). How do I achieve this without any disk I/O?
My base64_string doesn't have a filename. How do I simulate the filename so that it behaves just like I open a file from my disk?
I need the http request to post this:
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="dummy.txt"
Content-Type: application/x-object
... contents of file goes here ...
That's why I need to specify the filename.
You can use the io module:
import io
FILES = {}
f = io.BytesIO()
FILES{'file'] = ("dummy.txt", f)
response = requests.post(url = 'https://dummy.com/dummy', headers = {'some_header':'content'}, data={'some_payload':'content'}, files=FILES)
Here is the convert_base64_to_file function:
import base64
import io
def convert_base64_to_file(data):
return io.BytesIO(base64.b64decode(data))
To complete Megalng answer which solves half of my problem, we can add a name to the constructed file by doing this:
import base64
import io
def convert_base64_to_file(base64_string,filename):
f = io.BytesIO(base64.b64decode(base64_string))
f.name = filename
return f
I'm currently using the session.get() code found at this stackoverflow. When I save the drive files they don't have a filetype suffix, so I have to add one manually to based on file type to open it. Is there a way I can parse the chunk and get filetype variable or maybe even search by hexcode? Better method?
import requests
def download_file_from_google_drive(id, destination):
URL = "https://docs.google.com/uc?export=download"
session = requests.Session()
response = session.get(URL, params = { 'id' : id }, stream = True)
token = get_confirm_token(response)
if token:
params = { 'id' : id, 'confirm' : token }
response = session.get(URL, params = params, stream = True)
save_response_content(response, destination)
def get_confirm_token(response):
for key, value in response.cookies.items():
if key.startswith('download_warning'):
return value
def save_response_content(response, destination):
CHUNK_SIZE = 32768
with open(destination, "wb") as f:
for chunk in response.iter_content(CHUNK_SIZE):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
if __name__ == "__main__":
file_id = 'TAKE ID FROM SHAREABLE LINK'
destination = 'DESTINATION FILE ON YOUR DISK'
download_file_from_google_drive(file_id, destination)
Source: Python: download files from google drive using url
via #user6770522
I used beatifulSoup and findAll to grab the 'title' tag. Then applied it to destination by os path join.
for filename in soup.findAll('title'):
link = filename.string
filename = link.replace(' - Google Drive','')
destination = os.path.join(dir,filename)
file_path = os.path.join(year_name, destination)
drive_pull.download_file_from_google_drive(url_id, file_path)