Skip to content

Commit

Permalink
Merge pull request #215 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth committed Jun 7, 2023
2 parents 7db8dc5 + 12f7d16 commit 7405abf
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 37 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
88 changes: 71 additions & 17 deletions reportportal_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License

import logging
import warnings
from os import getenv

import requests
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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()
14 changes: 8 additions & 6 deletions reportportal_client/client.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ...
Expand All @@ -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 = ...
Expand All @@ -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 = ...,
Expand All @@ -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 = ...,
Expand Down Expand Up @@ -109,6 +111,6 @@ class RPClient:

def current_item(self) -> Text: ...

def start(self) -> None : ...
def start(self) -> None: ...

def clone(self) -> RPClient: ...
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from setuptools import setup, find_packages

__version__ = '5.3.4'
__version__ = '5.3.5'

TYPE_STUBS = ['*.pyi']

Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tests/steps/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 39 additions & 9 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'}
Expand All @@ -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[
Expand All @@ -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

0 comments on commit 7405abf

Please sign in to comment.