port-ocean 0.12.2.dev14__py3-none-any.whl → 0.12.2.dev16__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 +5 -8
- port_ocean/cli/commands/pull.py +16 -20
- port_ocean/clients/port/authentication.py +13 -12
- port_ocean/clients/port/client.py +8 -9
- port_ocean/clients/port/mixins/blueprints.py +14 -14
- port_ocean/clients/port/mixins/entities.py +8 -7
- 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 +35 -13
- port_ocean/clients/port/utils.py +23 -32
- port_ocean/core/defaults/clean.py +3 -3
- port_ocean/core/defaults/common.py +3 -3
- port_ocean/core/defaults/initialize.py +47 -48
- port_ocean/core/integrations/mixins/sync_raw.py +3 -3
- port_ocean/core/utils.py +3 -4
- port_ocean/helpers/async_client.py +53 -0
- port_ocean/helpers/retry.py +221 -71
- port_ocean/ocean.py +22 -20
- port_ocean/run.py +15 -20
- port_ocean/tests/clients/port/mixins/test_entities.py +4 -3
- port_ocean/tests/test_smoke.py +3 -3
- port_ocean/utils/async_http.py +8 -13
- port_ocean/utils/repeat.py +2 -6
- port_ocean/utils/signal.py +2 -1
- {port_ocean-0.12.2.dev14.dist-info → port_ocean-0.12.2.dev16.dist-info}/METADATA +1 -2
- {port_ocean-0.12.2.dev14.dist-info → port_ocean-0.12.2.dev16.dist-info}/RECORD +29 -28
- {port_ocean-0.12.2.dev14.dist-info → port_ocean-0.12.2.dev16.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.12.2.dev14.dist-info → port_ocean-0.12.2.dev16.dist-info}/WHEEL +0 -0
- {port_ocean-0.12.2.dev14.dist-info → port_ocean-0.12.2.dev16.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import aiohttp
|
|
2
|
+
import httpx
|
|
5
3
|
|
|
6
4
|
from port_ocean.cli.commands.main import cli_start, console
|
|
7
5
|
|
|
@@ -11,17 +9,16 @@ def list_git_folders(owner: str, repo_name: str, path: str) -> list[str]:
|
|
|
11
9
|
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/contents/{path}"
|
|
12
10
|
|
|
13
11
|
# Send a GET request to the API
|
|
14
|
-
response =
|
|
12
|
+
response = httpx.get(api_url)
|
|
15
13
|
|
|
16
14
|
# Check if the request was successful
|
|
17
|
-
if
|
|
18
|
-
text = asyncio.run(response.text())
|
|
15
|
+
if response.is_error:
|
|
19
16
|
console.print(
|
|
20
|
-
f"[bold red]Failed to list folders.[/bold red] Status Code: {response.
|
|
17
|
+
f"[bold red]Failed to list folders.[/bold red] Status Code: {response.status_code}, Error: {response.text}"
|
|
21
18
|
)
|
|
22
19
|
exit(1)
|
|
23
20
|
|
|
24
|
-
contents =
|
|
21
|
+
contents = response.json()
|
|
25
22
|
folders = [item["name"] for item in contents if item["type"] == "dir"]
|
|
26
23
|
return folders
|
|
27
24
|
|
port_ocean/cli/commands/pull.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from io import BytesIO
|
|
3
4
|
|
|
4
|
-
import aiohttp
|
|
5
5
|
import click
|
|
6
|
+
import httpx
|
|
6
7
|
|
|
7
8
|
from port_ocean.cli.commands.main import cli_start, console
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def download_github_folder(
|
|
11
|
-
|
|
12
|
+
owner: str, repo_name: str, folder_path: str, destination_path: str
|
|
12
13
|
) -> None:
|
|
13
14
|
# Construct the API URL to get the contents of the folder
|
|
14
15
|
api_url = f"https://api.github.com/repos/{owner}/{repo_name}/contents/{folder_path}"
|
|
15
16
|
|
|
16
17
|
# Send a GET request to the API
|
|
17
|
-
response =
|
|
18
|
+
response = httpx.get(api_url)
|
|
18
19
|
|
|
19
20
|
# Check if the request was successful
|
|
20
21
|
if response.is_error:
|
|
21
|
-
text = asyncio.run(response.text())
|
|
22
22
|
console.print(
|
|
23
|
-
f"[bold red]Failed to download the folder `{folder_path}`.[/bold red] Status Code: {response.
|
|
23
|
+
f"[bold red]Failed to download the folder `{folder_path}`.[/bold red] Status Code: {response.status_code}, Error: {response.text}"
|
|
24
24
|
)
|
|
25
25
|
exit(1)
|
|
26
26
|
|
|
@@ -28,27 +28,23 @@ def download_github_folder(
|
|
|
28
28
|
if not os.path.exists(destination_path):
|
|
29
29
|
os.makedirs(destination_path)
|
|
30
30
|
|
|
31
|
-
async def read_async():
|
|
32
|
-
async with aiohttp.ClientSession().request("GET", file_url) as file_response:
|
|
33
|
-
if file_response.status == 200:
|
|
34
|
-
with open(file_name, "wb") as file:
|
|
35
|
-
file.write(await file_response.read())
|
|
36
|
-
else:
|
|
37
|
-
text = asyncio.run(file_response.text())
|
|
38
|
-
console.print(
|
|
39
|
-
f"[bold red]Failed to download file `{content['name']}`.[/bold red] Status code: {file_response.status}, Error: {text}"
|
|
40
|
-
)
|
|
41
|
-
exit(1)
|
|
42
|
-
|
|
43
31
|
# Iterate over the files and download them
|
|
44
|
-
repo_contents =
|
|
32
|
+
repo_contents = response.json()
|
|
45
33
|
for content in repo_contents:
|
|
46
34
|
if content["type"] == "file":
|
|
47
35
|
file_url = content["download_url"]
|
|
48
36
|
file_name = os.path.join(destination_path, content["name"])
|
|
49
37
|
|
|
50
38
|
# Download the file
|
|
51
|
-
|
|
39
|
+
with httpx.stream("GET", file_url) as file_response:
|
|
40
|
+
if file_response.status_code == 200:
|
|
41
|
+
with open(file_name, "wb") as file:
|
|
42
|
+
shutil.copyfileobj(BytesIO(file_response.content), file)
|
|
43
|
+
else:
|
|
44
|
+
console.print(
|
|
45
|
+
f"[bold red]Failed to download file `{content['name']}`.[/bold red] Status code: {file_response.status_code}, Error: {file_response.text}"
|
|
46
|
+
)
|
|
47
|
+
exit(1)
|
|
52
48
|
|
|
53
49
|
console.print(f"Folder `{folder_path}` downloaded successfully!")
|
|
54
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import httpx
|
|
4
4
|
from loguru import logger
|
|
5
5
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
6
6
|
|
|
@@ -26,14 +26,14 @@ class TokenResponse(BaseModel):
|
|
|
26
26
|
|
|
27
27
|
class PortAuthentication:
|
|
28
28
|
def __init__(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
self,
|
|
30
|
+
client: httpx.AsyncClient,
|
|
31
|
+
client_id: str,
|
|
32
|
+
client_secret: str,
|
|
33
|
+
api_url: str,
|
|
34
|
+
integration_identifier: str,
|
|
35
|
+
integration_type: str,
|
|
36
|
+
integration_version: str,
|
|
37
37
|
):
|
|
38
38
|
self.client = client
|
|
39
39
|
self.api_url = api_url
|
|
@@ -51,9 +51,10 @@ class PortAuthentication:
|
|
|
51
51
|
response = await self.client.post(
|
|
52
52
|
f"{self.api_url}/auth/access_token",
|
|
53
53
|
json=credentials,
|
|
54
|
+
extensions={"retryable": True},
|
|
54
55
|
)
|
|
55
|
-
|
|
56
|
-
return TokenResponse(**
|
|
56
|
+
handle_status_code(response)
|
|
57
|
+
return TokenResponse(**response.json())
|
|
57
58
|
|
|
58
59
|
def user_agent(self, user_agent_type: UserAgentType | None = None) -> str:
|
|
59
60
|
user_agent = f"port-ocean/{self.integration_type}/{self.integration_version}/{self.integration_identifier}"
|
|
@@ -63,7 +64,7 @@ class PortAuthentication:
|
|
|
63
64
|
return user_agent
|
|
64
65
|
|
|
65
66
|
async def headers(
|
|
66
|
-
|
|
67
|
+
self, user_agent_type: UserAgentType | None = None
|
|
67
68
|
) -> dict[Any, Any]:
|
|
68
69
|
return {
|
|
69
70
|
"Authorization": await self.token,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
1
|
from loguru import logger
|
|
4
2
|
|
|
5
3
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
@@ -15,6 +13,7 @@ from port_ocean.clients.port.utils import (
|
|
|
15
13
|
get_internal_http_client,
|
|
16
14
|
)
|
|
17
15
|
from port_ocean.exceptions.clients import KafkaCredentialsNotFound
|
|
16
|
+
from typing import Any
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class PortClient(
|
|
@@ -57,9 +56,9 @@ class PortClient(
|
|
|
57
56
|
)
|
|
58
57
|
if response.is_error:
|
|
59
58
|
logger.error("Error getting kafka credentials")
|
|
60
|
-
|
|
59
|
+
handle_status_code(response)
|
|
61
60
|
|
|
62
|
-
credentials =
|
|
61
|
+
credentials = response.json().get("credentials")
|
|
63
62
|
|
|
64
63
|
if credentials is None:
|
|
65
64
|
raise KafkaCredentialsNotFound("No kafka credentials found")
|
|
@@ -73,10 +72,10 @@ class PortClient(
|
|
|
73
72
|
f"{self.api_url}/organization", headers=await self.auth.headers()
|
|
74
73
|
)
|
|
75
74
|
if response.is_error:
|
|
76
|
-
logger.error(f"Error getting organization id, error: {
|
|
77
|
-
|
|
75
|
+
logger.error(f"Error getting organization id, error: {response.text}")
|
|
76
|
+
handle_status_code(response)
|
|
78
77
|
|
|
79
|
-
return
|
|
78
|
+
return response.json()["organization"]["id"]
|
|
80
79
|
|
|
81
80
|
async def update_integration_state(
|
|
82
81
|
self, state: dict[str, Any], should_raise: bool = True, should_log: bool = True
|
|
@@ -88,8 +87,8 @@ class PortClient(
|
|
|
88
87
|
headers=await self.auth.headers(),
|
|
89
88
|
json=state,
|
|
90
89
|
)
|
|
91
|
-
|
|
90
|
+
handle_status_code(response, should_raise, should_log)
|
|
92
91
|
if response.is_success and should_log:
|
|
93
92
|
logger.info("Integration resync state updated successfully")
|
|
94
93
|
|
|
95
|
-
return
|
|
94
|
+
return response.json().get("integration", {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import httpx
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
@@ -10,7 +10,7 @@ from port_ocean.core.models import Blueprint
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class BlueprintClientMixin:
|
|
13
|
-
def __init__(self, auth: PortAuthentication, client:
|
|
13
|
+
def __init__(self, auth: PortAuthentication, client: httpx.AsyncClient):
|
|
14
14
|
self.auth = auth
|
|
15
15
|
self.client = client
|
|
16
16
|
|
|
@@ -22,8 +22,8 @@ class BlueprintClientMixin:
|
|
|
22
22
|
f"{self.auth.api_url}/blueprints/{identifier}",
|
|
23
23
|
headers=await self.auth.headers(),
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
return Blueprint.parse_obj(
|
|
25
|
+
handle_status_code(response, should_log=should_log)
|
|
26
|
+
return Blueprint.parse_obj(response.json()["blueprint"])
|
|
27
27
|
|
|
28
28
|
async def create_blueprint(
|
|
29
29
|
self,
|
|
@@ -35,8 +35,8 @@ class BlueprintClientMixin:
|
|
|
35
35
|
response = await self.client.post(
|
|
36
36
|
f"{self.auth.api_url}/blueprints", headers=headers, json=raw_blueprint
|
|
37
37
|
)
|
|
38
|
-
|
|
39
|
-
return
|
|
38
|
+
handle_status_code(response)
|
|
39
|
+
return response.json()["blueprint"]
|
|
40
40
|
|
|
41
41
|
async def patch_blueprint(
|
|
42
42
|
self,
|
|
@@ -51,7 +51,7 @@ class BlueprintClientMixin:
|
|
|
51
51
|
headers=headers,
|
|
52
52
|
json=raw_blueprint,
|
|
53
53
|
)
|
|
54
|
-
|
|
54
|
+
handle_status_code(response)
|
|
55
55
|
|
|
56
56
|
async def delete_blueprint(
|
|
57
57
|
self,
|
|
@@ -70,7 +70,7 @@ class BlueprintClientMixin:
|
|
|
70
70
|
f"{self.auth.api_url}/blueprints/{identifier}",
|
|
71
71
|
headers=headers,
|
|
72
72
|
)
|
|
73
|
-
|
|
73
|
+
handle_status_code(response, should_raise)
|
|
74
74
|
return None
|
|
75
75
|
else:
|
|
76
76
|
response = await self.client.delete(
|
|
@@ -78,8 +78,8 @@ class BlueprintClientMixin:
|
|
|
78
78
|
headers=await self.auth.headers(),
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
return
|
|
81
|
+
handle_status_code(response, should_raise)
|
|
82
|
+
return response.json().get("migrationId", "")
|
|
83
83
|
|
|
84
84
|
async def create_action(
|
|
85
85
|
self, action: dict[str, Any], should_log: bool = True
|
|
@@ -91,7 +91,7 @@ class BlueprintClientMixin:
|
|
|
91
91
|
headers=await self.auth.headers(),
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
handle_status_code(response, should_log=should_log)
|
|
95
95
|
|
|
96
96
|
async def create_scorecard(
|
|
97
97
|
self,
|
|
@@ -106,7 +106,7 @@ class BlueprintClientMixin:
|
|
|
106
106
|
headers=await self.auth.headers(),
|
|
107
107
|
)
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
handle_status_code(response, should_log=should_log)
|
|
110
110
|
|
|
111
111
|
async def create_page(
|
|
112
112
|
self, page: dict[str, Any], should_log: bool = True
|
|
@@ -118,7 +118,7 @@ class BlueprintClientMixin:
|
|
|
118
118
|
headers=await self.auth.headers(),
|
|
119
119
|
)
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
handle_status_code(response, should_log=should_log)
|
|
122
122
|
return page
|
|
123
123
|
|
|
124
124
|
async def delete_page(
|
|
@@ -132,4 +132,4 @@ class BlueprintClientMixin:
|
|
|
132
132
|
headers=await self.auth.headers(),
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
handle_status_code(response, should_raise)
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
from urllib.parse import quote_plus
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import httpx
|
|
6
6
|
from loguru import logger
|
|
7
7
|
|
|
8
8
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
@@ -15,7 +15,7 @@ from port_ocean.core.models import Entity
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class EntityClientMixin:
|
|
18
|
-
def __init__(self, auth: PortAuthentication, client:
|
|
18
|
+
def __init__(self, auth: PortAuthentication, client: httpx.AsyncClient):
|
|
19
19
|
self.auth = auth
|
|
20
20
|
self.client = client
|
|
21
21
|
# Semaphore is used to limit the number of concurrent requests to port, to avoid overloading it.
|
|
@@ -56,8 +56,8 @@ class EntityClientMixin:
|
|
|
56
56
|
f"entity: {entity.identifier} of "
|
|
57
57
|
f"blueprint: {entity.blueprint}"
|
|
58
58
|
)
|
|
59
|
-
|
|
60
|
-
result =
|
|
59
|
+
handle_status_code(response, should_raise)
|
|
60
|
+
result = response.json()
|
|
61
61
|
|
|
62
62
|
result_entity = (
|
|
63
63
|
Entity.parse_obj(result["entity"]) if result.get("entity") else entity
|
|
@@ -149,7 +149,7 @@ class EntityClientMixin:
|
|
|
149
149
|
f"blueprint: {entity.blueprint}"
|
|
150
150
|
)
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
handle_status_code(response, should_raise)
|
|
153
153
|
|
|
154
154
|
async def batch_delete_entities(
|
|
155
155
|
self,
|
|
@@ -204,10 +204,11 @@ class EntityClientMixin:
|
|
|
204
204
|
"exclude_calculated_properties": "true",
|
|
205
205
|
"include": ["blueprint", "identifier"],
|
|
206
206
|
},
|
|
207
|
+
extensions={"retryable": True},
|
|
207
208
|
timeout=30,
|
|
208
209
|
)
|
|
209
|
-
|
|
210
|
-
return [Entity.parse_obj(result) for result in
|
|
210
|
+
handle_status_code(response)
|
|
211
|
+
return [Entity.parse_obj(result) for result in response.json()["entities"]]
|
|
211
212
|
|
|
212
213
|
async def search_batch_entities(
|
|
213
214
|
self, user_agent_type: UserAgentType, entities_to_search: list[Entity]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Any, TYPE_CHECKING, Optional, TypedDict
|
|
2
2
|
from urllib.parse import quote_plus
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import httpx
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
7
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
@@ -22,7 +22,7 @@ class IntegrationClientMixin:
|
|
|
22
22
|
integration_identifier: str,
|
|
23
23
|
integration_version: str,
|
|
24
24
|
auth: PortAuthentication,
|
|
25
|
-
client:
|
|
25
|
+
client: httpx.AsyncClient,
|
|
26
26
|
):
|
|
27
27
|
self.integration_identifier = integration_identifier
|
|
28
28
|
self.integration_version = integration_version
|
|
@@ -30,7 +30,7 @@ class IntegrationClientMixin:
|
|
|
30
30
|
self.client = client
|
|
31
31
|
self._log_attributes: LogAttributes | None = None
|
|
32
32
|
|
|
33
|
-
async def _get_current_integration(self) ->
|
|
33
|
+
async def _get_current_integration(self) -> httpx.Response:
|
|
34
34
|
logger.info(f"Fetching integration with id: {self.integration_identifier}")
|
|
35
35
|
response = await self.client.get(
|
|
36
36
|
f"{self.auth.api_url}/integration/{self.integration_identifier}",
|
|
@@ -42,8 +42,8 @@ class IntegrationClientMixin:
|
|
|
42
42
|
self, should_raise: bool = True, should_log: bool = True
|
|
43
43
|
) -> dict[str, Any]:
|
|
44
44
|
response = await self._get_current_integration()
|
|
45
|
-
|
|
46
|
-
return
|
|
45
|
+
handle_status_code(response, should_raise, should_log)
|
|
46
|
+
return response.json().get("integration", {})
|
|
47
47
|
|
|
48
48
|
async def get_log_attributes(self) -> LogAttributes:
|
|
49
49
|
if self._log_attributes is None:
|
|
@@ -71,8 +71,8 @@ class IntegrationClientMixin:
|
|
|
71
71
|
response = await self.client.post(
|
|
72
72
|
f"{self.auth.api_url}/integration", headers=headers, json=json
|
|
73
73
|
)
|
|
74
|
-
|
|
75
|
-
return
|
|
74
|
+
handle_status_code(response)
|
|
75
|
+
return response.json()["integration"]
|
|
76
76
|
|
|
77
77
|
async def patch_integration(
|
|
78
78
|
self,
|
|
@@ -96,8 +96,8 @@ class IntegrationClientMixin:
|
|
|
96
96
|
headers=headers,
|
|
97
97
|
json=json,
|
|
98
98
|
)
|
|
99
|
-
|
|
100
|
-
return
|
|
99
|
+
handle_status_code(response)
|
|
100
|
+
return response.json()["integration"]
|
|
101
101
|
|
|
102
102
|
async def ingest_integration_logs(self, logs: list[dict[str, Any]]) -> None:
|
|
103
103
|
logger.debug("Ingesting logs")
|
|
@@ -110,7 +110,7 @@ class IntegrationClientMixin:
|
|
|
110
110
|
"logs": logs,
|
|
111
111
|
},
|
|
112
112
|
)
|
|
113
|
-
|
|
113
|
+
handle_status_code(response, should_log=False)
|
|
114
114
|
logger.debug("Logs successfully ingested")
|
|
115
115
|
|
|
116
116
|
async def ingest_integration_kind_examples(
|
|
@@ -125,5 +125,5 @@ class IntegrationClientMixin:
|
|
|
125
125
|
"examples": sensitive_log_filter.mask_object(data, full_hide=True),
|
|
126
126
|
},
|
|
127
127
|
)
|
|
128
|
-
|
|
128
|
+
handle_status_code(response, should_log=should_log)
|
|
129
129
|
logger.debug(f"Examples for kind {kind} successfully ingested")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import httpx
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from port_ocean.clients.port.authentication import PortAuthentication
|
|
@@ -9,7 +9,7 @@ from port_ocean.core.models import Migration
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class MigrationClientMixin:
|
|
12
|
-
def __init__(self, auth: PortAuthentication, client:
|
|
12
|
+
def __init__(self, auth: PortAuthentication, client: httpx.AsyncClient):
|
|
13
13
|
self.auth = auth
|
|
14
14
|
self.client = client
|
|
15
15
|
|
|
@@ -28,9 +28,9 @@ class MigrationClientMixin:
|
|
|
28
28
|
headers=headers,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
handle_status_code(response, should_raise=True)
|
|
32
32
|
|
|
33
|
-
migration_status =
|
|
33
|
+
migration_status = response.json().get("migration", {}).get("status", None)
|
|
34
34
|
if (
|
|
35
35
|
migration_status == "RUNNING"
|
|
36
36
|
or migration_status == "INITIALIZING"
|
|
@@ -43,4 +43,4 @@ class MigrationClientMixin:
|
|
|
43
43
|
f"Migration with id: {migration_id} finished with status {migration_status}",
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
return Migration.parse_obj(
|
|
46
|
+
return Migration.parse_obj(response.json()["migration"])
|
|
@@ -1,29 +1,51 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from http import HTTPStatus
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
import httpx
|
|
4
6
|
|
|
5
|
-
from port_ocean.helpers.retry import
|
|
7
|
+
from port_ocean.helpers.retry import RetryTransport
|
|
6
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from port_ocean.clients.port.client import PortClient
|
|
7
11
|
|
|
8
|
-
class TokenRetryRequestClass(RetryRequestClass):
|
|
9
|
-
async def _handle_unauthorized(self) -> None:
|
|
10
|
-
token = await self._session.port_client.auth.token
|
|
11
|
-
self.headers["Authorization"] = f"Bearer {token}"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
class TokenRetryTransport(RetryTransport):
|
|
14
|
+
def __init__(self, port_client: "PortClient", **kwargs: Any) -> None:
|
|
15
|
+
super().__init__(**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:
|
|
14
23
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
19
28
|
)
|
|
20
29
|
|
|
21
|
-
async def _should_retry_async(self, response:
|
|
30
|
+
async def _should_retry_async(self, response: httpx.Response) -> bool:
|
|
22
31
|
if self.is_token_error(response):
|
|
23
32
|
if self._logger:
|
|
24
33
|
self._logger.info(
|
|
25
34
|
"Got unauthorized response, trying to refresh token before retrying"
|
|
26
35
|
)
|
|
27
|
-
await self._handle_unauthorized()
|
|
36
|
+
await self._handle_unauthorized(response)
|
|
28
37
|
return True
|
|
29
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,12 +1,11 @@
|
|
|
1
|
-
from asyncio import ensure_future
|
|
2
1
|
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
4
|
-
import
|
|
3
|
+
import httpx
|
|
5
4
|
from loguru import logger
|
|
6
5
|
from werkzeug.local import LocalStack, LocalProxy
|
|
7
6
|
|
|
8
|
-
from port_ocean.
|
|
9
|
-
from port_ocean.
|
|
7
|
+
from port_ocean.clients.port.retry_transport import TokenRetryTransport
|
|
8
|
+
from port_ocean.helpers.async_client import OceanAsyncClient
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
12
11
|
from port_ocean.clients.port.client import PortClient
|
|
@@ -21,41 +20,33 @@ PORT_HTTP_MAX_CONNECTIONS_LIMIT = 200
|
|
|
21
20
|
PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS = 50
|
|
22
21
|
PORT_HTTP_TIMEOUT = 60.0
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
PORT_HTTPX_TIMEOUT = httpx.Timeout(PORT_HTTP_TIMEOUT)
|
|
24
|
+
PORT_HTTPX_LIMITS = httpx.Limits(
|
|
25
|
+
max_connections=PORT_HTTP_MAX_CONNECTIONS_LIMIT,
|
|
26
|
+
max_keepalive_connections=PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS,
|
|
27
|
+
)
|
|
25
28
|
|
|
26
|
-
_http_client: LocalStack[
|
|
29
|
+
_http_client: LocalStack[httpx.AsyncClient] = LocalStack()
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
RETRYABLE_ROUTES = frozenset(["/auth/access_token", "/entities/search"])
|
|
31
|
-
|
|
32
|
-
def _is_retryable(self) -> bool:
|
|
33
|
-
return super()._is_retryable() or any([route in self.url.path for route in self.RETRYABLE_ROUTES])
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class OceanPortAsyncClient(aiohttp.ClientSession):
|
|
37
|
-
def __init__(self, port_client: "PortClient", *args,
|
|
38
|
-
**kwargs):
|
|
39
|
-
self._port_client = port_client
|
|
40
|
-
super().__init__(*args, **kwargs)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _get_http_client_context(port_client: "PortClient") -> aiohttp.ClientSession:
|
|
32
|
+
def _get_http_client_context(port_client: "PortClient") -> httpx.AsyncClient:
|
|
44
33
|
client = _http_client.top
|
|
45
34
|
if client is None:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
client = OceanAsyncClient(
|
|
36
|
+
TokenRetryTransport,
|
|
37
|
+
transport_kwargs={"port_client": port_client},
|
|
38
|
+
timeout=PORT_HTTPX_TIMEOUT,
|
|
39
|
+
limits=PORT_HTTPX_LIMITS,
|
|
40
|
+
)
|
|
50
41
|
_http_client.push(client)
|
|
51
|
-
|
|
42
|
+
|
|
52
43
|
return client
|
|
53
44
|
|
|
54
45
|
|
|
55
|
-
_port_internal_async_client:
|
|
46
|
+
_port_internal_async_client: httpx.AsyncClient = None # type: ignore
|
|
56
47
|
|
|
57
48
|
|
|
58
|
-
def get_internal_http_client(port_client: "PortClient") ->
|
|
49
|
+
def get_internal_http_client(port_client: "PortClient") -> httpx.AsyncClient:
|
|
59
50
|
global _port_internal_async_client
|
|
60
51
|
if _port_internal_async_client is None:
|
|
61
52
|
_port_internal_async_client = LocalProxy(
|
|
@@ -65,12 +56,12 @@ def get_internal_http_client(port_client: "PortClient") -> aiohttp.ClientSession
|
|
|
65
56
|
return _port_internal_async_client
|
|
66
57
|
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
def handle_status_code(
|
|
60
|
+
response: httpx.Response, should_raise: bool = True, should_log: bool = True
|
|
70
61
|
) -> None:
|
|
71
|
-
if should_log and
|
|
62
|
+
if should_log and response.is_error:
|
|
72
63
|
logger.error(
|
|
73
|
-
f"Request failed with status code: {response.
|
|
64
|
+
f"Request failed with status code: {response.status_code}, Error: {response.text}"
|
|
74
65
|
)
|
|
75
66
|
if should_raise:
|
|
76
67
|
response.raise_for_status()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Type
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import httpx
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
7
|
from port_ocean.context.ocean import ocean
|
|
@@ -66,6 +66,6 @@ async def _clean_defaults(
|
|
|
66
66
|
for migration_id in migration_ids
|
|
67
67
|
)
|
|
68
68
|
)
|
|
69
|
-
except
|
|
70
|
-
logger.error(f"Failed to delete blueprints: {e.
|
|
69
|
+
except httpx.HTTPStatusError as e:
|
|
70
|
+
logger.error(f"Failed to delete blueprints: {e.response.text}.")
|
|
71
71
|
raise e
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Type, Any, TypedDict, Optional
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import httpx
|
|
6
6
|
import yaml
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
from starlette import status
|
|
@@ -39,8 +39,8 @@ async def is_integration_exists(port_client: PortClient) -> bool:
|
|
|
39
39
|
try:
|
|
40
40
|
await port_client.get_current_integration(should_log=False)
|
|
41
41
|
return True
|
|
42
|
-
except
|
|
43
|
-
if e.
|
|
42
|
+
except httpx.HTTPStatusError as e:
|
|
43
|
+
if e.response.status_code != status.HTTP_404_NOT_FOUND:
|
|
44
44
|
raise e
|
|
45
45
|
|
|
46
46
|
return False
|