From 363dff7b6414abedd303464d6db26fb60c4ed28e Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Wed, 24 May 2023 12:17:33 +0000 Subject: [PATCH 01/12] Changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8d57d8..f92dfea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [5.3.4] ### Added - Check for parent `RPClient` object in thread before logging, by @HardNorth From 1119f4cf2d9284f64d0dacdb4a627a40a4230fe6 Mon Sep 17 00:00:00 2001 From: "reportportal.io" Date: Wed, 24 May 2023 12:17:33 +0000 Subject: [PATCH 02/12] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'] From ffa7ca5e9b744f59cab36d32907c1fe7bbf8514e Mon Sep 17 00:00:00 2001 From: Hani Khan Date: Mon, 29 May 2023 11:41:12 +0530 Subject: [PATCH 03/12] Update get method body to None for get_project_settings Removing body from get request for get_project_settings. --- reportportal_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index 6d12a32d..fd89d0d5 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -274,7 +274,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 From 07c67769e0b3f01fbccaeb2261ed05c97b6f8f30 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 11:58:07 +0300 Subject: [PATCH 04/12] Add __getstate__ and __setstate__ functions to RPClient to make it picklable --- reportportal_client/client.py | 47 ++++++++++++++++++++++++---------- reportportal_client/client.pyi | 8 +++++- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index 6d12a32d..b0287014 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -37,6 +37,22 @@ logger.addHandler(logging.NullHandler()) +def _init_session(token, retries, max_pool_size): + retry_strategy = Retry( + total=retries, + backoff_factor=0.1, + status_forcelist=[429, 500, 502, 503, 504] + ) if retries else DEFAULT_RETRIES + session = requests.Session() + session.mount('https://', HTTPAdapter( + max_retries=retry_strategy, pool_maxsize=max_pool_size)) + # noinspection HttpUrlsUsage + session.mount('http://', HTTPAdapter( + max_retries=retry_strategy, pool_maxsize=max_pool_size)) + session.headers['Authorization'] = 'bearer {0}'.format(token) + return session + + class RPClient(object): """Report portal client. @@ -101,24 +117,13 @@ def __init__(self, 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.started = False - retry_strategy = Retry( - total=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)) - # 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) - + self.session = _init_session(token, retries, max_pool_size) self._log_manager = LogManager( self.endpoint, self.session, self.api_v2, self.launch_id, self.project, max_entry_number=log_batch_size, @@ -292,6 +297,7 @@ def log(self, time, message, level=None, attachment=None, item_id=None): def start(self): """Start the client.""" self._log_manager.start() + self.started = True def start_launch(self, name, @@ -428,6 +434,7 @@ def start_test_item(self, def terminate(self, *args, **kwargs): """Call this to terminate the client.""" self._log_manager.stop() + self.started = False def update_test_item(self, item_uuid, attributes=None, description=None): """Update existing test item at the Report Portal. @@ -478,3 +485,17 @@ def clone(self): if current_item: cloned._item_stack.append(current_item) return cloned + + def __getstate__(self): + state = self.__dict__.copy() + # Don't pickle session, since it contains unpickling 'socket' + del state['session'] + return state + + def __setstate__(self, state): + self.__dict__.update(state) + # Restore session field + self.session = _init_session(self.token, self.retries, + self.max_pool_size) + if self.started: + self.start() diff --git a/reportportal_client/client.pyi b/reportportal_client/client.pyi index 338ef024..8a771a62 100644 --- a/reportportal_client/client.pyi +++ b/reportportal_client/client.pyi @@ -1,6 +1,7 @@ from typing import Any, Dict, List, Optional, Text, Tuple, Union from requests import Session +from urllib3 import Retry from reportportal_client.core.rp_issues import Issue as Issue from reportportal_client.logs.log_manager import LogManager as LogManager @@ -10,6 +11,10 @@ from reportportal_client.steps import StepReporter def current() -> RPClient: ... +def _init_session(token: Text, retries: Optional[int], + max_pool_size: int) -> Session: ... + + class RPClient: _log_manager: LogManager = ... api_v1: Text = ... @@ -32,6 +37,7 @@ class RPClient: mode: str = ... _skip_analytics: Text = ... _item_stack: List[Text] = ... + started: bool = ... def __init__( self, @@ -109,6 +115,6 @@ class RPClient: def current_item(self) -> Text: ... - def start(self) -> None : ... + def start(self) -> None: ... def clone(self) -> RPClient: ... From ec630252db2d16194eb99213bec4605eb126509d Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 12:17:12 +0300 Subject: [PATCH 05/12] CHANGELOG.md update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f92dfea8..c22c810e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- `__getstate__` and `__setstate__` methods in `RPClient` class to make it possible to pickle it, by @HardNorth ## [5.3.4] ### Added From 693c17d546e5e66efd732399b3871c3298238eb3 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 15:51:55 +0300 Subject: [PATCH 06/12] token` field of `RPClient` class was renamed to `api_key` to maintain common convention --- CHANGELOG.md | 2 + README.md | 4 +- reportportal_client/client.py | 80 +++++++++++++++++++++++----------- reportportal_client/client.pyi | 17 +++----- tests/conftest.py | 2 +- tests/steps/conftest.py | 2 +- tests/test_client.py | 18 ++++---- 7 files changed, 76 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c22c810e..67fc5434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [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 ## [5.3.4] ### Added 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 b0287014..ddd5445e 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 @@ -37,22 +38,6 @@ logger.addHandler(logging.NullHandler()) -def _init_session(token, retries, max_pool_size): - retry_strategy = Retry( - total=retries, - backoff_factor=0.1, - status_forcelist=[429, 500, 502, 503, 504] - ) if retries else DEFAULT_RETRIES - session = requests.Session() - session.mount('https://', HTTPAdapter( - max_retries=retry_strategy, pool_maxsize=max_pool_size)) - # noinspection HttpUrlsUsage - session.mount('http://', HTTPAdapter( - max_retries=retry_strategy, pool_maxsize=max_pool_size)) - session.headers['Authorization'] = 'bearer {0}'.format(token) - return session - - class RPClient(object): """Report portal client. @@ -67,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, @@ -77,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 @@ -112,7 +97,6 @@ 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 @@ -123,11 +107,53 @@ def __init__(self, self._skip_analytics = getenv('AGENT_NO_ANALYTICS') self.started = False - self.session = _init_session(token, retries, max_pool_size) + 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=self.retries, + backoff_factor=0.1, + status_forcelist=[429, 500, 502, 503, 504] + ) 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 + 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) + return 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, @@ -470,7 +496,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, @@ -490,12 +516,14 @@ def __getstate__(self): state = self.__dict__.copy() # Don't pickle session, since it contains unpickling 'socket' del state['session'] + del state['_log_manager'] return state def __setstate__(self, state): self.__dict__.update(state) # Restore session field - self.session = _init_session(self.token, self.retries, - self.max_pool_size) + self.__init_session() + self.__init_log_manager() + if self.started: self.start() diff --git a/reportportal_client/client.pyi b/reportportal_client/client.pyi index 8a771a62..893c6045 100644 --- a/reportportal_client/client.pyi +++ b/reportportal_client/client.pyi @@ -1,20 +1,12 @@ from typing import Any, Dict, List, Optional, Text, Tuple, Union from requests import Session -from urllib3 import Retry from reportportal_client.core.rp_issues import Issue as Issue from reportportal_client.logs.log_manager import LogManager as LogManager from reportportal_client.steps import StepReporter -def current() -> RPClient: ... - - -def _init_session(token: Text, retries: Optional[int], - max_pool_size: int) -> Session: ... - - class RPClient: _log_manager: LogManager = ... api_v1: Text = ... @@ -27,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 = ... @@ -42,7 +34,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 = ..., @@ -54,6 +47,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 = ..., 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..4f2a101e 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[ From dafe0ba6e2a866eca9172adfbbca1c0af38b67d1 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 16:01:04 +0300 Subject: [PATCH 07/12] Fix __init_session method --- reportportal_client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index ddd5445e..54c4a315 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -147,7 +147,7 @@ def __init_session(self): if self.api_key: session.headers['Authorization'] = 'Bearer {0}'.format( self.api_key) - return session + self.session = session def __init_log_manager(self): self._log_manager = LogManager( From 926762e76315f14bc12cc3b39340d8f3ec415d56 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 16:03:39 +0300 Subject: [PATCH 08/12] Correct some comments --- reportportal_client/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index 54c4a315..90c0c9da 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -514,16 +514,19 @@ def clone(self): def __getstate__(self): state = self.__dict__.copy() - # Don't pickle session, since it contains unpickling 'socket' + # 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): self.__dict__.update(state) - # Restore session field + # Restore 'session' field self.__init_session() + # Restore '_log_manager' field self.__init_log_manager() + # Start client if it was started if self.started: self.start() From 13fed9e0a58574eefbee4aa5beed0523a73d9d21 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 16:07:36 +0300 Subject: [PATCH 09/12] Remove 'started' flag --- reportportal_client/client.py | 7 ------- reportportal_client/client.pyi | 1 - 2 files changed, 8 deletions(-) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index 90c0c9da..feee05d3 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -105,7 +105,6 @@ def __init__(self, self._item_stack = [] self.mode = mode self._skip_analytics = getenv('AGENT_NO_ANALYTICS') - self.started = False self.api_key = api_key if not self.api_key: @@ -323,7 +322,6 @@ def log(self, time, message, level=None, attachment=None, item_id=None): def start(self): """Start the client.""" self._log_manager.start() - self.started = True def start_launch(self, name, @@ -460,7 +458,6 @@ def start_test_item(self, def terminate(self, *args, **kwargs): """Call this to terminate the client.""" self._log_manager.stop() - self.started = False def update_test_item(self, item_uuid, attributes=None, description=None): """Update existing test item at the Report Portal. @@ -526,7 +523,3 @@ def __setstate__(self, state): self.__init_session() # Restore '_log_manager' field self.__init_log_manager() - - # Start client if it was started - if self.started: - self.start() diff --git a/reportportal_client/client.pyi b/reportportal_client/client.pyi index 893c6045..1b06924d 100644 --- a/reportportal_client/client.pyi +++ b/reportportal_client/client.pyi @@ -29,7 +29,6 @@ class RPClient: mode: str = ... _skip_analytics: Text = ... _item_stack: List[Text] = ... - started: bool = ... def __init__( self, From 7220613a3e5eb2123fcefd926345b77e80f9ce49 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 16:16:28 +0300 Subject: [PATCH 10/12] Add pydocs --- reportportal_client/client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/reportportal_client/client.py b/reportportal_client/client.py index feee05d3..42d9b4aa 100644 --- a/reportportal_client/client.py +++ b/reportportal_client/client.py @@ -510,6 +510,11 @@ def clone(self): 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'] @@ -518,6 +523,10 @@ def __getstate__(self): 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() From 9bcbe5486675b0a506c080126a4569a368e7060a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 17:11:29 +0300 Subject: [PATCH 11/12] Add tests --- tests/test_client.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 4f2a101e..b63ad07b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -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 From 12f7d164eb95895836a89d08c03423fcf274b8e1 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 7 Jun 2023 17:15:30 +0300 Subject: [PATCH 12/12] CHANGELOG.md update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67fc5434..c1b9c147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - `__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