diff --git a/setup.py b/setup.py index 749ef84..5d1145d 100644 --- a/setup.py +++ b/setup.py @@ -7,11 +7,11 @@ name='testrail_api', version=testrail_api.__version__, packages=['testrail_api'], - url='https://github.com/tolstislon/testrail-api', - license='MIT License', - author='tolstislon', - author_email='tolstislon@gmail.com', - description='Python wrapper of the TestRail API', + url=testrail_api.__url__, + license=testrail_api.__license__, + author=testrail_api.__author__, + author_email=testrail_api.__author_email__, + description=testrail_api.__description__, long_description=parse_from_file('README.md'), install_requires=[ 'requests>=2.20.1' diff --git a/testrail_api/__init__.py b/testrail_api/__init__.py index c0dccfd..4373479 100644 --- a/testrail_api/__init__.py +++ b/testrail_api/__init__.py @@ -1,5 +1,35 @@ -from ._testrail_api import TestRailAPI +# -*- coding: utf-8 -*- + +""" +Python wrapper of the TestRail API + +------------------ +from testrail_api import TestRailAPI + +api = TestRailAPI('https://example.testrail.com/', 'example@mail.com', 'password') +my_case = api.cases.get_case(22) +api.cases.add_case(1, 'New Case', milestone_id=1) +------------------ -__version__ = '1.2.0' +""" + +from .__version__ import ( + __version__, + __author__, + __author_email__, + __description__, + __license__, + __url__ +) + +from ._testrail_api import TestRailAPI -__all__ = ['TestRailAPI'] +__all__ = [ + 'TestRailAPI', + '__version__', + '__author__', + '__author_email__', + '__description__', + '__license__', + '__url__' +] diff --git a/testrail_api/__version__.py b/testrail_api/__version__.py new file mode 100644 index 0000000..44849ee --- /dev/null +++ b/testrail_api/__version__.py @@ -0,0 +1,6 @@ +__version__ = '1.3.0' +__url__ = 'https://github.com/tolstislon/testrail-api' +__description__ = 'Python wrapper of the TestRail API' +__author__ = 'tolstislon' +__author_email__ = 'tolstislon@gmail.com' +__license__ = 'MIT License' diff --git a/testrail_api/_category.py b/testrail_api/_category.py index 30c387e..5057c59 100644 --- a/testrail_api/_category.py +++ b/testrail_api/_category.py @@ -973,8 +973,7 @@ def add_attachment_to_result(self, result_id: int, path: Union[str, Path]) -> di :param path: The path to the file :return: response """ - file = path if isinstance(path, Path) else Path(path) - return self._session.attachment_request(METHODS.POST, f'add_attachment_to_result/{result_id}', file) + return self._session.attachment_request(METHODS.POST, f'add_attachment_to_result/{result_id}', path) def add_attachment_to_result_for_case(self, result_id: int, case_id: int, path: Union[str, Path]) -> dict: """ @@ -987,10 +986,10 @@ def add_attachment_to_result_for_case(self, result_id: int, case_id: int, path: :param path: The path to the file :return: response """ - file = path if isinstance(path, Path) else Path(path) return self._session.attachment_request( METHODS.POST, - f'add_attachment_to_result_for_case/{result_id}/{case_id}', file + f'add_attachment_to_result_for_case/{result_id}/{case_id}', + path ) def get_attachments_for_case(self, case_id: int) -> List[dict]: @@ -1015,16 +1014,17 @@ def get_attachments_for_test(self, test_id: int) -> List[dict]: """ return self._session.request(METHODS.GET, f'get_attachments_for_test/{test_id}') - def get_attachment(self, attachment_id: int): + def get_attachment(self, attachment_id: int, path: Union[str, Path]): """ http://docs.gurock.com/testrail-api2/reference-attachments#get_attachment Returns the requested attachment identified by attachment_id. :param attachment_id: + :param path: :return: response """ - return self._session.request(METHODS.GET, f'get_attachment/{attachment_id}') + return self._session.get_attachment(METHODS.GET, f'get_attachment/{attachment_id}', path) def delete_attachment(self, attachment_id: int) -> None: """ diff --git a/testrail_api/_session.py b/testrail_api/_session.py index 7a2a12a..c63f3b4 100644 --- a/testrail_api/_session.py +++ b/testrail_api/_session.py @@ -1,15 +1,33 @@ +import logging +from json.decoder import JSONDecodeError from pathlib import Path +from typing import Union +from .__version__ import __version__ import requests from ._enums import METHODS +log = logging.getLogger(__name__) + + +class TestRailAPIError(Exception): + pass + + +class StatusCodeError(TestRailAPIError): + pass + class Session: - _user_agent = 'Python TestRail API v: 1.2' + _user_agent = f'Python TestRail API v: {__version__}' - def __init__(self, base_url: str, user: str, password: str, **kwargs): + def __init__(self, base_url: str, user: str, password: str, exc: bool = False, **kwargs): """ + :param base_url: TestRail address + :param user: Email for the account on the TestRail + :param password: Password for the account on the TestRail + :param exc: Catching exceptions :param kwargs: :key timeout int :key verify bool @@ -21,21 +39,63 @@ def __init__(self, base_url: str, user: str, password: str, **kwargs): self.__timeout = kwargs.get('timeout', 30) self.__session = requests.Session() self.__session.headers['User-Agent'] = self._user_agent - self.__session.headers['Content-Type'] = 'application/json' self.__session.headers.update(kwargs.get('headers', {})) self.__session.verify = kwargs.get('verify', True) self.__session.auth = (user, password) + self.__exc = exc + log.info( + 'Create Session{url: %s, user: %s, timeout: %s, headers: %s, verify: %s, exception: %s}', + base_url, user, self.__timeout, self.__session.headers, self.__session.verify, self.__exc + ) - def request(self, method: METHODS, src: str, **kwargs): - """Base request method""" - url = f'{self.__base_url}{src}' - response = self.__session.request(method=method.value, url=url, timeout=self.__timeout, **kwargs) - if 'json' in response.headers.get('Content-Type', ''): + def __del__(self): + self.__session.close() + + def __response(self, response: requests.Response): + if not response.ok: + log.error('Code: %s, reason: %s url: %s, content: %s', + response.status_code, response.reason, response.url, response.content) + if not self.__exc: + raise StatusCodeError(response.status_code, response.reason, response.url, response.content) + + log.debug('Response body: %s', response.text) + try: return response.json() - else: + except (JSONDecodeError, ValueError): return response.text or None - def attachment_request(self, method: METHODS, src: str, file: Path, **kwargs): + def request(self, method: METHODS, src: str, raw: bool = False, **kwargs): + """Base request method""" + url = f'{self.__base_url}{src}' + if not src.startswith('add_attachment'): + headers = kwargs.setdefault('headers', {}) + headers.update({'Content-Type': 'application/json'}) + + try: + response = self.__session.request(method=method.value, url=url, timeout=self.__timeout, **kwargs) + except Exception as err: + log.error('%s', err, exc_info=True) + raise + + log.debug('Response header: %s', response.headers) + return response if raw else self.__response(response) + + @staticmethod + def _path(path: Union[Path, str]) -> Path: + return path if isinstance(path, Path) else Path(path) + + def attachment_request(self, method: METHODS, src: str, file: Union[Path, str], **kwargs): + """""" + file = self._path(file) + with file.open('rb') as attachment: + return self.request(method, src, files={'attachment': attachment}, **kwargs) + + def get_attachment(self, method: METHODS, srs: str, file: Union[Path, str], **kwargs) -> Path: """""" - return self.request(method, src, files={'file': file.open('rb')}, - headers={'Content-Type': 'multipart/form-data'}, **kwargs) + file = self._path(file) + response = self.request(method, srs, raw=True, **kwargs) + if response.ok: + with file.open('wb') as attachment: + attachment.write(response.content) + return file + return self.__response(response) diff --git a/testrail_api/_testrail_api.py b/testrail_api/_testrail_api.py index d7c6a73..9765a6a 100644 --- a/testrail_api/_testrail_api.py +++ b/testrail_api/_testrail_api.py @@ -1,5 +1,5 @@ """ -Description +TestRail API Categories """ from . import _category