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.

Files changed (30) hide show
  1. port_ocean/cli/commands/list_integrations.py +8 -5
  2. port_ocean/cli/commands/pull.py +20 -16
  3. port_ocean/clients/port/authentication.py +12 -13
  4. port_ocean/clients/port/client.py +9 -8
  5. port_ocean/clients/port/mixins/blueprints.py +14 -14
  6. port_ocean/clients/port/mixins/entities.py +7 -8
  7. port_ocean/clients/port/mixins/integrations.py +11 -11
  8. port_ocean/clients/port/mixins/migrations.py +5 -5
  9. port_ocean/clients/port/retry_transport.py +13 -35
  10. port_ocean/clients/port/utils.py +32 -23
  11. port_ocean/core/defaults/clean.py +3 -3
  12. port_ocean/core/defaults/common.py +3 -3
  13. port_ocean/core/defaults/initialize.py +48 -47
  14. port_ocean/core/integrations/base.py +3 -2
  15. port_ocean/core/integrations/mixins/sync_raw.py +3 -3
  16. port_ocean/core/utils.py +4 -3
  17. port_ocean/helpers/retry.py +71 -221
  18. port_ocean/log/logger_setup.py +2 -2
  19. port_ocean/ocean.py +18 -24
  20. port_ocean/tests/clients/port/mixins/test_entities.py +3 -4
  21. port_ocean/tests/test_smoke.py +3 -3
  22. port_ocean/utils/async_http.py +13 -8
  23. port_ocean/utils/repeat.py +9 -2
  24. port_ocean/utils/signal.py +1 -2
  25. {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/METADATA +3 -1
  26. {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/RECORD +29 -30
  27. {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/WHEEL +1 -1
  28. port_ocean/helpers/async_client.py +0 -53
  29. {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/LICENSE.md +0 -0
  30. {port_ocean-0.12.2.dev10.dist-info → port_ocean-0.12.2.dev12.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
- import httpx
2
+ import asyncio
3
+
4
+ import aiohttp
3
5
 
4
6
  from port_ocean.cli.commands.main import cli_start, console
5
7
 
@@ -9,16 +11,17 @@ def list_git_folders(owner: str, repo_name: str, path: str) -> list[str]:
9
11
  api_url = f"https://api.github.com/repos/{owner}/{repo_name}/contents/{path}"
10
12
 
11
13
  # Send a GET request to the API
12
- response = httpx.get(api_url)
14
+ response = asyncio.run(aiohttp.ClientSession().get(api_url))
13
15
 
14
16
  # Check if the request was successful
15
- if response.is_error:
17
+ if not response.ok:
18
+ text = asyncio.run(response.text())
16
19
  console.print(
17
- f"[bold red]Failed to list folders.[/bold red] Status Code: {response.status_code}, Error: {response.text}"
20
+ f"[bold red]Failed to list folders.[/bold red] Status Code: {response.status}, Error: {text}"
18
21
  )
19
22
  exit(1)
20
23
 
21
- contents = response.json()
24
+ contents = await response.json()
22
25
  folders = [item["name"] for item in contents if item["type"] == "dir"]
23
26
  return folders
24
27
 
@@ -1,26 +1,26 @@
1
+ import asyncio
1
2
  import os
2
- import shutil
3
- from io import BytesIO
4
3
 
4
+ import aiohttp
5
5
  import click
6
- import httpx
7
6
 
8
7
  from port_ocean.cli.commands.main import cli_start, console
9
8
 
10
9
 
11
10
  def download_github_folder(
12
- owner: str, repo_name: str, folder_path: str, destination_path: str
11
+ owner: str, repo_name: str, folder_path: str, destination_path: str
13
12
  ) -> None:
14
13
  # Construct the API URL to get the contents of the folder
15
14
  api_url = f"https://api.github.com/repos/{owner}/{repo_name}/contents/{folder_path}"
16
15
 
17
16
  # Send a GET request to the API
18
- response = httpx.get(api_url)
17
+ response = asyncio.run(aiohttp.ClientSession().request("GET", api_url))
19
18
 
20
19
  # Check if the request was successful
21
20
  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.status_code}, Error: {response.text}"
23
+ f"[bold red]Failed to download the folder `{folder_path}`.[/bold red] Status Code: {response.status}, Error: {text}"
24
24
  )
25
25
  exit(1)
26
26
 
@@ -28,23 +28,27 @@ 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
+
31
43
  # Iterate over the files and download them
32
- repo_contents = response.json()
44
+ repo_contents = await response.json()
33
45
  for content in repo_contents:
34
46
  if content["type"] == "file":
35
47
  file_url = content["download_url"]
36
48
  file_name = os.path.join(destination_path, content["name"])
37
49
 
38
50
  # Download the file
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)
51
+ asyncio.run(read_async())
48
52
 
49
53
  console.print(f"Folder `{folder_path}` downloaded successfully!")
50
54
 
@@ -1,6 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- import httpx
3
+ from aiohttp import ClientSession
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
- 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,
29
+ self,
30
+ client: ClientSession,
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,10 +51,9 @@ 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},
55
54
  )
56
- handle_status_code(response)
57
- return TokenResponse(**response.json())
55
+ await handle_status_code(response)
56
+ return TokenResponse(**(await response.json()))
58
57
 
59
58
  def user_agent(self, user_agent_type: UserAgentType | None = None) -> str:
60
59
  user_agent = f"port-ocean/{self.integration_type}/{self.integration_version}/{self.integration_identifier}"
@@ -64,7 +63,7 @@ class PortAuthentication:
64
63
  return user_agent
65
64
 
66
65
  async def headers(
67
- self, user_agent_type: UserAgentType | None = None
66
+ self, user_agent_type: UserAgentType | None = None
68
67
  ) -> dict[Any, Any]:
69
68
  return {
70
69
  "Authorization": await self.token,
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  from loguru import logger
2
4
 
3
5
  from port_ocean.clients.port.authentication import PortAuthentication
@@ -13,7 +15,6 @@ from port_ocean.clients.port.utils import (
13
15
  get_internal_http_client,
14
16
  )
15
17
  from port_ocean.exceptions.clients import KafkaCredentialsNotFound
16
- from typing import Any
17
18
 
18
19
 
19
20
  class PortClient(
@@ -56,9 +57,9 @@ class PortClient(
56
57
  )
57
58
  if response.is_error:
58
59
  logger.error("Error getting kafka credentials")
59
- handle_status_code(response)
60
+ await handle_status_code(response)
60
61
 
61
- credentials = response.json().get("credentials")
62
+ credentials = (await response.json()).get("credentials")
62
63
 
63
64
  if credentials is None:
64
65
  raise KafkaCredentialsNotFound("No kafka credentials found")
@@ -72,10 +73,10 @@ class PortClient(
72
73
  f"{self.api_url}/organization", headers=await self.auth.headers()
73
74
  )
74
75
  if response.is_error:
75
- logger.error(f"Error getting organization id, error: {response.text}")
76
- handle_status_code(response)
76
+ logger.error(f"Error getting organization id, error: {await response.text()}")
77
+ await handle_status_code(response)
77
78
 
78
- return response.json()["organization"]["id"]
79
+ return (await response.json())["organization"]["id"]
79
80
 
80
81
  async def update_integration_state(
81
82
  self, state: dict[str, Any], should_raise: bool = True, should_log: bool = True
@@ -87,8 +88,8 @@ class PortClient(
87
88
  headers=await self.auth.headers(),
88
89
  json=state,
89
90
  )
90
- handle_status_code(response, should_raise, should_log)
91
+ await handle_status_code(response, should_raise, should_log)
91
92
  if response.is_success and should_log:
92
93
  logger.info("Integration resync state updated successfully")
93
94
 
94
- return response.json().get("integration", {})
95
+ return (await response.json()).get("integration", {})
@@ -1,6 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- import httpx
3
+ import aiohttp
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: httpx.AsyncClient):
13
+ def __init__(self, auth: PortAuthentication, client: aiohttp.ClientSession):
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
- handle_status_code(response, should_log=should_log)
26
- return Blueprint.parse_obj(response.json()["blueprint"])
25
+ await handle_status_code(response, should_log=should_log)
26
+ return Blueprint.parse_obj((await 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
- handle_status_code(response)
39
- return response.json()["blueprint"]
38
+ await handle_status_code(response)
39
+ return (await 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
- handle_status_code(response)
54
+ await 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
- handle_status_code(response, should_raise)
73
+ await 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
- handle_status_code(response, should_raise)
82
- return response.json().get("migrationId", "")
81
+ await handle_status_code(response, should_raise)
82
+ return (await 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
- handle_status_code(response, should_log=should_log)
94
+ await 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
- handle_status_code(response, should_log=should_log)
109
+ await 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
- handle_status_code(response, should_log=should_log)
121
+ await 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
- handle_status_code(response, should_raise)
135
+ await 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 httpx
5
+ import aiohttp
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: httpx.AsyncClient):
18
+ def __init__(self, auth: PortAuthentication, client: aiohttp.ClientSession):
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
- handle_status_code(response, should_raise)
60
- result = response.json()
59
+ await handle_status_code(response, should_raise)
60
+ result = await 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
- handle_status_code(response, should_raise)
152
+ await handle_status_code(response, should_raise)
153
153
 
154
154
  async def batch_delete_entities(
155
155
  self,
@@ -204,11 +204,10 @@ class EntityClientMixin:
204
204
  "exclude_calculated_properties": "true",
205
205
  "include": ["blueprint", "identifier"],
206
206
  },
207
- extensions={"retryable": True},
208
207
  timeout=30,
209
208
  )
210
- handle_status_code(response)
211
- return [Entity.parse_obj(result) for result in response.json()["entities"]]
209
+ await handle_status_code(response)
210
+ return [Entity.parse_obj(result) for result in (await response.json())["entities"]]
212
211
 
213
212
  async def search_batch_entities(
214
213
  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 httpx
4
+ import aiohttp
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: httpx.AsyncClient,
25
+ client: aiohttp.ClientSession,
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) -> httpx.Response:
33
+ async def _get_current_integration(self) -> aiohttp.ClientResponse:
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
- handle_status_code(response, should_raise, should_log)
46
- return response.json().get("integration", {})
45
+ await handle_status_code(response, should_raise, should_log)
46
+ return (await 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
- handle_status_code(response)
75
- return response.json()["integration"]
74
+ await handle_status_code(response)
75
+ return (await 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
- handle_status_code(response)
100
- return response.json()["integration"]
99
+ await handle_status_code(response)
100
+ return (await 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
- handle_status_code(response, should_log=False)
113
+ await 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
- handle_status_code(response, should_log=should_log)
128
+ await 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 httpx
3
+ import aiohttp
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: httpx.AsyncClient):
12
+ def __init__(self, auth: PortAuthentication, client: aiohttp.ClientSession):
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
- handle_status_code(response, should_raise=True)
31
+ await handle_status_code(response, should_raise=True)
32
32
 
33
- migration_status = response.json().get("migration", {}).get("status", None)
33
+ migration_status = (await 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(response.json()["migration"])
46
+ return Migration.parse_obj(await response.json()["migration"])
@@ -1,51 +1,29 @@
1
- import asyncio
2
1
  from http import HTTPStatus
3
- from typing import TYPE_CHECKING, Any
4
2
 
5
- import httpx
3
+ from aiohttp import ClientResponse
6
4
 
7
- from port_ocean.helpers.retry import RetryTransport
5
+ from port_ocean.helpers.retry import RetryRequestClass
8
6
 
9
- if TYPE_CHECKING:
10
- from port_ocean.clients.port.client import PortClient
11
7
 
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
- 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:
13
+ def is_token_error(self, response: ClientResponse) -> bool:
23
14
  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
15
+ response.status == HTTPStatus.UNAUTHORIZED
16
+ and "/auth/access_token" not in str(self.url)
17
+ and self._session.port_client.auth.last_token_object is not None
18
+ and self._session.port_client.auth.last_token_object.expired
28
19
  )
29
20
 
30
- async def _should_retry_async(self, response: httpx.Response) -> bool:
21
+ async def _should_retry_async(self, response: ClientResponse) -> bool:
31
22
  if self.is_token_error(response):
32
23
  if self._logger:
33
24
  self._logger.info(
34
25
  "Got unauthorized response, trying to refresh token before retrying"
35
26
  )
36
- await self._handle_unauthorized(response)
27
+ await self._handle_unauthorized()
37
28
  return True
38
29
  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)
@@ -1,11 +1,12 @@
1
+ from asyncio import ensure_future
1
2
  from typing import TYPE_CHECKING
2
3
 
3
- import httpx
4
+ import aiohttp
4
5
  from loguru import logger
5
6
  from werkzeug.local import LocalStack, LocalProxy
6
7
 
7
- from port_ocean.clients.port.retry_transport import TokenRetryTransport
8
- from port_ocean.helpers.async_client import OceanAsyncClient
8
+ from port_ocean.helpers.retry import RetryRequestClass
9
+ from port_ocean.utils.signal import signal_handler
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from port_ocean.clients.port.client import PortClient
@@ -20,33 +21,41 @@ PORT_HTTP_MAX_CONNECTIONS_LIMIT = 200
20
21
  PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS = 50
21
22
  PORT_HTTP_TIMEOUT = 60.0
22
23
 
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
- )
24
+ TIMEOUT = aiohttp.ClientTimeout(total=PORT_HTTP_TIMEOUT)
28
25
 
29
- _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
26
+ _http_client: LocalStack[aiohttp.ClientSession] = LocalStack()
30
27
 
31
28
 
32
- def _get_http_client_context(port_client: "PortClient") -> httpx.AsyncClient:
29
+ class PortRetryRequestClass(RetryRequestClass):
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:
33
44
  client = _http_client.top
34
45
  if client is None:
35
- client = OceanAsyncClient(
36
- TokenRetryTransport,
37
- transport_kwargs={"port_client": port_client},
38
- timeout=PORT_HTTPX_TIMEOUT,
39
- limits=PORT_HTTPX_LIMITS,
40
- )
46
+ AIOHTTP_CONNECTOR = aiohttp.TCPConnector(limit=PORT_HTTP_MAX_CONNECTIONS_LIMIT, force_close=True)
47
+ client = OceanPortAsyncClient(port_client, timeout=TIMEOUT, request_class=PortRetryRequestClass,
48
+ connector=AIOHTTP_CONNECTOR,
49
+ trust_env=True)
41
50
  _http_client.push(client)
42
-
51
+ signal_handler.register(lambda: ensure_future(client.close()))
43
52
  return client
44
53
 
45
54
 
46
- _port_internal_async_client: httpx.AsyncClient = None # type: ignore
55
+ _port_internal_async_client: aiohttp.ClientSession = None # type: ignore
47
56
 
48
57
 
49
- def get_internal_http_client(port_client: "PortClient") -> httpx.AsyncClient:
58
+ def get_internal_http_client(port_client: "PortClient") -> aiohttp.ClientSession:
50
59
  global _port_internal_async_client
51
60
  if _port_internal_async_client is None:
52
61
  _port_internal_async_client = LocalProxy(
@@ -56,12 +65,12 @@ def get_internal_http_client(port_client: "PortClient") -> httpx.AsyncClient:
56
65
  return _port_internal_async_client
57
66
 
58
67
 
59
- def handle_status_code(
60
- response: httpx.Response, should_raise: bool = True, should_log: bool = True
68
+ async def handle_status_code(
69
+ response: aiohttp.ClientResponse, should_raise: bool = True, should_log: bool = True
61
70
  ) -> None:
62
- if should_log and response.is_error:
71
+ if should_log and not response.ok:
63
72
  logger.error(
64
- f"Request failed with status code: {response.status_code}, Error: {response.text}"
73
+ f"Request failed with status code: {response.status}, Error: {await response.text()}"
65
74
  )
66
75
  if should_raise:
67
76
  response.raise_for_status()
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  from typing import Type
3
3
 
4
- import httpx
4
+ import aiohttp
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 httpx.HTTPStatusError as e:
70
- logger.error(f"Failed to delete blueprints: {e.response.text}.")
69
+ except aiohttp.ClientResponseError as e:
70
+ logger.error(f"Failed to delete blueprints: {e.message}.")
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 httpx
5
+ import aiohttp
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 httpx.HTTPStatusError as e:
43
- if e.response.status_code != status.HTTP_404_NOT_FOUND:
42
+ except aiohttp.ClientResponseError as e:
43
+ if e.status != status.HTTP_404_NOT_FOUND:
44
44
  raise e
45
45
 
46
46
  return False