testrail_conn.py (3564B)
1 # flake8: noqa 2 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 """TestRail API binding for Python 3.x. 8 9 (API v2, available since TestRail 3.0) 10 11 Compatible with TestRail 3.0 and later. 12 13 Learn more: 14 15 http://docs.gurock.com/testrail-api2/start 16 http://docs.gurock.com/testrail-api2/accessing 17 18 Copyright Gurock Software GmbH. See license.md for details. 19 """ 20 21 import base64 22 import json 23 24 import requests 25 26 27 class APIClient: 28 def __init__(self, base_url): 29 self.user = "" 30 self.password = "" 31 if not base_url.endswith("/"): 32 base_url += "/" 33 self.__url = base_url + "index.php?/api/v2/" 34 35 def send_get(self, uri, filepath=None): 36 """Issue a GET request (read) against the API. 37 38 Args: 39 uri: The API method to call including parameters, e.g. get_case/1. 40 filepath: The path and file name for attachment download; used only 41 for 'get_attachment/:attachment_id'. 42 43 Returns: 44 A dict containing the result of the request. 45 """ 46 return self.__send_request("GET", uri, filepath) 47 48 def send_post(self, uri, data): 49 """Issue a POST request (write) against the API. 50 51 Args: 52 uri: The API method to call, including parameters, e.g. add_case/1. 53 data: The data to submit as part of the request as a dict; strings 54 must be UTF-8 encoded. If adding an attachment, must be the 55 path to the file. 56 57 Returns: 58 A dict containing the result of the request. 59 """ 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(bytes("%s:%s" % (self.user, self.password), "utf-8")), 67 "ascii", 68 ).strip() 69 headers = {"Authorization": "Basic " + auth} 70 71 if method == "POST": 72 if uri[:14] == "add_attachment": # add_attachment API method 73 files = {"attachment": (open(data, "rb"))} 74 response = requests.post(url, headers=headers, files=files) 75 files["attachment"].close() 76 else: 77 headers["Content-Type"] = "application/json" 78 payload = bytes(json.dumps(data), "utf-8") 79 response = requests.post(url, headers=headers, data=payload) 80 else: 81 headers["Content-Type"] = "application/json" 82 response = requests.get(url, headers=headers) 83 84 if response.status_code > 201: 85 try: 86 error = response.json() 87 except ( 88 requests.exceptions.HTTPError 89 ): # response.content not formatted as JSON 90 error = str(response.content) 91 raise APIError( 92 "TestRail API returned HTTP %s (%s)" % (response.status_code, error) 93 ) 94 else: 95 if uri[:15] == "get_attachment/": # Expecting file, not JSON 96 try: 97 open(data, "wb").write(response.content) 98 return data 99 except FileNotFoundError: 100 return "Error saving attachment." 101 else: 102 try: 103 return response.json() 104 except requests.exceptions.HTTPError: 105 return {} 106 107 108 class APIError(Exception): 109 pass