port-ocean 0.12.2.dev10__py3-none-any.whl → 0.12.2.dev12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of port-ocean might be problematic. Click here for more details.
- port_ocean/cli/commands/list_integrations.py +8 -5
- port_ocean/cli/commands/pull.py +20 -16
- port_ocean/clients/port/authentication.py +12 -13
- port_ocean/clients/port/client.py +9 -8
- port_ocean/clients/port/mixins/blueprints.py +14 -14
- port_ocean/clients/port/mixins/entities.py +7 -8
- port_ocean/clients/port/mixins/integrations.py +11 -11
- port_ocean/clients/port/mixins/migrations.py +5 -5
- port_ocean/clients/port/retry_transport.py +13 -35
- port_ocean/clients/port/utils.py +32 -23
- port_ocean/core/defaults/clean.py +3 -3
- port_ocean/core/defaults/common.py +3 -3
- port_ocean/core/defaults/initialize.py +48 -47
- port_ocean/core/integrations/base.py +3 -2
- port_ocean/core/integrations/mixins/sync_raw.py +3 -3
- port_ocean/core/utils.py +4 -3
- port_ocean/helpers/retry.py +71 -221
- port_ocean/log/logger_setup.py +2 -2
- port_ocean/ocean.py +18 -24
- port_ocean/tests/clients/port/mixins/test_entities.py +3 -4
- port_ocean/tests/test_smoke.py +3 -3
- port_ocean/utils/async_http.py +13 -8
- port_ocean/utils/repeat.py +9 -2
- port_ocean/utils/signal.py +1 -2
- {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/METADATA +3 -1
- {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/RECORD +29 -30
- {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/WHEEL +1 -1
- port_ocean/helpers/async_client.py +0 -53
- {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Type, Any
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import aiohttp
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
7
|
from port_ocean.clients.port.client import PortClient
|
|
@@ -21,7 +21,7 @@ from port_ocean.exceptions.port_defaults import (
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def deconstruct_blueprints_to_creation_steps(
|
|
24
|
-
|
|
24
|
+
raw_blueprints: list[dict[str, Any]],
|
|
25
25
|
) -> tuple[list[dict[str, Any]], ...]:
|
|
26
26
|
"""
|
|
27
27
|
Deconstructing the blueprint into stages so the api wont fail to create a blueprint if there is a conflict
|
|
@@ -53,9 +53,9 @@ def deconstruct_blueprints_to_creation_steps(
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
async def _initialize_required_integration_settings(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
port_client: PortClient,
|
|
57
|
+
default_mapping: PortAppConfig,
|
|
58
|
+
integration_config: IntegrationConfiguration,
|
|
59
59
|
) -> None:
|
|
60
60
|
try:
|
|
61
61
|
logger.info("Initializing integration at port")
|
|
@@ -80,8 +80,8 @@ async def _initialize_required_integration_settings(
|
|
|
80
80
|
integration_config.event_listener.to_request(),
|
|
81
81
|
port_app_config=default_mapping,
|
|
82
82
|
)
|
|
83
|
-
except
|
|
84
|
-
logger.error(f"Failed to apply default mapping: {err.
|
|
83
|
+
except aiohttp.ClientResponseError as err:
|
|
84
|
+
logger.error(f"Failed to apply default mapping: {err.message}.")
|
|
85
85
|
raise err
|
|
86
86
|
|
|
87
87
|
logger.info("Checking for diff in integration configuration")
|
|
@@ -89,9 +89,9 @@ async def _initialize_required_integration_settings(
|
|
|
89
89
|
"changelog_destination"
|
|
90
90
|
)
|
|
91
91
|
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
integration.get("changelogDestination") != changelog_destination
|
|
93
|
+
or integration.get("installationAppType") != integration_config.integration.type
|
|
94
|
+
or integration.get("version") != port_client.integration_version
|
|
95
95
|
):
|
|
96
96
|
await port_client.patch_integration(
|
|
97
97
|
integration_config.integration.type, changelog_destination
|
|
@@ -99,8 +99,8 @@ async def _initialize_required_integration_settings(
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
async def _create_resources(
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
port_client: PortClient,
|
|
103
|
+
defaults: Defaults,
|
|
104
104
|
) -> None:
|
|
105
105
|
creation_stage, *blueprint_patches = deconstruct_blueprints_to_creation_steps(
|
|
106
106
|
defaults.blueprints
|
|
@@ -133,9 +133,9 @@ async def _create_resources(
|
|
|
133
133
|
|
|
134
134
|
if blueprint_errors:
|
|
135
135
|
for error in blueprint_errors:
|
|
136
|
-
if isinstance(error,
|
|
136
|
+
if isinstance(error, aiohttp.ClientResponseError):
|
|
137
137
|
logger.warning(
|
|
138
|
-
f"Failed to create resources: {error.
|
|
138
|
+
f"Failed to create resources: {error.message}. Rolling back changes..."
|
|
139
139
|
)
|
|
140
140
|
|
|
141
141
|
raise AbortDefaultCreationError(
|
|
@@ -155,8 +155,8 @@ async def _create_resources(
|
|
|
155
155
|
)
|
|
156
156
|
)
|
|
157
157
|
|
|
158
|
-
except
|
|
159
|
-
logger.error(f"Failed to create resources: {err.
|
|
158
|
+
except aiohttp.ClientResponseError as err:
|
|
159
|
+
logger.error(f"Failed to create resources: {err.message}. continuing...")
|
|
160
160
|
raise AbortDefaultCreationError(created_blueprints_identifiers, [err])
|
|
161
161
|
try:
|
|
162
162
|
created_actions, actions_errors = await gather_and_split_errors_from_results(
|
|
@@ -185,9 +185,9 @@ async def _create_resources(
|
|
|
185
185
|
errors = actions_errors + scorecards_errors + pages_errors
|
|
186
186
|
if errors:
|
|
187
187
|
for error in errors:
|
|
188
|
-
if isinstance(error,
|
|
188
|
+
if isinstance(error, aiohttp.ClientResponseError):
|
|
189
189
|
logger.warning(
|
|
190
|
-
f"Failed to create resource: {error.
|
|
190
|
+
f"Failed to create resource: {error.message}. continuing..."
|
|
191
191
|
)
|
|
192
192
|
|
|
193
193
|
except Exception as err:
|
|
@@ -195,44 +195,45 @@ async def _create_resources(
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
async def _initialize_defaults(
|
|
198
|
-
|
|
198
|
+
config_class: Type[PortAppConfig], integration_config: IntegrationConfiguration
|
|
199
199
|
) -> None:
|
|
200
200
|
port_client = ocean.port_client
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
async with ocean.port_client.client:
|
|
202
|
+
defaults = get_port_integration_defaults(config_class)
|
|
203
|
+
if not defaults:
|
|
204
|
+
logger.warning("No defaults found. Skipping initialization...")
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
if defaults.port_app_config:
|
|
208
|
+
await _initialize_required_integration_settings(
|
|
209
|
+
port_client, defaults.port_app_config, integration_config
|
|
210
|
+
)
|
|
210
211
|
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
if not integration_config.initialize_port_resources:
|
|
213
|
+
return
|
|
213
214
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
try:
|
|
216
|
+
logger.info("Found default resources, starting creation process")
|
|
217
|
+
await _create_resources(port_client, defaults)
|
|
218
|
+
except AbortDefaultCreationError as e:
|
|
219
|
+
logger.warning(
|
|
220
|
+
f"Failed to create resources. Rolling back blueprints : {e.blueprints_to_rollback}"
|
|
221
|
+
)
|
|
222
|
+
await asyncio.gather(
|
|
223
|
+
*(
|
|
224
|
+
port_client.delete_blueprint(
|
|
225
|
+
identifier,
|
|
226
|
+
should_raise=False,
|
|
227
|
+
user_agent_type=UserAgentType.exporter,
|
|
228
|
+
)
|
|
229
|
+
for identifier in e.blueprints_to_rollback
|
|
227
230
|
)
|
|
228
|
-
for identifier in e.blueprints_to_rollback
|
|
229
231
|
)
|
|
230
|
-
|
|
231
|
-
raise ExceptionGroup(str(e), e.errors)
|
|
232
|
+
raise ExceptionGroup(str(e), e.errors)
|
|
232
233
|
|
|
233
234
|
|
|
234
235
|
def initialize_defaults(
|
|
235
|
-
|
|
236
|
+
config_class: Type[PortAppConfig], integration_config: IntegrationConfiguration
|
|
236
237
|
) -> None:
|
|
237
238
|
asyncio.new_event_loop().run_until_complete(
|
|
238
239
|
_initialize_defaults(config_class, integration_config)
|
|
@@ -77,5 +77,6 @@ class BaseIntegration(SyncRawMixin, SyncMixin):
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
logger.info("Initializing event listener")
|
|
80
|
-
event_listener = await self.event_listener_factory.create_event_listener()
|
|
81
|
-
await event_listener.start()
|
|
80
|
+
# event_listener = await self.event_listener_factory.create_event_listener()
|
|
81
|
+
# await event_listener.start()
|
|
82
|
+
|
|
@@ -431,13 +431,13 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
431
431
|
# entities_at_port = await ocean.port_client.search_entities(
|
|
432
432
|
# user_agent_type
|
|
433
433
|
# )
|
|
434
|
-
# except
|
|
434
|
+
# except aiohttp.ClientError as e:
|
|
435
435
|
# logger.warning(
|
|
436
436
|
# "Failed to fetch the current state of entities at Port. "
|
|
437
437
|
# "Skipping delete phase due to unknown initial state. "
|
|
438
438
|
# f"Error: {e}\n"
|
|
439
|
-
# f"Response status code: {e.
|
|
440
|
-
# f"Response content: {e.
|
|
439
|
+
# f"Response status code: {e.status if isinstance(e, aiohttp.ClientResponseError) else None}\n"
|
|
440
|
+
# f"Response content: {e.message if isinstance(e, aiohttp.ClientResponseError) else None}\n"
|
|
441
441
|
# )
|
|
442
442
|
# did_fetched_current_state = False
|
|
443
443
|
|
port_ocean/core/utils.py
CHANGED
|
@@ -39,9 +39,10 @@ async def validate_integration_runtime(
|
|
|
39
39
|
requested_runtime: Runtime,
|
|
40
40
|
) -> None:
|
|
41
41
|
logger.debug("Validating integration runtime")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
async with port_client.client:
|
|
43
|
+
current_integration = await port_client.get_current_integration(
|
|
44
|
+
should_raise=False, should_log=False
|
|
45
|
+
)
|
|
45
46
|
current_runtime = current_integration.get("installationType", "OnPrem")
|
|
46
47
|
if current_integration and current_runtime != requested_runtime.value:
|
|
47
48
|
raise IntegrationRuntimeException(
|
port_ocean/helpers/retry.py
CHANGED
|
@@ -1,51 +1,18 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import functools
|
|
2
3
|
import random
|
|
3
|
-
import time
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from functools import partial
|
|
6
5
|
from http import HTTPStatus
|
|
7
|
-
from typing import Any, Callable, Coroutine, Iterable, Mapping
|
|
6
|
+
from typing import Any, Callable, Coroutine, Iterable, Mapping
|
|
8
7
|
|
|
9
|
-
import
|
|
8
|
+
import aiohttp
|
|
9
|
+
from aiohttp import ClientResponse
|
|
10
10
|
from dateutil.parser import isoparse
|
|
11
|
+
from loguru import logger
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
# Adapted from https://github.com/encode/httpx/issues/108#issuecomment-
|
|
14
|
-
class
|
|
15
|
-
"""
|
|
16
|
-
A custom HTTP transport that automatically retries requests using an exponential backoff strategy
|
|
17
|
-
for specific HTTP status codes and request methods.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
wrapped_transport (Union[httpx.BaseTransport, httpx.AsyncBaseTransport]): The underlying HTTP transport
|
|
21
|
-
to wrap and use for making requests.
|
|
22
|
-
max_attempts (int, optional): The maximum number of times to retry a request before giving up. Defaults to 10.
|
|
23
|
-
max_backoff_wait (float, optional): The maximum time to wait between retries in seconds. Defaults to 60.
|
|
24
|
-
backoff_factor (float, optional): The factor by which the wait time increases with each retry attempt.
|
|
25
|
-
Defaults to 0.1.
|
|
26
|
-
jitter_ratio (float, optional): The amount of jitter to add to the backoff time. Jitter is a random
|
|
27
|
-
value added to the backoff time to avoid a "thundering herd" effect. The value should be between 0 and 0.5.
|
|
28
|
-
Defaults to 0.1.
|
|
29
|
-
respect_retry_after_header (bool, optional): Whether to respect the Retry-After header in HTTP responses
|
|
30
|
-
when deciding how long to wait before retrying. Defaults to True.
|
|
31
|
-
retryable_methods (Iterable[str], optional): The HTTP methods that can be retried. Defaults to
|
|
32
|
-
["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"].
|
|
33
|
-
retry_status_codes (Iterable[int], optional): The HTTP status codes that can be retried. Defaults to
|
|
34
|
-
[429, 502, 503, 504].
|
|
35
|
-
|
|
36
|
-
Attributes:
|
|
37
|
-
_wrapped_transport (Union[httpx.BaseTransport, httpx.AsyncBaseTransport]): The underlying HTTP transport
|
|
38
|
-
being wrapped.
|
|
39
|
-
_max_attempts (int): The maximum number of times to retry a request.
|
|
40
|
-
_backoff_factor (float): The factor by which the wait time increases with each retry attempt.
|
|
41
|
-
_respect_retry_after_header (bool): Whether to respect the Retry-After header in HTTP responses.
|
|
42
|
-
_retryable_methods (frozenset): The HTTP methods that can be retried.
|
|
43
|
-
_retry_status_codes (frozenset): The HTTP status codes that can be retried.
|
|
44
|
-
_jitter_ratio (float): The amount of jitter to add to the backoff time.
|
|
45
|
-
_max_backoff_wait (float): The maximum time to wait between retries in seconds.
|
|
46
|
-
|
|
47
|
-
"""
|
|
48
|
-
|
|
14
|
+
# Adapted from https://github.com/encode/httpx/issues/108#issuecomment-1434439481n
|
|
15
|
+
class RetryRequestClass(aiohttp.ClientRequest):
|
|
49
16
|
RETRYABLE_METHODS = frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
|
|
50
17
|
RETRYABLE_STATUS_CODES = frozenset(
|
|
51
18
|
[
|
|
@@ -58,23 +25,23 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
58
25
|
MAX_BACKOFF_WAIT = 60
|
|
59
26
|
|
|
60
27
|
def __init__(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
28
|
+
self,
|
|
29
|
+
method,
|
|
30
|
+
url,
|
|
31
|
+
max_attempts: int = 10,
|
|
32
|
+
max_backoff_wait: float = MAX_BACKOFF_WAIT,
|
|
33
|
+
backoff_factor: float = 0.1,
|
|
34
|
+
jitter_ratio: float = 0.1,
|
|
35
|
+
respect_retry_after_header: bool = True,
|
|
36
|
+
retryable_methods: Iterable[str] | None = None,
|
|
37
|
+
retry_status_codes: Iterable[int] | None = None,
|
|
38
|
+
*args,
|
|
39
|
+
**kwargs
|
|
71
40
|
) -> None:
|
|
72
41
|
"""
|
|
73
42
|
Initializes the instance of RetryTransport class with the given parameters.
|
|
74
43
|
|
|
75
44
|
Args:
|
|
76
|
-
wrapped_transport (Union[httpx.BaseTransport, httpx.AsyncBaseTransport]):
|
|
77
|
-
The transport layer that will be wrapped and retried upon failure.
|
|
78
45
|
max_attempts (int, optional):
|
|
79
46
|
The maximum number of times the request can be retried in case of failure.
|
|
80
47
|
Defaults to 10.
|
|
@@ -98,7 +65,6 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
98
65
|
Defaults to [429, 502, 503, 504].
|
|
99
66
|
logger (Any): The logger to use for logging retries.
|
|
100
67
|
"""
|
|
101
|
-
self._wrapped_transport = wrapped_transport
|
|
102
68
|
if jitter_ratio < 0 or jitter_ratio > 0.5:
|
|
103
69
|
raise ValueError(
|
|
104
70
|
f"Jitter ratio should be between 0 and 0.5, actual {jitter_ratio}"
|
|
@@ -119,117 +85,34 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
119
85
|
)
|
|
120
86
|
self._jitter_ratio = jitter_ratio
|
|
121
87
|
self._max_backoff_wait = max_backoff_wait
|
|
122
|
-
self._logger = logger
|
|
123
|
-
|
|
124
|
-
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
125
|
-
"""
|
|
126
|
-
Sends an HTTP request, possibly with retries.
|
|
127
|
-
|
|
128
|
-
Args:
|
|
129
|
-
request (httpx.Request): The request to send.
|
|
130
|
-
|
|
131
|
-
Returns:
|
|
132
|
-
httpx.Response: The response received.
|
|
133
|
-
|
|
134
|
-
"""
|
|
135
|
-
transport: httpx.BaseTransport = self._wrapped_transport # type: ignore
|
|
136
|
-
if request.method in self._retryable_methods:
|
|
137
|
-
send_method = partial(transport.handle_request)
|
|
138
|
-
response = self._retry_operation(request, send_method)
|
|
139
|
-
else:
|
|
140
|
-
response = transport.handle_request(request)
|
|
141
|
-
return response
|
|
142
|
-
|
|
143
|
-
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
144
|
-
"""Sends an HTTP request, possibly with retries.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
request: The request to perform.
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
The response.
|
|
151
|
-
|
|
152
|
-
"""
|
|
153
|
-
transport: httpx.AsyncBaseTransport = self._wrapped_transport # type: ignore
|
|
154
|
-
if self._is_retryable_method(request):
|
|
155
|
-
send_method = partial(transport.handle_async_request)
|
|
156
|
-
response = await self._retry_operation_async(request, send_method)
|
|
157
|
-
else:
|
|
158
|
-
response = await transport.handle_async_request(request)
|
|
159
|
-
return response
|
|
160
|
-
|
|
161
|
-
async def aclose(self) -> None:
|
|
162
|
-
"""
|
|
163
|
-
Closes the underlying HTTP transport, terminating all outstanding connections and rejecting any further
|
|
164
|
-
requests.
|
|
165
|
-
|
|
166
|
-
This should be called before the object is dereferenced, to ensure that connections are properly cleaned up.
|
|
167
|
-
"""
|
|
168
|
-
transport: httpx.AsyncBaseTransport = self._wrapped_transport # type: ignore
|
|
169
|
-
await transport.aclose()
|
|
170
88
|
|
|
171
|
-
|
|
172
|
-
"""
|
|
173
|
-
Closes the underlying HTTP transport, terminating all outstanding connections and rejecting any further
|
|
174
|
-
requests.
|
|
89
|
+
super().__init__(method, url, *args, **kwargs)
|
|
175
90
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
transport: httpx.BaseTransport = self._wrapped_transport # type: ignore
|
|
179
|
-
transport.close()
|
|
180
|
-
|
|
181
|
-
def _is_retryable_method(self, request: httpx.Request) -> bool:
|
|
182
|
-
return request.method in self._retryable_methods or request.extensions.get(
|
|
183
|
-
"retryable", False
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
def _should_retry(self, response: httpx.Response) -> bool:
|
|
187
|
-
return response.status_code in self._retry_status_codes
|
|
91
|
+
def _is_retryable(self) -> bool:
|
|
92
|
+
return self.method in self._retryable_methods
|
|
188
93
|
|
|
189
94
|
def _log_error(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
error: Exception | None,
|
|
95
|
+
self,
|
|
96
|
+
error: Exception | None,
|
|
193
97
|
) -> None:
|
|
194
|
-
if
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if isinstance(error, httpx.ConnectTimeout):
|
|
198
|
-
self._logger.error(
|
|
199
|
-
f"Request {request.method} {request.url} failed to connect: {str(error)}"
|
|
98
|
+
if isinstance(error, aiohttp.ServerConnectionError):
|
|
99
|
+
logger.error(
|
|
100
|
+
f"Request {self.method} {self.url} failed to connect: {str(error)}"
|
|
200
101
|
)
|
|
201
|
-
elif isinstance(error,
|
|
202
|
-
|
|
203
|
-
f"Request {
|
|
102
|
+
elif isinstance(error, aiohttp.ConnectionTimeoutError):
|
|
103
|
+
logger.error(
|
|
104
|
+
f"Request {self.method} {self.url} failed with a timeout exception: {str(error)}"
|
|
204
105
|
)
|
|
205
|
-
elif isinstance(error,
|
|
206
|
-
|
|
207
|
-
f"Request {
|
|
106
|
+
elif isinstance(error, aiohttp.ClientError):
|
|
107
|
+
logger.error(
|
|
108
|
+
f"Request {self.method} {self.url} failed with an HTTP error: {str(error)}"
|
|
208
109
|
)
|
|
209
110
|
|
|
210
|
-
def
|
|
211
|
-
self
|
|
212
|
-
request: httpx.Request,
|
|
213
|
-
sleep_time: float,
|
|
214
|
-
response: httpx.Response | None,
|
|
215
|
-
error: Exception | None,
|
|
216
|
-
) -> None:
|
|
217
|
-
if self._logger and response:
|
|
218
|
-
self._logger.warning(
|
|
219
|
-
f"Request {request.method} {request.url} failed with status code:"
|
|
220
|
-
f" {response.status_code}, retrying in {sleep_time} seconds." # noqa: F821
|
|
221
|
-
)
|
|
222
|
-
elif self._logger and error:
|
|
223
|
-
self._logger.warning(
|
|
224
|
-
f"Request {request.method} {request.url} failed with exception:"
|
|
225
|
-
f" {type(error).__name__} - {str(error) or 'No error message'}, retrying in {sleep_time} seconds."
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
async def _should_retry_async(self, response: httpx.Response) -> bool:
|
|
229
|
-
return response.status_code in self._retry_status_codes
|
|
111
|
+
async def _should_retry_async(self, response: ClientResponse) -> bool:
|
|
112
|
+
return response.status in self._retry_status_codes
|
|
230
113
|
|
|
231
114
|
def _calculate_sleep(
|
|
232
|
-
|
|
115
|
+
self, attempts_made: int, headers: Mapping[str, str]
|
|
233
116
|
) -> float:
|
|
234
117
|
# Retry-After
|
|
235
118
|
# The Retry-After response HTTP header indicates how long the user agent should wait before
|
|
@@ -260,91 +143,58 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
260
143
|
total_backoff = backoff + jitter
|
|
261
144
|
return min(total_backoff, self._max_backoff_wait)
|
|
262
145
|
|
|
146
|
+
def _log_before_retry(
|
|
147
|
+
self,
|
|
148
|
+
sleep_time: float,
|
|
149
|
+
response: ClientResponse | None,
|
|
150
|
+
error: Exception | None,
|
|
151
|
+
) -> None:
|
|
152
|
+
if response:
|
|
153
|
+
logger.warning(
|
|
154
|
+
f"Request {self.method} {self.url} failed with status code:"
|
|
155
|
+
f" {response.status}, retrying in {sleep_time} seconds." # noqa: F821
|
|
156
|
+
)
|
|
157
|
+
elif error:
|
|
158
|
+
logger.warning(
|
|
159
|
+
f"Request {self.method} {self.url} failed with exception:"
|
|
160
|
+
f" {type(error).__name__} - {str(error) or 'No error message'}, retrying in {sleep_time} seconds."
|
|
161
|
+
)
|
|
162
|
+
|
|
263
163
|
async def _retry_operation_async(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
send_method: Callable[..., Coroutine[Any, Any, httpx.Response]],
|
|
267
|
-
) -> httpx.Response:
|
|
164
|
+
self, request: Callable[..., Coroutine[Any, Any, ClientResponse]]
|
|
165
|
+
) -> ClientResponse:
|
|
268
166
|
remaining_attempts = self._max_attempts
|
|
269
167
|
attempts_made = 0
|
|
270
|
-
response:
|
|
168
|
+
response: ClientResponse | None = None
|
|
271
169
|
error: Exception | None = None
|
|
272
170
|
while True:
|
|
273
171
|
if attempts_made > 0:
|
|
274
|
-
sleep_time = self._calculate_sleep(attempts_made, {})
|
|
275
|
-
self._log_before_retry(
|
|
172
|
+
sleep_time = self._calculate_sleep(attempts_made, response.headers if response else {})
|
|
173
|
+
self._log_before_retry(sleep_time, response, error)
|
|
276
174
|
await asyncio.sleep(sleep_time)
|
|
277
175
|
|
|
278
176
|
error = None
|
|
279
177
|
response = None
|
|
280
178
|
try:
|
|
281
|
-
response = await
|
|
282
|
-
response.request = request
|
|
179
|
+
response = await request()
|
|
283
180
|
if remaining_attempts < 1 or not (
|
|
284
|
-
|
|
181
|
+
await self._should_retry_async(response)
|
|
285
182
|
):
|
|
286
183
|
return response
|
|
287
|
-
|
|
288
|
-
except httpx.ConnectTimeout as e:
|
|
184
|
+
except (aiohttp.ServerConnectionError, aiohttp.ConnectionTimeoutError, aiohttp.ClientError) as e:
|
|
289
185
|
error = e
|
|
290
186
|
if remaining_attempts < 1:
|
|
291
|
-
self._log_error(
|
|
292
|
-
raise
|
|
293
|
-
except httpx.ReadTimeout as e:
|
|
294
|
-
error = e
|
|
295
|
-
if remaining_attempts < 1:
|
|
296
|
-
self._log_error(request, error)
|
|
297
|
-
raise
|
|
298
|
-
except httpx.TimeoutException as e:
|
|
299
|
-
error = e
|
|
300
|
-
if remaining_attempts < 1:
|
|
301
|
-
self._log_error(request, error)
|
|
302
|
-
raise
|
|
303
|
-
except httpx.HTTPError as e:
|
|
304
|
-
error = e
|
|
305
|
-
if remaining_attempts < 1:
|
|
306
|
-
self._log_error(request, error)
|
|
187
|
+
self._log_error(error)
|
|
307
188
|
raise
|
|
308
189
|
attempts_made += 1
|
|
309
190
|
remaining_attempts -= 1
|
|
310
191
|
|
|
311
|
-
def
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
while True:
|
|
321
|
-
if attempts_made > 0:
|
|
322
|
-
sleep_time = self._calculate_sleep(attempts_made, {})
|
|
323
|
-
self._log_before_retry(request, sleep_time, response, error)
|
|
324
|
-
time.sleep(sleep_time)
|
|
325
|
-
|
|
326
|
-
error = None
|
|
327
|
-
response = None
|
|
328
|
-
try:
|
|
329
|
-
response = send_method(request)
|
|
330
|
-
response.request = request
|
|
331
|
-
if remaining_attempts < 1 or not self._should_retry(response):
|
|
332
|
-
return response
|
|
333
|
-
response.close()
|
|
334
|
-
except httpx.ConnectTimeout as e:
|
|
335
|
-
error = e
|
|
336
|
-
if remaining_attempts < 1:
|
|
337
|
-
self._log_error(request, error)
|
|
338
|
-
raise
|
|
339
|
-
except httpx.TimeoutException as e:
|
|
340
|
-
error = e
|
|
341
|
-
if remaining_attempts < 1:
|
|
342
|
-
self._log_error(request, error)
|
|
343
|
-
raise
|
|
344
|
-
except httpx.HTTPError as e:
|
|
345
|
-
error = e
|
|
346
|
-
if remaining_attempts < 1:
|
|
347
|
-
self._log_error(request, error)
|
|
348
|
-
raise
|
|
349
|
-
attempts_made += 1
|
|
350
|
-
remaining_attempts -= 1
|
|
192
|
+
async def send(self, conn: "Connection") -> "ClientResponse":
|
|
193
|
+
request = functools.partial(
|
|
194
|
+
super().send, conn
|
|
195
|
+
)
|
|
196
|
+
if self._is_retryable():
|
|
197
|
+
response = await self._retry_operation_async(request)
|
|
198
|
+
else:
|
|
199
|
+
response = await request()
|
|
200
|
+
return response
|
port_ocean/log/logger_setup.py
CHANGED
|
@@ -15,8 +15,8 @@ from port_ocean.utils.signal import signal_handler
|
|
|
15
15
|
def setup_logger(level: LogLevelType, enable_http_handler: bool) -> None:
|
|
16
16
|
logger.remove()
|
|
17
17
|
_stdout_loguru_handler(level)
|
|
18
|
-
if enable_http_handler:
|
|
19
|
-
|
|
18
|
+
# if enable_http_handler:
|
|
19
|
+
# _http_loguru_handler(level)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _stdout_loguru_handler(level: LogLevelType) -> None:
|