diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8d57d8..c1b9c147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] ### Added +- `__getstate__` and `__setstate__` methods in `RPClient` class to make it possible to pickle it, by @HardNorth +### Changed +- `token` field of `RPClient` class was renamed to `api_key` to maintain common convention, by @HardNorth +### Fixed +- Issue [#214](https://github.com/reportportal/client-Python/issues/214): HTTP RFC compliance fix for getting project settings, by @hanikhan + +## [5.3.4] +### Added - Check for parent `RPClient` object in thread before logging, by @HardNorth ## [5.3.3] diff --git a/README.md b/README.md index bca58cb4..196d2c51 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,13 @@ from reportportal_client.helpers import timestamp endpoint = "http://docker.local:8080" project = "default" # You can get UUID from user profile page in the Report Portal. -token = "1adf271d-505f-44a8-ad71-0afbdf8c83bd" +api_key = "1adf271d-505f-44a8-ad71-0afbdf8c83bd" launch_name = "Test launch" launch_doc = "Testing logging with attachment." client = RPClient(endpoint=endpoint, project=project, - token=token) + api_key=api_key) # Start log upload thread client.start() diff --git a/reportportal_client/client.py b/reportportal_client/client.py index 6d12a32d..8f34bd61 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -14,6 +14,7 @@ # limitations under the License import logging +import warnings from os import getenv import requests @@ -51,7 +52,7 @@ class RPClient(object): def __init__(self, endpoint, project, - token, + api_key=None, log_batch_size=20, is_skipped_an_issue=True, verify_ssl=True, @@ -61,12 +62,12 @@ def __init__(self, http_timeout=(10, 10), log_batch_payload_size=MAX_LOG_BATCH_PAYLOAD_SIZE, mode='DEFAULT', - **_): + **kwargs): """Initialize required attributes. :param endpoint: Endpoint of the report portal service :param project: Project name to report to - :param token: Authorization token + :param api_key: Authorization API key :param log_batch_size: Option to set the maximum number of logs that can be processed in one batch :param is_skipped_an_issue: Option to mark skipped tests as not @@ -96,33 +97,62 @@ def __init__(self, self.launch_id = launch_id self.log_batch_size = log_batch_size self.log_batch_payload_size = log_batch_payload_size - self.token = token self.verify_ssl = verify_ssl self.retries = retries self.max_pool_size = max_pool_size self.http_timeout = http_timeout - self.session = requests.Session() self.step_reporter = StepReporter(self) self._item_stack = [] self.mode = mode self._skip_analytics = getenv('AGENT_NO_ANALYTICS') + self.api_key = api_key + if not self.api_key: + if 'token' in kwargs: + warnings.warn( + message="Argument `token` is deprecated since 5.3.5 and " + "will be subject for removing in the next major " + "version. Use `api_key` argument instead.", + category=DeprecationWarning, + stacklevel=2 + ) + self.api_key = kwargs['token'] + + if not self.api_key: + warnings.warn( + message="Argument `api_key` is `None` or empty string, " + "that's not supposed to happen because Report " + "Portal is usually requires an authorization key. " + "Please check your code.", + category=RuntimeWarning, + stacklevel=2 + ) + + self.__init_session() + self.__init_log_manager() + + def __init_session(self): retry_strategy = Retry( - total=retries, + total=self.retries, backoff_factor=0.1, status_forcelist=[429, 500, 502, 503, 504] - ) if retries else DEFAULT_RETRIES - self.session.mount('https://', HTTPAdapter( - max_retries=retry_strategy, pool_maxsize=max_pool_size)) + ) if self.retries else DEFAULT_RETRIES + session = requests.Session() + session.mount('https://', HTTPAdapter( + max_retries=retry_strategy, pool_maxsize=self.max_pool_size)) # noinspection HttpUrlsUsage - self.session.mount('http://', HTTPAdapter( - max_retries=retry_strategy, pool_maxsize=max_pool_size)) - self.session.headers['Authorization'] = 'bearer {0}'.format(self.token) - + session.mount('http://', HTTPAdapter( + max_retries=retry_strategy, pool_maxsize=self.max_pool_size)) + if self.api_key: + session.headers['Authorization'] = 'Bearer {0}'.format( + self.api_key) + self.session = session + + def __init_log_manager(self): self._log_manager = LogManager( self.endpoint, self.session, self.api_v2, self.launch_id, - self.project, max_entry_number=log_batch_size, - max_payload_size=log_batch_payload_size, + self.project, max_entry_number=self.log_batch_size, + max_payload_size=self.log_batch_payload_size, verify_ssl=self.verify_ssl) def finish_launch(self, @@ -274,7 +304,7 @@ def get_project_settings(self): :return: HTTP response in dictionary """ url = uri_join(self.base_url_v1, 'settings') - response = HttpRequest(self.session.get, url=url, json={}, + response = HttpRequest(self.session.get, url=url, verify_ssl=self.verify_ssl).make() return response.json if response else None @@ -463,7 +493,7 @@ def clone(self): cloned = RPClient( endpoint=self.endpoint, project=self.project, - token=self.token, + api_key=self.api_key, log_batch_size=self.log_batch_size, is_skipped_an_issue=self.is_skipped_an_issue, verify_ssl=self.verify_ssl, @@ -478,3 +508,27 @@ def clone(self): if current_item: cloned._item_stack.append(current_item) return cloned + + def __getstate__(self): + """Control object pickling and return object fields as Dictionary. + + :returns: object state dictionary + :rtype: dict + """ + state = self.__dict__.copy() + # Don't pickle 'session' field, since it contains unpickling 'socket' + del state['session'] + # Don't pickle '_log_manager' field, since it uses 'session' field + del state['_log_manager'] + return state + + def __setstate__(self, state): + """Control object pickling, receives object state as Dictionary. + + :param dict state: object state dictionary + """ + self.__dict__.update(state) + # Restore 'session' field + self.__init_session() + # Restore '_log_manager' field + self.__init_log_manager() diff --git a/reportportal_client/client.pyi b/reportportal_client/client.pyi index 338ef024..1b06924d 100644 --- a/reportportal_client/client.pyi +++ b/reportportal_client/client.pyi @@ -7,9 +7,6 @@ from reportportal_client.logs.log_manager import LogManager as LogManager from reportportal_client.steps import StepReporter -def current() -> RPClient: ... - - class RPClient: _log_manager: LogManager = ... api_v1: Text = ... @@ -22,7 +19,7 @@ class RPClient: log_batch_size: int = ... log_batch_payload_size: int = ... project: Text = ... - token: Text = ... + api_key: Text = ... verify_ssl: bool = ... retries: int = ... max_pool_size: int = ... @@ -36,7 +33,8 @@ class RPClient: def __init__( self, endpoint: Text, - project: Text, token: Text, + project: Text, + api_key: Text, log_batch_size: int = ..., is_skipped_an_issue: bool = ..., verify_ssl: bool = ..., @@ -48,6 +46,10 @@ class RPClient: mode: str = ... ) -> None: ... + def __init_session(self) -> None: ... + + def __init_log_manager(self) -> None: ... + def finish_launch(self, end_time: Text, status: Text = ..., @@ -109,6 +111,6 @@ class RPClient: def current_item(self) -> Text: ... - def start(self) -> None : ... + def start(self) -> None: ... def clone(self) -> RPClient: ... diff --git a/setup.py b/setup.py index af687580..8d1aaabd 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup, find_packages -__version__ = '5.3.4' +__version__ = '5.3.5' TYPE_STUBS = ['*.pyi'] diff --git a/tests/conftest.py b/tests/conftest.py index 115fa316..fc040a8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,6 +28,6 @@ def inner(ret_code, ret_value): @fixture def rp_client(): """Prepare instance of the RPClient for testing.""" - client = RPClient('http://endpoint', 'project', 'token') + client = RPClient('http://endpoint', 'project', 'api_key') client.session = mock.Mock() return client diff --git a/tests/steps/conftest.py b/tests/steps/conftest.py index 6b0c16bd..cd376d97 100644 --- a/tests/steps/conftest.py +++ b/tests/steps/conftest.py @@ -21,7 +21,7 @@ @fixture def rp_client(): - client = RPClient('http://endpoint', 'project', 'token') + client = RPClient('http://endpoint', 'project', 'api_key') client.session = mock.Mock() client.step_reporter = StepReporter(client) return client diff --git a/tests/test_client.py b/tests/test_client.py index 489ca1ad..b63ad07b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -125,7 +125,7 @@ def get_call(*args, **kwargs): @mock.patch('reportportal_client.client.send_event') def test_skip_statistics(send_event, getenv): getenv.return_value = '1' - client = RPClient('http://endpoint', 'project', 'token') + client = RPClient('http://endpoint', 'project', 'api_key') client.session = mock.Mock() client.start_launch('Test Launch', timestamp()) assert mock.call('start_launch', None, None) not in send_event.mock_calls @@ -135,16 +135,16 @@ def test_skip_statistics(send_event, getenv): @mock.patch('reportportal_client.client.send_event') def test_statistics(send_event, getenv): getenv.return_value = '' - client = RPClient('http://endpoint', 'project', 'token') + client = RPClient('http://endpoint', 'project', 'api_key') client.session = mock.Mock() client.start_launch('Test Launch', timestamp()) assert mock.call('start_launch', None, None) in send_event.mock_calls def test_clone(): - args = ['http://endpoint', 'project', 'token'] - kwargs = {'log_batch_size': 30, 'is_skipped_an_issue': False, - 'verify_ssl': False, 'retries': 5, + args = ['http://endpoint', 'project'] + kwargs = {'api_key': 'api_key', 'log_batch_size': 30, + 'is_skipped_an_issue': False, 'verify_ssl': False, 'retries': 5, 'max_pool_size': 30, 'launch_id': 'test-123', 'http_timeout': (30, 30), 'log_batch_payload_size': 1000000, 'mode': 'DEBUG'} @@ -153,10 +153,10 @@ def test_clone(): client._item_stack.append('test-322') cloned = client.clone() assert cloned is not None and client is not cloned - assert cloned.endpoint == args[0] and cloned.project == args[ - 1] and cloned.token == args[2] - assert cloned.log_batch_size == kwargs[ - 'log_batch_size'] and cloned.is_skipped_an_issue == kwargs[ + assert cloned.endpoint == args[0] and cloned.project == args[1] + assert cloned.api_key == kwargs[ + 'api_key'] and cloned.log_batch_size == kwargs[ + 'log_batch_size'] and cloned.is_skipped_an_issue == kwargs[ 'is_skipped_an_issue'] and cloned.verify_ssl == kwargs[ 'verify_ssl'] and cloned.retries == kwargs[ 'retries'] and cloned.max_pool_size == kwargs[ @@ -166,3 +166,33 @@ def test_clone(): 'log_batch_payload_size'] and cloned.mode == kwargs['mode'] assert len(cloned._item_stack) == 1 \ and client.current_item() == cloned.current_item() + + +@mock.patch('reportportal_client.client.warnings.warn') +def test_deprecated_token_argument(warn): + api_key = 'api_key' + client = RPClient(endpoint='http://endpoint', project='project', + token=api_key) + + assert warn.call_count == 1 + assert client.api_key == api_key + + +@mock.patch('reportportal_client.client.warnings.warn') +def test_api_key_argument(warn): + api_key = 'api_key' + client = RPClient(endpoint='http://endpoint', project='project', + api_key=api_key) + + assert warn.call_count == 0 + assert client.api_key == api_key + + +@mock.patch('reportportal_client.client.warnings.warn') +def test_empty_api_key_argument(warn): + api_key = '' + client = RPClient(endpoint='http://endpoint', project='project', + api_key=api_key) + + assert warn.call_count == 1 + assert client.api_key == api_key