Ilya Menkov | ad23403 | 2019-10-22 13:43:54 +0400 | [diff] [blame] | 1 | # |
| 2 | # TestRail API binding for Python 3.x (API v2, available since |
| 3 | # TestRail 3.0) |
| 4 | # Compatible with TestRail 3.0 and later. |
| 5 | # |
| 6 | # Learn more: |
| 7 | # |
| 8 | # http://docs.gurock.com/testrail-api2/start |
| 9 | # http://docs.gurock.com/testrail-api2/accessing |
| 10 | # |
| 11 | # Copyright Gurock Software GmbH. See license.md for details. |
| 12 | # |
| 13 | |
| 14 | import requests |
| 15 | import json |
| 16 | import base64 |
| 17 | |
| 18 | |
| 19 | class APIClient: |
| 20 | def __init__(self, base_url): |
| 21 | self.user = '' |
| 22 | self.password = '' |
| 23 | if not base_url.endswith('/'): |
| 24 | base_url += '/' |
| 25 | self.__url = base_url + 'index.php?/api/v2/' |
| 26 | |
| 27 | # |
| 28 | # Send Get |
| 29 | # |
| 30 | # Issues a GET request (read) against the API and returns the result |
| 31 | # (as Python dict) or filepath if successful file download |
| 32 | # |
| 33 | # Arguments: |
| 34 | # |
| 35 | # uri The API method to call including parameters |
| 36 | # (e.g. get_case/1) |
| 37 | # |
| 38 | # filepath The path and file name for attachment download |
| 39 | # Used only for 'get_attachment/:attachment_id' |
| 40 | # |
| 41 | def send_get(self, uri, filepath=None): |
| 42 | return self.__send_request('GET', uri, filepath) |
| 43 | |
| 44 | # |
| 45 | # Send POST |
| 46 | # |
| 47 | # Issues a POST request (write) against the API and returns the result |
| 48 | # (as Python dict). |
| 49 | # |
| 50 | # Arguments: |
| 51 | # |
| 52 | # uri The API method to call including parameters |
| 53 | # (e.g. add_case/1) |
| 54 | # data The data to submit as part of the request (as |
| 55 | # Python dict, strings must be UTF-8 encoded) |
| 56 | # If adding an attachment, must be the path |
| 57 | # to the file |
| 58 | # |
| 59 | def send_post(self, uri, data): |
| 60 | return self.__send_request('POST', uri, data) |
| 61 | |
| 62 | def __send_request(self, method, uri, data): |
| 63 | url = self.__url + uri |
| 64 | |
| 65 | auth = str( |
| 66 | base64.b64encode( |
| 67 | bytes('%s:%s' % (self.user, self.password), 'utf-8') |
| 68 | ), |
| 69 | 'ascii' |
| 70 | ).strip() |
| 71 | headers = {'Authorization': 'Basic ' + auth} |
| 72 | |
| 73 | if method == 'POST': |
| 74 | if uri[:14] == 'add_attachment': # add_attachment API method |
| 75 | files = {'attachment': (open(data, 'rb'))} |
| 76 | response = requests.post(url, headers=headers, files=files) |
| 77 | files['attachment'].close() |
| 78 | else: |
| 79 | headers['Content-Type'] = 'application/json' |
| 80 | payload = bytes(json.dumps(data), 'utf-8') |
| 81 | response = requests.post(url, headers=headers, data=payload) |
| 82 | else: |
| 83 | headers['Content-Type'] = 'application/json' |
| 84 | response = requests.get(url, headers=headers) |
| 85 | |
| 86 | if response.status_code > 201: |
| 87 | try: |
| 88 | error = response.json() |
| 89 | except: # response.content not formatted as JSON |
| 90 | error = str(response.content) |
| 91 | raise APIError('TestRail API returned HTTP %s (%s)' % (response.status_code, error)) |
| 92 | else: |
| 93 | if uri[:15] == 'get_attachment/': # Expecting file, not JSON |
| 94 | try: |
| 95 | open(data, 'wb').write(response.content) |
| 96 | return (data) |
| 97 | except: |
| 98 | return ("Error saving attachment.") |
| 99 | else: |
| 100 | return response.json() |
| 101 | |
| 102 | |
| 103 | class APIError(Exception): |
| 104 | pass |