如何使用请求 POST 方法上传 PDF 文件,并且请求必须使用 python for xeroAPI 格式化为多部分 MIME?

问题描述

我正在尝试使用 python 请求库(POST 方法)在 Xero 帐户中上传 PDF 文件,Xeros FilesAPI 说“请求必须格式化为多部分 MIME”并且有一些必填字段(link)但我不知道如何做到这一点...如果我执行 GET 请求,我将获取 Xero 帐户中的文件列表,但在发布文件(POST 请求)时遇到问题...

我的代码

post_url = 'https://api.xero.com/files.xro/1.0/Files/'
files = {'file': open('/home/mobin/PycharmProjects/s3toxero/data/in_test_upload.pdf','rb')}

response = requests.post(
    post_url,headers={
        'Authorization': 'Bearer ' + new_tokens[0],'Xero-tenant-id': xero_tenant_id,'Accept': 'application/json','Content-type': 'multipart/form-data; boundary=JLQPFBPUP0','Content-Length': '1068',},files=files,)

json_response = response.json()

print(f'Uploading Responsoe ==> {json_response}')
print(f'Uploading Responsoe ==> {response}')

错误消息/响应:

Uploading Responsoe ==> [{'type': 'Validation','title': 'Validation failure','detail': 'No file is was attached'}]
Uploading Responsoe ==> <Response [400]>

解决方法

正如我所见,您设置的边界不正确。您在标题中设置它,但不告诉 requests 库使用自定义边界。让我给你举个例子:

>>> import requests
>>> post_url = 'https://api.xero.com/files.xro/1.0/Files/'
>>> files = {'file': open('/tmp/test.txt','rb')}
>>> headers = {
...    'Authorization': 'Bearer secret',...    'Xero-tenant-id': '42',...    'Accept': 'application/json',...    'Content-type': 'multipart/form-data; boundary=JLQPFBPUP0',...    'Content-Length': '1068',... }
>>> print(requests.Request('POST',post_url,files=files,headers=headers).prepare().body.decode('utf8'))
--f3e21ca5e554dd96430f07bb7a0d0e77
Content-Disposition: form-data; name="file"; filename="test.txt"


--f3e21ca5e554dd96430f07bb7a0d0e77--

如您所见,实际边界 (f3e21ca5e554dd96430f07bb7a0d0e77) 与标头 (JLQPFBPUP0) 中传递的不同。

您实际上可以直接使用请求模块来控制边界,如下所示:

让我们准备一个测试文件:

$ touch /tmp/test.txt
$ echo 'Hello,World!' > /tmp/test.txt 

测试一下:

>>> import requests
>>> post_url = 'https://api.xero.com/files.xro/1.0/Files/'
>>> files = {'file': open('/tmp/test.txt','rb')}
>>> headers = {
...     'Authorization': 'Bearer secret',...     'Xero-tenant-id': '42',...     'Accept': 'application/json',...     'Content-Length': '1068',... }
>>> body,content_type = requests.models.RequestEncodingMixin._encode_files(files,{})
>>> headers['Content-type'] = content_type
>>> print(requests.Request('POST',data=body,headers=headers).prepare().body.decode('utf8'))
--db57d23ff5dee7dc8dbab418e4bcb6dc
Content-Disposition: form-data; name="file"; filename="test.txt"

Hello,World!

--db57d23ff5dee7dc8dbab418e4bcb6dc--

>>> headers['Content-type']
'multipart/form-data; boundary=db57d23ff5dee7dc8dbab418e4bcb6dc'

此处的边界与标题中的边界相同。

另一种选择是使用 requests-toolbelt;以下示例取自 this GitHub issue 线程:

from requests_toolbelt import MultipartEncoder

fields = {
    # your multipart form fields
}

m = MultipartEncoder(fields,boundary='my_super_custom_header')
r = requests.post(url,headers={'Content-Type': m.content_type},data=m.to_string())

但最好完全不要手工传递bundary,将这项工作委托给requests库。


更新:

使用 Xero Files API 和 Python 请求的最小工作示例:

from os.path import abspath
import requests

access_token = 'secret'
tenant_id = 'secret'

filename = abspath('./example.png')

post_url = 'https://api.xero.com/files.xro/1.0/Files'
files = {'filename': open(filename,'rb')}
values = {'name': 'Xero'}

headers = {
    'Authorization': f'Bearer {access_token}','Xero-tenant-id': f'{tenant_id}','Accept': 'application/json',}

response = requests.post(
    post_url,headers=headers,data=values
)

assert response.status_code == 201
,

我已经使用 Xero 的 Files API 对此进行了测试,将一个名为“helloworld.rtf”的文件上传到与我的主 app.py 文件相同的目录中。

var1 = "Bearer "
var2 = YOUR_ACCESS_TOKEN
access_token_header = var1 + var2
body = open('helloworld.rtf','rb')

mp_encoder = MultipartEncoder(
fields={
     'helloworld.rtf': ('helloworld.rtf',body),}
 )

r = requests.post(
    'https://api.xero.com/files.xro/1.0/Files',data=mp_encoder,# The MultipartEncoder is posted as data
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={
        'Content-Type': mp_encoder.content_type,'xero-tenant-id': YOUR_XERO_TENANT_ID,'Authorization': access_token_header
    }
)
,

看起来你已经解决了。供参考和任何未来使用 Xero 支持包 (https://github.com/XeroAPI/xero-python) 的开发人员

我们刚刚在示例应用中添加了 files_api 示例代码,因此如果您使用的是 Python SDK,以下内容将上传文件

https://github.com/XeroAPI/xero-python-oauth2-app/pull/29/files

name = "my-image"
filename= "my-image.jpg"
mime_type = "image/jpg"
with open('my-image.jpg','rb') as f:
    body = f.read()

try:
    file_object = files_api.upload_file(
        xero_tenant_id,name = name,filename= filename,mime_type = mime_type,body=body
    )
except AccountingBadRequestException as exception:
    json = jsonify(exception.error_data)
else:
    json = serialize_model(file_object)