Skip to content

Commit 7405abf

Browse files
authored
Merge pull request #215 from reportportal/develop
Release
2 parents 7db8dc5 + 12f7d16 commit 7405abf

File tree

8 files changed

+131
-37
lines changed

8 files changed

+131
-37
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## [Unreleased]
44
### Added
5+
- `__getstate__` and `__setstate__` methods in `RPClient` class to make it possible to pickle it, by @HardNorth
6+
### Changed
7+
- `token` field of `RPClient` class was renamed to `api_key` to maintain common convention, by @HardNorth
8+
### Fixed
9+
- Issue [#214](https://github.com/reportportal/client-Python/issues/214): HTTP RFC compliance fix for getting project settings, by @hanikhan
10+
11+
## [5.3.4]
12+
### Added
513
- Check for parent `RPClient` object in thread before logging, by @HardNorth
614

715
## [5.3.3]

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ from reportportal_client.helpers import timestamp
4040
endpoint = "http://docker.local:8080"
4141
project = "default"
4242
# You can get UUID from user profile page in the Report Portal.
43-
token = "1adf271d-505f-44a8-ad71-0afbdf8c83bd"
43+
api_key = "1adf271d-505f-44a8-ad71-0afbdf8c83bd"
4444
launch_name = "Test launch"
4545
launch_doc = "Testing logging with attachment."
4646

4747

4848
client = RPClient(endpoint=endpoint, project=project,
49-
token=token)
49+
api_key=api_key)
5050

5151
# Start log upload thread
5252
client.start()

reportportal_client/client.py

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License
1515

1616
import logging
17+
import warnings
1718
from os import getenv
1819

1920
import requests
@@ -51,7 +52,7 @@ class RPClient(object):
5152
def __init__(self,
5253
endpoint,
5354
project,
54-
token,
55+
api_key=None,
5556
log_batch_size=20,
5657
is_skipped_an_issue=True,
5758
verify_ssl=True,
@@ -61,12 +62,12 @@ def __init__(self,
6162
http_timeout=(10, 10),
6263
log_batch_payload_size=MAX_LOG_BATCH_PAYLOAD_SIZE,
6364
mode='DEFAULT',
64-
**_):
65+
**kwargs):
6566
"""Initialize required attributes.
6667
6768
:param endpoint: Endpoint of the report portal service
6869
:param project: Project name to report to
69-
:param token: Authorization token
70+
:param api_key: Authorization API key
7071
:param log_batch_size: Option to set the maximum number of
7172
logs that can be processed in one batch
7273
:param is_skipped_an_issue: Option to mark skipped tests as not
@@ -96,33 +97,62 @@ def __init__(self,
9697
self.launch_id = launch_id
9798
self.log_batch_size = log_batch_size
9899
self.log_batch_payload_size = log_batch_payload_size
99-
self.token = token
100100
self.verify_ssl = verify_ssl
101101
self.retries = retries
102102
self.max_pool_size = max_pool_size
103103
self.http_timeout = http_timeout
104-
self.session = requests.Session()
105104
self.step_reporter = StepReporter(self)
106105
self._item_stack = []
107106
self.mode = mode
108107
self._skip_analytics = getenv('AGENT_NO_ANALYTICS')
109108

109+
self.api_key = api_key
110+
if not self.api_key:
111+
if 'token' in kwargs:
112+
warnings.warn(
113+
message="Argument `token` is deprecated since 5.3.5 and "
114+
"will be subject for removing in the next major "
115+
"version. Use `api_key` argument instead.",
116+
category=DeprecationWarning,
117+
stacklevel=2
118+
)
119+
self.api_key = kwargs['token']
120+
121+
if not self.api_key:
122+
warnings.warn(
123+
message="Argument `api_key` is `None` or empty string, "
124+
"that's not supposed to happen because Report "
125+
"Portal is usually requires an authorization key. "
126+
"Please check your code.",
127+
category=RuntimeWarning,
128+
stacklevel=2
129+
)
130+
131+
self.__init_session()
132+
self.__init_log_manager()
133+
134+
def __init_session(self):
110135
retry_strategy = Retry(
111-
total=retries,
136+
total=self.retries,
112137
backoff_factor=0.1,
113138
status_forcelist=[429, 500, 502, 503, 504]
114-
) if retries else DEFAULT_RETRIES
115-
self.session.mount('https://', HTTPAdapter(
116-
max_retries=retry_strategy, pool_maxsize=max_pool_size))
139+
) if self.retries else DEFAULT_RETRIES
140+
session = requests.Session()
141+
session.mount('https://', HTTPAdapter(
142+
max_retries=retry_strategy, pool_maxsize=self.max_pool_size))
117143
# noinspection HttpUrlsUsage
118-
self.session.mount('http://', HTTPAdapter(
119-
max_retries=retry_strategy, pool_maxsize=max_pool_size))
120-
self.session.headers['Authorization'] = 'bearer {0}'.format(self.token)
121-
144+
session.mount('http://', HTTPAdapter(
145+
max_retries=retry_strategy, pool_maxsize=self.max_pool_size))
146+
if self.api_key:
147+
session.headers['Authorization'] = 'Bearer {0}'.format(
148+
self.api_key)
149+
self.session = session
150+
151+
def __init_log_manager(self):
122152
self._log_manager = LogManager(
123153
self.endpoint, self.session, self.api_v2, self.launch_id,
124-
self.project, max_entry_number=log_batch_size,
125-
max_payload_size=log_batch_payload_size,
154+
self.project, max_entry_number=self.log_batch_size,
155+
max_payload_size=self.log_batch_payload_size,
126156
verify_ssl=self.verify_ssl)
127157

128158
def finish_launch(self,
@@ -274,7 +304,7 @@ def get_project_settings(self):
274304
:return: HTTP response in dictionary
275305
"""
276306
url = uri_join(self.base_url_v1, 'settings')
277-
response = HttpRequest(self.session.get, url=url, json={},
307+
response = HttpRequest(self.session.get, url=url,
278308
verify_ssl=self.verify_ssl).make()
279309
return response.json if response else None
280310

@@ -463,7 +493,7 @@ def clone(self):
463493
cloned = RPClient(
464494
endpoint=self.endpoint,
465495
project=self.project,
466-
token=self.token,
496+
api_key=self.api_key,
467497
log_batch_size=self.log_batch_size,
468498
is_skipped_an_issue=self.is_skipped_an_issue,
469499
verify_ssl=self.verify_ssl,
@@ -478,3 +508,27 @@ def clone(self):
478508
if current_item:
479509
cloned._item_stack.append(current_item)
480510
return cloned
511+
512+
def __getstate__(self):
513+
"""Control object pickling and return object fields as Dictionary.
514+
515+
:returns: object state dictionary
516+
:rtype: dict
517+
"""
518+
state = self.__dict__.copy()
519+
# Don't pickle 'session' field, since it contains unpickling 'socket'
520+
del state['session']
521+
# Don't pickle '_log_manager' field, since it uses 'session' field
522+
del state['_log_manager']
523+
return state
524+
525+
def __setstate__(self, state):
526+
"""Control object pickling, receives object state as Dictionary.
527+
528+
:param dict state: object state dictionary
529+
"""
530+
self.__dict__.update(state)
531+
# Restore 'session' field
532+
self.__init_session()
533+
# Restore '_log_manager' field
534+
self.__init_log_manager()

reportportal_client/client.pyi

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ from reportportal_client.logs.log_manager import LogManager as LogManager
77
from reportportal_client.steps import StepReporter
88

99

10-
def current() -> RPClient: ...
11-
12-
1310
class RPClient:
1411
_log_manager: LogManager = ...
1512
api_v1: Text = ...
@@ -22,7 +19,7 @@ class RPClient:
2219
log_batch_size: int = ...
2320
log_batch_payload_size: int = ...
2421
project: Text = ...
25-
token: Text = ...
22+
api_key: Text = ...
2623
verify_ssl: bool = ...
2724
retries: int = ...
2825
max_pool_size: int = ...
@@ -36,7 +33,8 @@ class RPClient:
3633
def __init__(
3734
self,
3835
endpoint: Text,
39-
project: Text, token: Text,
36+
project: Text,
37+
api_key: Text,
4038
log_batch_size: int = ...,
4139
is_skipped_an_issue: bool = ...,
4240
verify_ssl: bool = ...,
@@ -48,6 +46,10 @@ class RPClient:
4846
mode: str = ...
4947
) -> None: ...
5048

49+
def __init_session(self) -> None: ...
50+
51+
def __init_log_manager(self) -> None: ...
52+
5153
def finish_launch(self,
5254
end_time: Text,
5355
status: Text = ...,
@@ -109,6 +111,6 @@ class RPClient:
109111

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

112-
def start(self) -> None : ...
114+
def start(self) -> None: ...
113115

114116
def clone(self) -> RPClient: ...

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from setuptools import setup, find_packages
66

7-
__version__ = '5.3.4'
7+
__version__ = '5.3.5'
88

99
TYPE_STUBS = ['*.pyi']
1010

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ def inner(ret_code, ret_value):
2828
@fixture
2929
def rp_client():
3030
"""Prepare instance of the RPClient for testing."""
31-
client = RPClient('http://endpoint', 'project', 'token')
31+
client = RPClient('http://endpoint', 'project', 'api_key')
3232
client.session = mock.Mock()
3333
return client

tests/steps/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
@fixture
2323
def rp_client():
24-
client = RPClient('http://endpoint', 'project', 'token')
24+
client = RPClient('http://endpoint', 'project', 'api_key')
2525
client.session = mock.Mock()
2626
client.step_reporter = StepReporter(client)
2727
return client

tests/test_client.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def get_call(*args, **kwargs):
125125
@mock.patch('reportportal_client.client.send_event')
126126
def test_skip_statistics(send_event, getenv):
127127
getenv.return_value = '1'
128-
client = RPClient('http://endpoint', 'project', 'token')
128+
client = RPClient('http://endpoint', 'project', 'api_key')
129129
client.session = mock.Mock()
130130
client.start_launch('Test Launch', timestamp())
131131
assert mock.call('start_launch', None, None) not in send_event.mock_calls
@@ -135,16 +135,16 @@ def test_skip_statistics(send_event, getenv):
135135
@mock.patch('reportportal_client.client.send_event')
136136
def test_statistics(send_event, getenv):
137137
getenv.return_value = ''
138-
client = RPClient('http://endpoint', 'project', 'token')
138+
client = RPClient('http://endpoint', 'project', 'api_key')
139139
client.session = mock.Mock()
140140
client.start_launch('Test Launch', timestamp())
141141
assert mock.call('start_launch', None, None) in send_event.mock_calls
142142

143143

144144
def test_clone():
145-
args = ['http://endpoint', 'project', 'token']
146-
kwargs = {'log_batch_size': 30, 'is_skipped_an_issue': False,
147-
'verify_ssl': False, 'retries': 5,
145+
args = ['http://endpoint', 'project']
146+
kwargs = {'api_key': 'api_key', 'log_batch_size': 30,
147+
'is_skipped_an_issue': False, 'verify_ssl': False, 'retries': 5,
148148
'max_pool_size': 30, 'launch_id': 'test-123',
149149
'http_timeout': (30, 30),
150150
'log_batch_payload_size': 1000000, 'mode': 'DEBUG'}
@@ -153,10 +153,10 @@ def test_clone():
153153
client._item_stack.append('test-322')
154154
cloned = client.clone()
155155
assert cloned is not None and client is not cloned
156-
assert cloned.endpoint == args[0] and cloned.project == args[
157-
1] and cloned.token == args[2]
158-
assert cloned.log_batch_size == kwargs[
159-
'log_batch_size'] and cloned.is_skipped_an_issue == kwargs[
156+
assert cloned.endpoint == args[0] and cloned.project == args[1]
157+
assert cloned.api_key == kwargs[
158+
'api_key'] and cloned.log_batch_size == kwargs[
159+
'log_batch_size'] and cloned.is_skipped_an_issue == kwargs[
160160
'is_skipped_an_issue'] and cloned.verify_ssl == kwargs[
161161
'verify_ssl'] and cloned.retries == kwargs[
162162
'retries'] and cloned.max_pool_size == kwargs[
@@ -166,3 +166,33 @@ def test_clone():
166166
'log_batch_payload_size'] and cloned.mode == kwargs['mode']
167167
assert len(cloned._item_stack) == 1 \
168168
and client.current_item() == cloned.current_item()
169+
170+
171+
@mock.patch('reportportal_client.client.warnings.warn')
172+
def test_deprecated_token_argument(warn):
173+
api_key = 'api_key'
174+
client = RPClient(endpoint='http://endpoint', project='project',
175+
token=api_key)
176+
177+
assert warn.call_count == 1
178+
assert client.api_key == api_key
179+
180+
181+
@mock.patch('reportportal_client.client.warnings.warn')
182+
def test_api_key_argument(warn):
183+
api_key = 'api_key'
184+
client = RPClient(endpoint='http://endpoint', project='project',
185+
api_key=api_key)
186+
187+
assert warn.call_count == 0
188+
assert client.api_key == api_key
189+
190+
191+
@mock.patch('reportportal_client.client.warnings.warn')
192+
def test_empty_api_key_argument(warn):
193+
api_key = ''
194+
client = RPClient(endpoint='http://endpoint', project='project',
195+
api_key=api_key)
196+
197+
assert warn.call_count == 1
198+
assert client.api_key == api_key

0 commit comments

Comments
 (0)