Skip to content

Commit edc516d

Browse files
authored
Merge pull request #258 from reportportal/develop
Release
2 parents c86efa9 + 2226ebf commit edc516d

File tree

6 files changed

+92
-17
lines changed

6 files changed

+92
-17
lines changed

CHANGELOG.md

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

33
## [Unreleased]
44
### Added
5+
- `ErrorPrintingHttpRequest` and `ErrorPrintingAsyncHttpRequest` classes to avoid recursion on ReportPortal logging, by @HardNorth
6+
### Removed
7+
- Any logging on log requests to ReportPortal, by @HardNorth
8+
9+
## [5.6.3]
10+
### Added
511
- All Requests now have their names, by @HardNorth
612
### Removed
713
- `NOT_FOUND` constant, as it only causes infinite issues, by @HardNorth

reportportal_client/aio/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
AsyncItemStartRequest,
6262
AsyncRPLogBatch,
6363
AsyncRPRequestLog,
64+
ErrorPrintingAsyncHttpRequest,
6465
LaunchFinishRequest,
6566
LaunchStartRequest,
6667
RPFile,
@@ -568,7 +569,7 @@ async def log_batch(self, log_batch: Optional[List[AsyncRPRequestLog]]) -> Optio
568569
"""
569570
url = root_uri_join(self.base_url_v2, "log")
570571
if log_batch:
571-
response = await AsyncHttpRequest(
572+
response = await ErrorPrintingAsyncHttpRequest(
572573
(await self.session()).post, url=url, data=AsyncRPLogBatch(log_batch).payload, name="log"
573574
).make()
574575
if not response:

reportportal_client/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
# noinspection PyProtectedMember
4141
from reportportal_client.core.rp_issues import Issue
4242
from reportportal_client.core.rp_requests import (
43+
ErrorPrintingHttpRequest,
4344
HttpRequest,
4445
ItemFinishRequest,
4546
ItemStartRequest,
@@ -809,7 +810,7 @@ def update_test_item(
809810
def _log(self, batch: Optional[List[RPRequestLog]]) -> Optional[Tuple[str, ...]]:
810811
if batch:
811812
url = uri_join(self.base_url_v2, "log")
812-
response = HttpRequest(
813+
response = ErrorPrintingHttpRequest(
813814
self.session.post,
814815
url,
815816
files=RPLogBatch(batch).payload,

reportportal_client/core/rp_requests.py

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020

2121
import asyncio
2222
import logging
23+
import sys
24+
import traceback
2325
from dataclasses import dataclass
26+
from datetime import datetime
2427
from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union
2528

2629
import aiohttp
@@ -48,7 +51,7 @@
4851

4952

5053
class HttpRequest:
51-
"""This model stores attributes related to ReportPortal HTTP requests."""
54+
"""This object stores attributes related to ReportPortal HTTP requests and makes them."""
5255

5356
session_method: Callable
5457
url: Any
@@ -121,8 +124,8 @@ def priority(self, value: Priority) -> None:
121124
def make(self) -> Optional[RPResponse]:
122125
"""Make HTTP request to the ReportPortal API.
123126
124-
The method catches any request preparation error to not fail reporting. Since we are reporting tool
125-
and should not fail tests.
127+
The method catches any request error to not fail reporting. Since we are reporting tool and should not fail
128+
tests.
126129
127130
:return: wrapped HTTP response or None in case of failure
128131
"""
@@ -141,8 +144,45 @@ def make(self) -> Optional[RPResponse]:
141144
logger.warning("ReportPortal %s request failed", self.name, exc_info=exc)
142145

143146

147+
class ErrorPrintingHttpRequest(HttpRequest):
148+
"""This is specific request object which catches any request error and prints it to the "std.err".
149+
150+
The object is supposed to be used in logging methods only to prevent infinite recursion of logging, when logging
151+
framework configured to log everything to ReportPortal. In this case if a request to ReportPortal fails, the
152+
failure will be logged to ReportPortal once again and, for example, in case of endpoint configuration error, it
153+
will also fail and will be logged again. So, the recursion will never end.
154+
155+
This class is used to prevent this situation. It catches any request error and prints it to the "std.err".
156+
"""
157+
158+
def make(self) -> Optional[RPResponse]:
159+
"""Make HTTP request to the ReportPortal API.
160+
161+
The method catches any request error and prints it to the "std.err".
162+
163+
:return: wrapped HTTP response or None in case of failure
164+
"""
165+
# noinspection PyBroadException
166+
try:
167+
return RPResponse(
168+
self.session_method(
169+
self.url,
170+
data=self.data,
171+
json=self.json,
172+
files=self.files,
173+
verify=self.verify_ssl,
174+
timeout=self.http_timeout,
175+
)
176+
)
177+
except Exception:
178+
print(
179+
f"{datetime.now().isoformat()} - [ERROR] - ReportPortal request error:\n{traceback.format_exc()}",
180+
file=sys.stderr,
181+
)
182+
183+
144184
class AsyncHttpRequest(HttpRequest):
145-
"""This model stores attributes related to asynchronous ReportPortal HTTP requests."""
185+
"""This object stores attributes related to asynchronous ReportPortal HTTP requests and make them."""
146186

147187
def __init__(
148188
self,
@@ -166,8 +206,8 @@ def __init__(
166206
async def make(self) -> Optional[AsyncRPResponse]:
167207
"""Asynchronously make HTTP request to the ReportPortal API.
168208
169-
The method catches any request preparation error to not fail reporting. Since we are reporting tool
170-
and should not fail tests.
209+
The method catches any request error to not fail reporting. Since we are reporting tool and should not fail
210+
tests.
171211
172212
:return: wrapped HTTP response or None in case of failure
173213
"""
@@ -182,6 +222,39 @@ async def make(self) -> Optional[AsyncRPResponse]:
182222
logger.warning("ReportPortal %s request failed", self.name, exc_info=exc)
183223

184224

225+
class ErrorPrintingAsyncHttpRequest(AsyncHttpRequest):
226+
"""This is specific request object which catches any request error and prints it to the "std.err".
227+
228+
The object is supposed to be used in logging methods only to prevent infinite recursion of logging, when logging
229+
framework configured to log everything to ReportPortal. In this case if a request to ReportPortal fails, the
230+
failure will be logged to ReportPortal once again and, for example, in case of endpoint configuration error, it
231+
will also fail and will be logged again. So, the recursion will never end.
232+
233+
This class is used to prevent this situation. It catches any request error and prints it to the "std.err".
234+
"""
235+
236+
async def make(self) -> Optional[AsyncRPResponse]:
237+
"""Asynchronously make HTTP request to the ReportPortal API.
238+
239+
The method catches any request error and prints it to the "std.err".
240+
241+
:return: wrapped HTTP response or None in case of failure
242+
"""
243+
url = await await_if_necessary(self.url)
244+
if not url:
245+
return None
246+
data = await await_if_necessary(self.data)
247+
json = await await_if_necessary(self.json)
248+
# noinspection PyBroadException
249+
try:
250+
return AsyncRPResponse(await self.session_method(url, data=data, json=json))
251+
except Exception:
252+
print(
253+
f"{datetime.now().isoformat()} - [ERROR] - ReportPortal request error:\n{traceback.format_exc()}",
254+
file=sys.stderr,
255+
)
256+
257+
185258
class RPRequestBase(metaclass=AbstractBaseClass):
186259
"""Base class for specific ReportPortal request models.
187260

reportportal_client/core/rp_responses.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ def _iter_json_messages(json: Any) -> Generator[str, None, None]:
3636
data = json.get("responses", [json])
3737
for chunk in data:
3838
if "message" not in chunk:
39-
logger.warning(f"Response chunk does not contain 'message' field: {str(chunk)}")
4039
continue
4140
message = chunk["message"]
4241
if message:
@@ -115,9 +114,7 @@ def message(self) -> Optional[str]:
115114
116115
:return: message as string or NOT_FOUND, or None if the response is not JSON
117116
"""
118-
if self.json is None:
119-
return None
120-
return self.json.get("message")
117+
return _get_field("message", self.json)
121118

122119
@property
123120
def messages(self) -> Optional[Tuple[str, ...]]:
@@ -181,10 +178,7 @@ async def message(self) -> Optional[str]:
181178
182179
:return: message as string or NOT_FOUND, or None if the response is not JSON
183180
"""
184-
json = await self.json
185-
if json is None:
186-
return None
187-
return _get_field("message", json)
181+
return _get_field("message", await self.json)
188182

189183
@property
190184
async def messages(self) -> Optional[Tuple[str, ...]]:

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 find_packages, setup
66

7-
__version__ = "5.6.3"
7+
__version__ = "5.6.4"
88

99
TYPE_STUBS = ["*.pyi"]
1010

0 commit comments

Comments
 (0)