Skip to content

Commit db66fbb

Browse files
authored
Merge pull request #175 from reportportal/nested-steps
Nested steps
2 parents e98f646 + dd892fe commit db66fbb

File tree

13 files changed

+633
-33
lines changed

13 files changed

+633
-33
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ repos:
1212
rev: 6.0.0
1313
hooks:
1414
- id: pydocstyle
15+
exclude: |
16+
(?x)^(
17+
tests/.*
18+
)
1519
- repo: https://github.com/Lucas-C/pre-commit-hooks-markup
1620
rev: v1.0.1
1721
hooks:

reportportal_client/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"""
2-
Copyright (c) 2018 http://reportportal.io .
2+
Copyright (c) 2022 https://reportportal.io .
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
https://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,5 +15,11 @@
1515
"""
1616

1717
from .service import ReportPortalService
18+
from .steps import step
19+
from ._local import current
1820

19-
__all__ = ('ReportPortalService',)
21+
__all__ = [
22+
'ReportPortalService',
23+
'step',
24+
'current'
25+
]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) 2022 https://reportportal.io .
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# https://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License
13+
"""Report Portal client context storing and retrieving module."""
14+
from threading import local
15+
16+
17+
__INSTANCES = local()
18+
19+
20+
def current():
21+
"""Return current Report Portal client."""
22+
if hasattr(__INSTANCES, 'current'):
23+
return __INSTANCES.current
24+
25+
26+
def set_current(client):
27+
"""Save Report Portal client as current.
28+
29+
The method is not intended to use used by users. Report Portal client calls
30+
it itself when new client is created.
31+
32+
:param client: Report Portal client
33+
"""
34+
__INSTANCES.current = client

reportportal_client/client.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""This module contains Report Portal Client class.
22
3-
Copyright (c) 2018 http://reportportal.io .
3+
Copyright (c) 2022 https://reportportal.io .
44
55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
77
You may obtain a copy of the License at
88
9-
http://www.apache.org/licenses/LICENSE-2.0
9+
https://www.apache.org/licenses/LICENSE-2.0
1010
1111
Unless required by applicable law or agreed to in writing, software
1212
distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,22 +19,33 @@
1919
import requests
2020
from requests.adapters import HTTPAdapter
2121

22-
from reportportal_client.core.log_manager import LogManager
23-
from reportportal_client.core.rp_requests import (
22+
from ._local import set_current
23+
from .core.log_manager import LogManager
24+
from .core.rp_requests import (
2425
HttpRequest,
2526
ItemStartRequest,
2627
ItemFinishRequest,
2728
LaunchStartRequest,
2829
LaunchFinishRequest
2930
)
30-
from reportportal_client.helpers import uri_join, verify_value_length
31+
from .helpers import uri_join, verify_value_length
32+
from .static.defines import NOT_FOUND
33+
from .steps import StepReporter
3134

3235
logger = logging.getLogger(__name__)
3336
logger.addHandler(logging.NullHandler())
3437

3538

3639
class RPClient(object):
37-
"""Report portal client."""
40+
"""Report portal client.
41+
42+
The class is supposed to use by Report Portal agents: both custom and
43+
official to make calls to Report Portal. It handles HTTP request and
44+
response bodies generation and serialization, connection retries and log
45+
batching.
46+
NOTICE: the class is not thread-safe, use new class instance for every new
47+
thread to avoid request/response messing and other issues.
48+
"""
3849

3950
def __init__(self,
4051
endpoint,
@@ -60,6 +71,7 @@ def __init__(self,
6071
:param max_pool_size: Option to set the maximum number of
6172
connections to save the pool.
6273
"""
74+
set_current(self)
6375
self._batch_logs = []
6476
self.api_v1, self.api_v2 = 'v1', 'v2'
6577
self.endpoint = endpoint
@@ -74,6 +86,8 @@ def __init__(self,
7486
self.token = token
7587
self.verify_ssl = verify_ssl
7688
self.session = requests.Session()
89+
self.step_reporter = StepReporter(self)
90+
self._item_stack = []
7791
if retries:
7892
self.session.mount('https://', HTTPAdapter(
7993
max_retries=retries, pool_maxsize=max_pool_size))
@@ -128,13 +142,16 @@ def finish_test_item(self,
128142
"failed", "stopped", "skipped", "interrupted",
129143
"cancelled" or None
130144
:param attributes: Test item attributes(tags). Pairs of key and value.
131-
Overrides attributes on start
145+
Override attributes on start
132146
:param description: Test item description. Overrides description
133147
from start request.
134148
:param issue: Issue of the current test item
135149
:param retry: Used to report retry of the test. Allowable values:
136150
"True" or "False"
137151
"""
152+
if item_id is NOT_FOUND:
153+
logger.warning("Uttempt to finish non-existent item")
154+
return None
138155
url = uri_join(self.base_url_v2, 'item', item_id)
139156
request_payload = ItemFinishRequest(
140157
end_time,
@@ -148,6 +165,7 @@ def finish_test_item(self,
148165
).payload
149166
response = HttpRequest(self.session.put, url=url, json=request_payload,
150167
verify_ssl=self.verify_ssl).make()
168+
self._item_stack.pop()
151169
logger.debug('finish_test_item - ID: %s', item_id)
152170
logger.debug('response message: %s', response.message)
153171
return response.message
@@ -316,12 +334,19 @@ def start_test_item(self,
316334
retry=retry,
317335
test_case_id=test_case_id
318336
).payload
337+
319338
response = HttpRequest(self.session.post,
320339
url=url,
321340
json=request_payload,
322341
verify_ssl=self.verify_ssl).make()
323-
logger.debug('start_test_item - ID: %s', response.id)
324-
return response.id
342+
item_id = response.id
343+
if item_id is not NOT_FOUND:
344+
logger.debug('start_test_item - ID: %s', item_id)
345+
self._item_stack.append(item_id)
346+
else:
347+
logger.warning('start_test_item - invalid response: %s',
348+
str(response.json))
349+
return item_id
325350

326351
def terminate(self, *args, **kwargs):
327352
"""Call this to terminate the client."""
@@ -345,3 +370,7 @@ def update_test_item(self, item_uuid, attributes=None, description=None):
345370
verify_ssl=self.verify_ssl).make()
346371
logger.debug('update_test_item - Item: %s', item_id)
347372
return response.message
373+
374+
def current_item(self):
375+
"""Retrieve the last item reported by the client."""
376+
return self._item_stack[-1] if len(self._item_stack) > 0 else None

reportportal_client/client.pyi

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
from requests import Session
21
from typing import Any, Dict, List, Optional, Text, Tuple, Union
2+
3+
from requests import Session
4+
35
from reportportal_client.core.log_manager import LogManager as LogManager
46
from reportportal_client.core.rp_issues import Issue as Issue
7+
from reportportal_client.steps import StepReporter
8+
9+
10+
def current() -> RPClient: ...
11+
512

613
class RPClient:
714
_log_manager: LogManager = ...
@@ -17,6 +24,8 @@ class RPClient:
1724
token: Text = ...
1825
verify_ssl: bool = ...
1926
session: Session = ...
27+
step_reporter: StepReporter = ...
28+
2029
def __init__(self,
2130
endpoint: Text,
2231
project: Text, token: Text,
@@ -26,29 +35,38 @@ class RPClient:
2635
retries: int = ...,
2736
max_pool_size: int = ...,
2837
launch_id: Text = ...) -> None: ...
38+
2939
def finish_launch(self,
3040
end_time: Text,
3141
status: Text = ...,
3242
attributes: Optional[Union[List, Dict]] = ...,
3343
**kwargs: Any) -> Dict: ...
44+
3445
def finish_test_item(self,
35-
item_id: Text,
36-
end_time: Text,
37-
status: Text,
38-
issue: Optional[Issue] = ...,
39-
attributes: List = ...,
40-
**kwargs: Any) -> None: ...
46+
item_id: Text,
47+
end_time: Text,
48+
status: Text,
49+
issue: Optional[Issue] = ...,
50+
attributes: List = ...,
51+
**kwargs: Any) -> Optional[Text]: ...
52+
4153
def get_item_id_by_uuid(self, uuid: Text) -> Text: ...
54+
4255
def get_launch_info(self) -> Dict: ...
56+
4357
def get_launch_ui_id(self) -> Optional[Dict]: ...
58+
4459
def get_launch_ui_url(self) -> Text: ...
60+
4561
def get_project_settings(self) -> Dict: ...
62+
4663
def log(self,
4764
time: Text,
4865
message: Text,
4966
level: Optional[Union[int, Text]] = ...,
5067
attachment: Optional[Dict] = ...,
5168
item_id: Optional[Text] = ...) -> None: ...
69+
5270
def start_launch(self,
5371
name: Text,
5472
start_time: Text,
@@ -58,16 +76,23 @@ class RPClient:
5876
rerun: bool = ...,
5977
rerun_of: Text = ...,
6078
**kwargs: Any) -> Text: ...
79+
6180
def start_test_item(self,
62-
name: Text,
63-
start_time: Text,
64-
item_type: Text,
65-
description: Text = ...,
66-
attributes: Optional[Union[List, Dict]] = ...,
67-
parameters: Dict = ...,
68-
parent_item_id: Text = ...,
69-
has_stats: bool = ...,
70-
code_ref: Text = ...,
71-
**kwargs: Any) -> Text: ...
81+
name: Text,
82+
start_time: Text,
83+
item_type: Text,
84+
description: Text = ...,
85+
attributes: Optional[Union[List, Dict]] = ...,
86+
parameters: Dict = ...,
87+
parent_item_id: Text = ...,
88+
has_stats: bool = ...,
89+
code_ref: Text = ...,
90+
**kwargs: Any) -> Text: ...
91+
7292
def terminate(self, *args: Tuple, **kwargs: Any) -> None: ...
73-
def update_test_item(self, item_uuid: Text, attributes: Optional[Union[List, Dict]], description: Optional[Text]):
93+
94+
def update_test_item(self, item_uuid: Text,
95+
attributes: Optional[Union[List, Dict]],
96+
description: Optional[Text]) -> Text: ...
97+
98+
def current_item(self) -> Text: ...

reportportal_client/helpers.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
See the License for the specific language governing permissions and
1212
limitations under the License.
1313
"""
14+
import inspect
1415
import logging
1516
import time
1617
import uuid
18+
import warnings
1719
from platform import machine, processor, system
1820

1921
import six
@@ -156,3 +158,27 @@ def uri_join(*uri_parts):
156158
An uri string.
157159
"""
158160
return '/'.join(str(s).strip('/').strip('\\') for s in uri_parts)
161+
162+
163+
def get_function_params(func, args, kwargs):
164+
"""Extract argument names from the function and combine them with values.
165+
166+
:param func: the function to get arg names
167+
:param args: function's arg values
168+
:param kwargs: function's kwargs
169+
:return: a dictionary of values
170+
"""
171+
# Use deprecated method for python 2.7 compatibility, it's still here for
172+
# Python 3.10.2, so it's completely redundant to show the warning
173+
with warnings.catch_warnings():
174+
warnings.filterwarnings("ignore", category=DeprecationWarning)
175+
# noinspection PyDeprecation
176+
arg_spec = inspect.getargspec(func)
177+
result = dict()
178+
for i, arg_name in enumerate(arg_spec.args):
179+
if i >= len(args):
180+
break
181+
result[arg_name] = args[i]
182+
for arg_name, arg_value in kwargs.items():
183+
result[arg_name] = arg_value
184+
return result if len(result.items()) > 0 else None

reportportal_client/helpers.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ from .errors import EntryCreatedError as EntryCreatedError, \
33
ResponseError as ResponseError
44
from logging import Logger
55

6-
from typing import Text
6+
from typing import Text, Tuple, List, Callable, Any, Dict, Optional
77
from requests import Response
88

99
logger: Logger
@@ -21,5 +21,6 @@ def get_data(response: Response) -> dict: ...
2121
def get_json(response: Response) -> dict: ...
2222
def get_error_messages(data: dict) -> list: ...
2323
def verify_value_length(attributes: list[dict]) -> list[dict]: ...
24-
2524
def timestamp() -> Text: ...
25+
def get_function_params(func: Callable, args: Tuple[Any, ...],
26+
kwargs: Dict[Text, Any]) -> Dict[Text, Any]: ...

0 commit comments

Comments
 (0)