port-ocean 0.4.3__py3-none-any.whl → 0.4.4__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/clients/port/authentication.py +5 -5
- port_ocean/clients/port/client.py +2 -5
- port_ocean/clients/port/mixins/blueprints.py +1 -7
- port_ocean/clients/port/mixins/entities.py +1 -7
- port_ocean/clients/port/mixins/integrations.py +1 -5
- port_ocean/clients/port/retry_transport.py +51 -0
- port_ocean/clients/port/utils.py +19 -47
- port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +10 -2
- port_ocean/helpers/retry.py +10 -8
- port_ocean/utils.py +33 -1
- {port_ocean-0.4.3.dist-info → port_ocean-0.4.4.dist-info}/METADATA +1 -1
- {port_ocean-0.4.3.dist-info → port_ocean-0.4.4.dist-info}/RECORD +15 -14
- {port_ocean-0.4.3.dist-info → port_ocean-0.4.4.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.4.3.dist-info → port_ocean-0.4.4.dist-info}/WHEEL +0 -0
- {port_ocean-0.4.3.dist-info → port_ocean-0.4.4.dist-info}/entry_points.txt +0 -0
|
@@ -42,7 +42,7 @@ class PortAuthentication:
|
|
|
42
42
|
self.integration_identifier = integration_identifier
|
|
43
43
|
self.integration_type = integration_type
|
|
44
44
|
self.integration_version = integration_version
|
|
45
|
-
self.
|
|
45
|
+
self.last_token_object: TokenResponse | None = None
|
|
46
46
|
|
|
47
47
|
async def _get_token(self, client_id: str, client_secret: str) -> TokenResponse:
|
|
48
48
|
logger.info(f"Fetching access token for clientId: {client_id}")
|
|
@@ -71,13 +71,13 @@ class PortAuthentication:
|
|
|
71
71
|
|
|
72
72
|
@property
|
|
73
73
|
async def token(self) -> str:
|
|
74
|
-
if not self.
|
|
74
|
+
if not self.last_token_object or self.last_token_object.expired:
|
|
75
75
|
msg = "Token expired, fetching new token"
|
|
76
|
-
if not self.
|
|
76
|
+
if not self.last_token_object:
|
|
77
77
|
msg = "No token found, fetching new token"
|
|
78
78
|
logger.info(msg)
|
|
79
|
-
self.
|
|
79
|
+
self.last_token_object = await self._get_token(
|
|
80
80
|
self.client_id, self.client_secret
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
return self.
|
|
83
|
+
return self.last_token_object.full_token
|
|
@@ -10,8 +10,7 @@ from port_ocean.clients.port.types import (
|
|
|
10
10
|
)
|
|
11
11
|
from port_ocean.clients.port.utils import (
|
|
12
12
|
handle_status_code,
|
|
13
|
-
|
|
14
|
-
retry_on_http_status,
|
|
13
|
+
get_internal_http_client,
|
|
15
14
|
)
|
|
16
15
|
from port_ocean.exceptions.clients import KafkaCredentialsNotFound
|
|
17
16
|
|
|
@@ -32,7 +31,7 @@ class PortClient(
|
|
|
32
31
|
integration_version: str,
|
|
33
32
|
):
|
|
34
33
|
self.api_url = f"{base_url}/v1"
|
|
35
|
-
self.client =
|
|
34
|
+
self.client = get_internal_http_client(self)
|
|
36
35
|
self.auth = PortAuthentication(
|
|
37
36
|
self.client,
|
|
38
37
|
client_id,
|
|
@@ -49,7 +48,6 @@ class PortClient(
|
|
|
49
48
|
BlueprintClientMixin.__init__(self, self.auth, self.client)
|
|
50
49
|
MigrationClientMixin.__init__(self, self.auth, self.client)
|
|
51
50
|
|
|
52
|
-
@retry_on_http_status(status_code=401)
|
|
53
51
|
async def get_kafka_creds(self) -> KafkaCreds:
|
|
54
52
|
logger.info("Fetching organization kafka credentials")
|
|
55
53
|
response = await self.client.get(
|
|
@@ -66,7 +64,6 @@ class PortClient(
|
|
|
66
64
|
|
|
67
65
|
return credentials
|
|
68
66
|
|
|
69
|
-
@retry_on_http_status(status_code=401)
|
|
70
67
|
async def get_org_id(self) -> str:
|
|
71
68
|
logger.info("Fetching organization id")
|
|
72
69
|
|
|
@@ -5,7 +5,7 @@ from loguru import logger
|
|
|
5
5
|
|
|
6
6
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
7
7
|
from port_ocean.clients.port.types import UserAgentType
|
|
8
|
-
from port_ocean.clients.port.utils import handle_status_code
|
|
8
|
+
from port_ocean.clients.port.utils import handle_status_code
|
|
9
9
|
from port_ocean.core.models import Blueprint
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,7 +14,6 @@ class BlueprintClientMixin:
|
|
|
14
14
|
self.auth = auth
|
|
15
15
|
self.client = client
|
|
16
16
|
|
|
17
|
-
@retry_on_http_status(status_code=401)
|
|
18
17
|
async def get_blueprint(self, identifier: str) -> Blueprint:
|
|
19
18
|
logger.info(f"Fetching blueprint with id: {identifier}")
|
|
20
19
|
response = await self.client.get(
|
|
@@ -24,7 +23,6 @@ class BlueprintClientMixin:
|
|
|
24
23
|
handle_status_code(response)
|
|
25
24
|
return Blueprint.parse_obj(response.json()["blueprint"])
|
|
26
25
|
|
|
27
|
-
@retry_on_http_status(status_code=401)
|
|
28
26
|
async def create_blueprint(
|
|
29
27
|
self,
|
|
30
28
|
raw_blueprint: dict[str, Any],
|
|
@@ -39,7 +37,6 @@ class BlueprintClientMixin:
|
|
|
39
37
|
if response.is_success:
|
|
40
38
|
return response.json()["blueprint"]
|
|
41
39
|
|
|
42
|
-
@retry_on_http_status(status_code=401)
|
|
43
40
|
async def patch_blueprint(
|
|
44
41
|
self,
|
|
45
42
|
identifier: str,
|
|
@@ -55,7 +52,6 @@ class BlueprintClientMixin:
|
|
|
55
52
|
)
|
|
56
53
|
handle_status_code(response)
|
|
57
54
|
|
|
58
|
-
@retry_on_http_status(status_code=401)
|
|
59
55
|
async def delete_blueprint(
|
|
60
56
|
self,
|
|
61
57
|
identifier: str,
|
|
@@ -85,7 +81,6 @@ class BlueprintClientMixin:
|
|
|
85
81
|
handle_status_code(response, should_raise)
|
|
86
82
|
return response.json().get("migrationId", "")
|
|
87
83
|
|
|
88
|
-
@retry_on_http_status(status_code=401)
|
|
89
84
|
async def create_action(
|
|
90
85
|
self, blueprint_identifier: str, action: dict[str, Any]
|
|
91
86
|
) -> None:
|
|
@@ -98,7 +93,6 @@ class BlueprintClientMixin:
|
|
|
98
93
|
|
|
99
94
|
handle_status_code(response)
|
|
100
95
|
|
|
101
|
-
@retry_on_http_status(status_code=401)
|
|
102
96
|
async def create_scorecard(
|
|
103
97
|
self,
|
|
104
98
|
blueprint_identifier: str,
|
|
@@ -5,7 +5,7 @@ from loguru import logger
|
|
|
5
5
|
|
|
6
6
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
7
7
|
from port_ocean.clients.port.types import RequestOptions, UserAgentType
|
|
8
|
-
from port_ocean.clients.port.utils import handle_status_code
|
|
8
|
+
from port_ocean.clients.port.utils import handle_status_code
|
|
9
9
|
from port_ocean.core.models import Entity
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,7 +14,6 @@ class EntityClientMixin:
|
|
|
14
14
|
self.auth = auth
|
|
15
15
|
self.client = client
|
|
16
16
|
|
|
17
|
-
@retry_on_http_status(status_code=401)
|
|
18
17
|
async def upsert_entity(
|
|
19
18
|
self,
|
|
20
19
|
entity: Entity,
|
|
@@ -50,7 +49,6 @@ class EntityClientMixin:
|
|
|
50
49
|
)
|
|
51
50
|
handle_status_code(response, should_raise)
|
|
52
51
|
|
|
53
|
-
@retry_on_http_status(status_code=401)
|
|
54
52
|
async def delete_entity(
|
|
55
53
|
self,
|
|
56
54
|
entity: Entity,
|
|
@@ -80,7 +78,6 @@ class EntityClientMixin:
|
|
|
80
78
|
|
|
81
79
|
handle_status_code(response, should_raise)
|
|
82
80
|
|
|
83
|
-
@retry_on_http_status(status_code=401)
|
|
84
81
|
async def validate_entity_exist(self, identifier: str, blueprint: str) -> None:
|
|
85
82
|
logger.info(f"Validating entity {identifier} of blueprint {blueprint} exists")
|
|
86
83
|
|
|
@@ -96,7 +93,6 @@ class EntityClientMixin:
|
|
|
96
93
|
)
|
|
97
94
|
handle_status_code(response)
|
|
98
95
|
|
|
99
|
-
@retry_on_http_status(status_code=401)
|
|
100
96
|
async def search_entities(self, user_agent_type: UserAgentType) -> list[Entity]:
|
|
101
97
|
query = {
|
|
102
98
|
"combinator": "and",
|
|
@@ -122,7 +118,6 @@ class EntityClientMixin:
|
|
|
122
118
|
handle_status_code(response)
|
|
123
119
|
return [Entity.parse_obj(result) for result in response.json()["entities"]]
|
|
124
120
|
|
|
125
|
-
@retry_on_http_status(status_code=401)
|
|
126
121
|
async def search_dependent_entities(self, entity: Entity) -> list[Entity]:
|
|
127
122
|
body = {
|
|
128
123
|
"combinator": "and",
|
|
@@ -146,7 +141,6 @@ class EntityClientMixin:
|
|
|
146
141
|
|
|
147
142
|
return [Entity.parse_obj(result) for result in response.json()["entities"]]
|
|
148
143
|
|
|
149
|
-
@retry_on_http_status(status_code=401)
|
|
150
144
|
async def validate_entity_payload(
|
|
151
145
|
self, entity: Entity, options: RequestOptions
|
|
152
146
|
) -> None:
|
|
@@ -5,7 +5,7 @@ from loguru import logger
|
|
|
5
5
|
from starlette import status
|
|
6
6
|
|
|
7
7
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
8
|
-
from port_ocean.clients.port.utils import handle_status_code
|
|
8
|
+
from port_ocean.clients.port.utils import handle_status_code
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from port_ocean.core.handlers.port_app_config.models import PortAppConfig
|
|
@@ -32,7 +32,6 @@ class IntegrationClientMixin:
|
|
|
32
32
|
)
|
|
33
33
|
return response
|
|
34
34
|
|
|
35
|
-
@retry_on_http_status(status_code=401)
|
|
36
35
|
async def get_current_integration(
|
|
37
36
|
self, should_raise: bool = True, should_log: bool = True
|
|
38
37
|
) -> dict[str, Any]:
|
|
@@ -40,7 +39,6 @@ class IntegrationClientMixin:
|
|
|
40
39
|
handle_status_code(response, should_raise, should_log)
|
|
41
40
|
return response.json()["integration"]
|
|
42
41
|
|
|
43
|
-
@retry_on_http_status(status_code=401)
|
|
44
42
|
async def create_integration(
|
|
45
43
|
self,
|
|
46
44
|
_type: str,
|
|
@@ -63,7 +61,6 @@ class IntegrationClientMixin:
|
|
|
63
61
|
)
|
|
64
62
|
handle_status_code(response)
|
|
65
63
|
|
|
66
|
-
@retry_on_http_status(status_code=401)
|
|
67
64
|
async def patch_integration(
|
|
68
65
|
self,
|
|
69
66
|
_type: str | None = None,
|
|
@@ -88,7 +85,6 @@ class IntegrationClientMixin:
|
|
|
88
85
|
)
|
|
89
86
|
handle_status_code(response)
|
|
90
87
|
|
|
91
|
-
@retry_on_http_status(status_code=401)
|
|
92
88
|
async def initialize_integration(
|
|
93
89
|
self,
|
|
94
90
|
_type: str,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from port_ocean.helpers.retry import RetryTransport
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from port_ocean.clients.port.client import PortClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TokenRetryTransport(RetryTransport):
|
|
14
|
+
def __init__(self, port_client: "PortClient", *args: Any, **kwargs: Any) -> None:
|
|
15
|
+
super().__init__(*args, **kwargs)
|
|
16
|
+
self.port_client = port_client
|
|
17
|
+
|
|
18
|
+
async def _handle_unauthorized(self, response: httpx.Response) -> None:
|
|
19
|
+
token = await self.port_client.auth.token
|
|
20
|
+
response.headers["Authorization"] = f"Bearer {token}"
|
|
21
|
+
|
|
22
|
+
def is_token_error(self, response: httpx.Response) -> bool:
|
|
23
|
+
return (
|
|
24
|
+
response.status_code == HTTPStatus.UNAUTHORIZED
|
|
25
|
+
and "/auth/access_token" not in str(response.request.url)
|
|
26
|
+
and self.port_client.auth.last_token_object is not None
|
|
27
|
+
and self.port_client.auth.last_token_object.expired
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
async def _should_retry_async(self, response: httpx.Response) -> bool:
|
|
31
|
+
if self.is_token_error(response):
|
|
32
|
+
if self._logger:
|
|
33
|
+
self._logger.info(
|
|
34
|
+
"Got unauthorized response, trying to refresh token before retrying"
|
|
35
|
+
)
|
|
36
|
+
await self._handle_unauthorized(response)
|
|
37
|
+
return True
|
|
38
|
+
return await super()._should_retry_async(response)
|
|
39
|
+
|
|
40
|
+
def _should_retry(self, response: httpx.Response) -> bool:
|
|
41
|
+
if self.is_token_error(response):
|
|
42
|
+
if self._logger:
|
|
43
|
+
self._logger.info(
|
|
44
|
+
"Got unauthorized response, trying to refresh token before retrying"
|
|
45
|
+
)
|
|
46
|
+
asyncio.get_running_loop().run_until_complete(
|
|
47
|
+
self._handle_unauthorized(response)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return True
|
|
51
|
+
return super()._should_retry(response)
|
port_ocean/clients/port/utils.py
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import Callable, Any
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
4
3
|
import httpx
|
|
5
4
|
from loguru import logger
|
|
6
5
|
from werkzeug.local import LocalStack, LocalProxy
|
|
7
6
|
|
|
8
|
-
from port_ocean.
|
|
7
|
+
from port_ocean.clients.port.retry_transport import TokenRetryTransport
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from port_ocean.clients.port.client import PortClient
|
|
9
11
|
|
|
10
12
|
_http_client: LocalStack[httpx.AsyncClient] = LocalStack()
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def _get_http_client_context() -> httpx.AsyncClient:
|
|
15
|
+
def _get_http_client_context(port_client: "PortClient") -> httpx.AsyncClient:
|
|
14
16
|
client = _http_client.top
|
|
15
17
|
if client is None:
|
|
16
18
|
client = httpx.AsyncClient(
|
|
17
|
-
transport=
|
|
19
|
+
transport=TokenRetryTransport(
|
|
20
|
+
port_client,
|
|
18
21
|
httpx.AsyncHTTPTransport(),
|
|
19
22
|
logger=logger,
|
|
20
23
|
)
|
|
@@ -24,7 +27,17 @@ def _get_http_client_context() -> httpx.AsyncClient:
|
|
|
24
27
|
return client
|
|
25
28
|
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
_port_internal_async_client: httpx.AsyncClient = None # type: ignore
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_internal_http_client(port_client: "PortClient") -> httpx.AsyncClient:
|
|
34
|
+
global _port_internal_async_client
|
|
35
|
+
if _port_internal_async_client is None:
|
|
36
|
+
_port_internal_async_client = LocalProxy(
|
|
37
|
+
lambda: _get_http_client_context(port_client)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return _port_internal_async_client
|
|
28
41
|
|
|
29
42
|
|
|
30
43
|
def handle_status_code(
|
|
@@ -36,44 +49,3 @@ def handle_status_code(
|
|
|
36
49
|
)
|
|
37
50
|
if should_raise:
|
|
38
51
|
response.raise_for_status()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def retry_on_http_status(
|
|
42
|
-
status_code: int,
|
|
43
|
-
max_retries: int = 2,
|
|
44
|
-
verbose: bool = True,
|
|
45
|
-
) -> Callable[..., Callable[..., Any]]:
|
|
46
|
-
"""
|
|
47
|
-
Decorator to retry a function if it raises a httpx.HTTPStatusError with a given status code
|
|
48
|
-
:param status_code: The status code to retry on
|
|
49
|
-
:param max_retries: The maximum number of retries
|
|
50
|
-
:param verbose: Whether to log retries
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
54
|
-
@wraps(func)
|
|
55
|
-
async def wrapper(*args, **kwargs) -> Any: # type: ignore
|
|
56
|
-
retries = 0
|
|
57
|
-
while retries < max_retries:
|
|
58
|
-
try:
|
|
59
|
-
result = await func(*args, **kwargs)
|
|
60
|
-
return result
|
|
61
|
-
except httpx.HTTPStatusError as err:
|
|
62
|
-
if err.response.status_code == status_code:
|
|
63
|
-
retries += 1
|
|
64
|
-
if retries < max_retries:
|
|
65
|
-
if verbose:
|
|
66
|
-
logger.warning(
|
|
67
|
-
f"Retrying {func.__name__} after {status_code} error. Retry {retries}/{max_retries}"
|
|
68
|
-
)
|
|
69
|
-
else:
|
|
70
|
-
logger.error(
|
|
71
|
-
f"Reached max retries {max_retries} for {func.__name__} after {status_code} error"
|
|
72
|
-
)
|
|
73
|
-
raise
|
|
74
|
-
else:
|
|
75
|
-
raise
|
|
76
|
-
|
|
77
|
-
return wrapper
|
|
78
|
-
|
|
79
|
-
return decorator
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from graphlib import TopologicalSorter
|
|
1
|
+
from graphlib import TopologicalSorter, CycleError
|
|
2
2
|
from typing import Set
|
|
3
3
|
|
|
4
4
|
from port_ocean.core.models import Entity
|
|
5
|
+
from port_ocean.exceptions.core import OceanAbortException
|
|
5
6
|
|
|
6
7
|
Node = tuple[str, str]
|
|
7
8
|
|
|
@@ -35,4 +36,11 @@ def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
|
|
|
35
36
|
nodes[node(entity)].add(node(related_entity))
|
|
36
37
|
|
|
37
38
|
sort_op = TopologicalSorter(nodes)
|
|
38
|
-
|
|
39
|
+
try:
|
|
40
|
+
return [entities_map[item] for item in sort_op.static_order()]
|
|
41
|
+
except CycleError as ex:
|
|
42
|
+
raise OceanAbortException(
|
|
43
|
+
"Cannot order entities due to cyclic dependencies. \n"
|
|
44
|
+
"If you do want to have cyclic dependencies, please make sure to set the keys"
|
|
45
|
+
" 'createMissingRelatedEntities' and 'deleteDependentEntities' in the integration config in Port."
|
|
46
|
+
) from ex
|
port_ocean/helpers/retry.py
CHANGED
|
@@ -178,6 +178,12 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
178
178
|
transport: httpx.BaseTransport = self._wrapped_transport # type: ignore
|
|
179
179
|
transport.close()
|
|
180
180
|
|
|
181
|
+
def _should_retry(self, response: httpx.Response) -> bool:
|
|
182
|
+
return response.status_code in self._retry_status_codes
|
|
183
|
+
|
|
184
|
+
async def _should_retry_async(self, response: httpx.Response) -> bool:
|
|
185
|
+
return response.status_code in self._retry_status_codes
|
|
186
|
+
|
|
181
187
|
def _calculate_sleep(
|
|
182
188
|
self, attempts_made: int, headers: Union[httpx.Headers, Mapping[str, str]]
|
|
183
189
|
) -> float:
|
|
@@ -228,10 +234,8 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
228
234
|
)
|
|
229
235
|
await asyncio.sleep(sleep_time)
|
|
230
236
|
response = await send_method(request)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
or response.status_code not in self._retry_status_codes
|
|
234
|
-
):
|
|
237
|
+
response.request = request
|
|
238
|
+
if remaining_attempts < 1 or not (await self._should_retry_async(response)):
|
|
235
239
|
return response
|
|
236
240
|
await response.aclose()
|
|
237
241
|
attempts_made += 1
|
|
@@ -255,10 +259,8 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
|
255
259
|
)
|
|
256
260
|
time.sleep(sleep_time)
|
|
257
261
|
response = send_method(request)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
or response.status_code not in self._retry_status_codes
|
|
261
|
-
):
|
|
262
|
+
response.request = request
|
|
263
|
+
if remaining_attempts < 1 or not self._should_retry(response):
|
|
262
264
|
return response
|
|
263
265
|
response.close()
|
|
264
266
|
attempts_made += 1
|
port_ocean/utils.py
CHANGED
|
@@ -3,17 +3,49 @@ import inspect
|
|
|
3
3
|
from asyncio import ensure_future
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from importlib.util import module_from_spec, spec_from_file_location
|
|
6
|
-
from pathlib import Path
|
|
7
6
|
from time import time
|
|
8
7
|
from traceback import format_exception
|
|
9
8
|
from types import ModuleType
|
|
10
9
|
from typing import Callable, Any, Coroutine
|
|
11
10
|
from uuid import uuid4
|
|
12
11
|
|
|
12
|
+
import httpx
|
|
13
13
|
import tomli
|
|
14
14
|
import yaml
|
|
15
15
|
from loguru import logger
|
|
16
|
+
from pathlib import Path
|
|
16
17
|
from starlette.concurrency import run_in_threadpool
|
|
18
|
+
from werkzeug.local import LocalStack, LocalProxy
|
|
19
|
+
|
|
20
|
+
from port_ocean.helpers.retry import RetryTransport
|
|
21
|
+
|
|
22
|
+
_http_client: LocalStack[httpx.AsyncClient] = LocalStack()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_http_client_context() -> httpx.AsyncClient:
|
|
26
|
+
client = _http_client.top
|
|
27
|
+
if client is None:
|
|
28
|
+
client = httpx.AsyncClient(
|
|
29
|
+
transport=RetryTransport(
|
|
30
|
+
httpx.AsyncHTTPTransport(),
|
|
31
|
+
logger=logger,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
_http_client.push(client)
|
|
35
|
+
|
|
36
|
+
return client
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
Utilize this client for all outbound integration requests to the third-party application. It functions as a wrapper
|
|
41
|
+
around the httpx.AsyncClient, incorporating retry logic at the transport layer for handling retries on 5xx errors and
|
|
42
|
+
connection errors.
|
|
43
|
+
|
|
44
|
+
The client is instantiated lazily, only coming into existence upon its initial access. It should not be closed when in
|
|
45
|
+
use, as it operates as a singleton shared across all events in the thread. It also takes care of recreating the client
|
|
46
|
+
in scenarios such as the creation of a new event loop, such as when initiating a new thread.
|
|
47
|
+
"""
|
|
48
|
+
http_async_client: httpx.AsyncClient = LocalProxy(lambda: _get_http_client_context()) # type: ignore
|
|
17
49
|
|
|
18
50
|
|
|
19
51
|
def get_time(seconds_precision: bool = True) -> float:
|
|
@@ -34,15 +34,16 @@ port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py,
|
|
|
34
34
|
port_ocean/cli/utils.py,sha256=IUK2UbWqjci-lrcDdynZXqVP5B5TcjF0w5CpEVUks-k,54
|
|
35
35
|
port_ocean/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
port_ocean/clients/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
port_ocean/clients/port/authentication.py,sha256=
|
|
38
|
-
port_ocean/clients/port/client.py,sha256=
|
|
37
|
+
port_ocean/clients/port/authentication.py,sha256=DXqZQaYUb2mhWRZXoTivteEyQPTsHtJdcIEByA7sVsM,2859
|
|
38
|
+
port_ocean/clients/port/client.py,sha256=3GYCM0ZkX3pB6sNoOb-7_6dm0Jr5_vqhflD9iltf_As,2640
|
|
39
39
|
port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
port_ocean/clients/port/mixins/blueprints.py,sha256=
|
|
41
|
-
port_ocean/clients/port/mixins/entities.py,sha256=
|
|
42
|
-
port_ocean/clients/port/mixins/integrations.py,sha256=
|
|
40
|
+
port_ocean/clients/port/mixins/blueprints.py,sha256=vp7kVzr704fCcp6Cvuwv4LR1GeM_Jh6FpaycLe9mv20,3794
|
|
41
|
+
port_ocean/clients/port/mixins/entities.py,sha256=1cHBawvB24Cm_85OwP9pbqljnKiUkYrSOawtH1n3_Sg,5620
|
|
42
|
+
port_ocean/clients/port/mixins/integrations.py,sha256=FuDoQfQnfJvhK-nKpftQ8k7VsB9odNRlkMkGCmxwaWY,4280
|
|
43
43
|
port_ocean/clients/port/mixins/migrations.py,sha256=M93i_aryfDazRHjkpTS3-sV3UshLCxmsv6yAIOziFl8,1463
|
|
44
|
+
port_ocean/clients/port/retry_transport.py,sha256=zLJFZpjTr663-ttRxRM4WQ0KTGbFqwOccYZnmxc8Kww,1880
|
|
44
45
|
port_ocean/clients/port/types.py,sha256=NnT1W2H_tZ_9cyEFoDhb90q3apf8p9emkHYspbPoZJQ,593
|
|
45
|
-
port_ocean/clients/port/utils.py,sha256=
|
|
46
|
+
port_ocean/clients/port/utils.py,sha256=KMQqaWnvRgtbuEdT2oI9h4ZBIo02BmI-dVNo8x_INTk,1462
|
|
46
47
|
port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
48
|
port_ocean/config/base.py,sha256=e1oW66ynjo7Sp2PcLBp9aCzj59JpTaiKUXVJK8VB8TA,5540
|
|
48
49
|
port_ocean/config/dynamic.py,sha256=CIRDnqFUPSnNMLZ-emRCMVAjEQNBlIujhZ7OGwi_aKs,1816
|
|
@@ -74,7 +75,7 @@ port_ocean/core/handlers/entities_state_applier/base.py,sha256=FMsrBOVgaO4o7B1kl
|
|
|
74
75
|
port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
76
|
port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=mtvdHn3wlfs9hVFeHjvAOyzZe4YQQr9sehNxQz229_Y,8270
|
|
76
77
|
port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=1zncwCbE-Gej0xaWKlzZgoXxOBe9bgs_YxlZ8QW3NdI,1751
|
|
77
|
-
port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=
|
|
78
|
+
port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=82BvU8t5w9uhsxX8hbnwuRPuWhW3cMeuT_5sVIkip1I,1550
|
|
78
79
|
port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py,sha256=nKuQ-RlalGG07olxm6l5NHeOuQT9dEZLoMpD-AN5nq0,1392
|
|
79
80
|
port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
|
|
80
81
|
port_ocean/core/handlers/entity_processor/base.py,sha256=3ucPc-esMI5M6gFl_l2j_ncEqOIe7m1bCH4rAXtGDzg,1710
|
|
@@ -102,16 +103,16 @@ port_ocean/exceptions/context.py,sha256=qC1OLppiv6XZuXSHRO2P_VPFSzrc_uUQNvfSYLeA
|
|
|
102
103
|
port_ocean/exceptions/core.py,sha256=ygxtPQ9IG8NzIrzZok5OqkefVrqcC4bjZ-2Vf9IPZuA,790
|
|
103
104
|
port_ocean/exceptions/port_defaults.py,sha256=R3ufJcfllb7NZSwHOpBs8kbjsIZVQLM6vKO6dz4w-EE,407
|
|
104
105
|
port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
-
port_ocean/helpers/retry.py,sha256=
|
|
106
|
+
port_ocean/helpers/retry.py,sha256=U19OwT-yQecysZj7SISTBMLz2HlRDlsGYocZqgnGdDo,12167
|
|
106
107
|
port_ocean/logger_setup.py,sha256=dOA58ZBAfe9dcAoBybXFWgDfsJG-uIX1ABI0r2CyUG8,1160
|
|
107
108
|
port_ocean/middlewares.py,sha256=8rGu9XSKvbNCQGzWvfaijDrp-0ATJrWAQfBji2CnSck,2475
|
|
108
109
|
port_ocean/ocean.py,sha256=9dFBf46N6fht6ibQUjnDDizY-p0qa0Uw-415JTDQmus,3626
|
|
109
110
|
port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
111
|
port_ocean/run.py,sha256=xvYZlfi-J-IcqsAg8tNVnvl1mEUr6wPSya_-Bbf6jAU,1811
|
|
111
|
-
port_ocean/utils.py,sha256=
|
|
112
|
+
port_ocean/utils.py,sha256=ZQCdOk1DzAA3hwUxFAzmbxQurnNYGpl1bDsPocJSkko,6129
|
|
112
113
|
port_ocean/version.py,sha256=2ugCk8TXPsRIuFviZ8j3RPaszSw2HE-KuaW8vhgWJVM,172
|
|
113
|
-
port_ocean-0.4.
|
|
114
|
-
port_ocean-0.4.
|
|
115
|
-
port_ocean-0.4.
|
|
116
|
-
port_ocean-0.4.
|
|
117
|
-
port_ocean-0.4.
|
|
114
|
+
port_ocean-0.4.4.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
115
|
+
port_ocean-0.4.4.dist-info/METADATA,sha256=yK9Pg-oN24yqjX-KSTAsARrGzk3f4zeUxU8VK3k6iSI,6490
|
|
116
|
+
port_ocean-0.4.4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
117
|
+
port_ocean-0.4.4.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
118
|
+
port_ocean-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|