Solved

Service Request attachments using Python requests module

  • 31 May 2023
  • 2 replies
  • 400 views

I can attach a file using Postman but cannot figure out how to do it in Python.  Below is a barebones script demonstrating a good response reading the record, followed by a bad response attempting to add an attachment.  I'm getting a 415 response (Unsupported Media Type).  Any assistance would be greatly appreciated!

 

$ python -V

Python 3.8.5

 

$ pip show requests |grep -e ^Name -e ^Version

Name: requests

Version: 2.26.0

 

$ python sysaid_test.py <my-account-name> 1168503

 

*** output ***

 

Get record type and status

--------------------------

url: https://*******.sysaidit.com/api/v1/sr?fields=sr_type,status&ids=1168503

session headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

prepared headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

status_code: 200

text...

('[{"id":"1168503","canUpdate":true,"canDelete":false,"canArchive":false,"hasChildren":false,"info":[{"key":"sr_type","value":10,"valueClass":"","valueCaption":"Request","keyCaption":"Service '

'Record Type"},{"key":"status","value":57,"valueClass":0,"valueCaption":"Work '

'in Progress","keyCaption":"Status"}]}]')

 

Attach file to request

----------------------

filepath: /tmp/sysaid_attachment.xlsx

filename: sysaid_attachment.xlsx

url: https://*******.sysaidit.com/api/v1/sr/1168503/attachment

session headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

prepped headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json', 'Content-Length': '8813'}

body (1st 200 characters): b'--13913b69799838b597498be9bcb74aba\r\nContent-Disposition: form-data; name="file"; filename="sysaid_attachment.xlsx"\r\nContent-Type: application/json\r\n\r\nPK\x03\x04\x14\x00\x00\x00\x08\x00\xecm\xb6V\xe4\xc5\x13\xbd\x82\x00\x00\x00\xb1\x00\x00\x00\x10\x00\x00\x00docProps/app.xmlM\x8eM\x0b'

body (last 200 characters): b'lPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\xedm\xb6V\xa1-6u\xaf\x00\x00\x00\xfb\x01\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01.\x1d\x00\x00xl/_rels/workbook.xml.relsPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\xedm\xb6V\xf6\x8b\xb9 \x15\x01\x00\x00\xd7\x03\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x15\x1e\x00\x00[Content_Types].xmlPK\x05\x06\x00\x00\x00\x00\t\x00\t\x00>\x02\x00\x00[\x1f\x00\x00\x00\x00\r\n--13913b69799838b597498be9bcb74aba--\r\n'

status_code: 415

response headers: {'x-frame-options': 'SAMEORIGIN', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'cache-control': 'no-cache', 'last-modified': 'Wed, 31 May 2023 19:07:10 GMT', 'content-type': 'text/html;charset=utf-8', 'content-language': 'en', 'content-length': '457', 'date': 'Wed, 31 May 2023 19:07:11 GMT', 'strict-transport-security': 'max-age=63072000; includeSubDomains;', 'set-cookie': 'SERVERID=inst05us-autoscale-app13|ZHea4|ZHea4; path=/'}

history: <!doctype html><html lang="en"><head><title>HTTP Status 415 – Unsupported Media Type</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 415 – Unsupported Media Type</h1></body></html>

 

*** code ***

 

#!/usr/bin/python

 

# tested in python3.8

# requires requests package: https://pypi.org/project/requests/

# assumes a valid JSESSIONID is stored in cookiepath - no login routine included here

# set filepath to your attachment

# invoke with account name and record id

# Requests @ PyPI: https://pypi.org/project/requests/

# Documentation: https://requests.readthedocs.io/en/latest/

 

 

import sys

import requests

from pprint import pprint, pformat

 

cookiepath='/tmp/jsessionid'

filepath = "/tmp/sysaid_attachment.xlsx"

 

def get_headers():

    try:

        #with open('/tmp/sysaid/%s/state/jsessionid' %account, 'r') as fd:

        with open(cookiepath, 'r') as fd:

            lines = fd.readlines()

            fd.close()

    except FileNotFoundError:

        print("File not found")

        return {}

    else:

        h = { 'Cookie': lines[0].strip(), 'Content-Type': 'application/json' }

 

    return h

 

### CHECK ARGS ###

 

if len(sys.argv) != 3:

    sys.exit('Usage: sysaid_test.py ACCOUNT RECORD-ID')

if not sys.argv[2].isdigit():

    sys.exit('Usage: sysaid_test.py ACCOUNT RECORD-ID')

 

 

### INITIALIZE vars ###

 

account = sys.argv[1].strip()

reqid = sys.argv[2].strip()

url = "https://%s.sysaidit.com/api/v1/sr?fields=sr_type,status&ids=%s" %(account, reqid)

session = requests.Session()

headers = get_headers()

# should a HEAD request be sent here to make JSESSION persistent in session data?

 

### READ the record for type and status ###

 

req = requests.Request('GET', url=url, headers=headers)

prepped = req.prepare()

print("Get record type and status")

print("--------------------------")

print("url: %s" %url.replace(account, "*******"))

print("session headers: %s" %str(headers))

print("prepared headers: %s" %str(prepped.headers))

 

# send prepared request

response = session.send(prepped)

 

# check status returned

sc = response.status_code

print("status_code: %s" %sc)

if sc == 200:

    print("text...")

    pprint(response.text)

else:

    sys.exit('Read failed. Aborting')

 

### ATTACH a file to the record ###

 

#if False:

if True:

    # create the request per the Requests doc:

    filename = filepath.split('/')[-1]

    url = "https://%s.sysaidit.com/api/v1/sr/%s/attachment" %(account, reqid)

 

    # create attachment

    files = {'file': (filename, open(filepath, 'rb'), 'application/json') }

    #files = {'file': (filename, open(filepath, 'rb'), 'application/octet-stream', {'Expires': '0'})}

    #files = {'file': open(filepath, 'rb')}

    #files = {'file': (filename, open(filepath, 'rb'), 'application/octet-stream') }

    #files = {'file': (filename, open(filepath, 'rb'), 'application/vnd.ms-excel') }

 

    req = requests.Request('POST', url, files=files, headers=headers)

    #req = requests.Request('POST', url=url, data=files, headers=headers)

    prepped = req.prepare()

 

    # modify headers for this invocation (not persisted to session data)

    #prepped.headers['Content-Type'] = "application/json"

    #prepped.headers['Content-Type'] = "multipart/form-data"

    #prepped.headers['Content-Disposition'] = "multipart/form-data"

    #del prepped.headers['Content-Type']

    #del prepped.headers['Content-Disposition']

 

    print()

    print("Attach file to request")

    print("----------------------")

    print("filepath: %s" %filepath)

    print("filename: %s" %filename)

    print("url: %s" %url.replace(account, "*******"))

    print("session headers: %s" %str(headers))

    print("prepped headers: %s" %str(prepped.headers))

    if len(prepped.body) < 200:

        print("body: %s" %prepped.body)

    else:

        print("body (1st 200 characters): %s" %prepped.body[:200])

        print("body (last 200 characters): %s" %prepped.body[-200:])

 

    # send prepared request

    response = session.send(prepped)

 

    # check status returned

    sc = response.status_code

    print("status_code: %s" %sc)

    if sc == 200:

        print("text...")

        pprint(response.text)

    print("response headers: %s" %str(response.headers))

    print("history: %s" %str(response.text))

icon

Best answer by cary512 1 June 2023, 17:24

View original

2 replies

Solved, thanks to this blog post: https://blog.jetbridge.com/multipart-encoded-python-requests/

Only the cookie header should be provided to requests.post.  Removing the Content-Type header from the prepared request before sending didn’t work.  

    # create attachment

    files = {'file': open(filepath, 'rb')}

    cookieHeaders = { "Cookie": headers['Cookie'] }

    req = requests.Request('POST', url, files=files, headers=cookieHeaders)

    prepped = req.prepare()

    response = session.send(prepped)

 

After this I’m getting a 200 response.

Thank you, @cary512 for coming back and sharing your findings! 🙏
I’ll mark your question as solved.

Reply