port-ocean 0.4.3__tar.gz → 0.4.4__tar.gz

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 (118) hide show
  1. {port_ocean-0.4.3 → port_ocean-0.4.4}/PKG-INFO +1 -1
  2. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/authentication.py +5 -5
  3. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/client.py +2 -5
  4. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/mixins/blueprints.py +1 -7
  5. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/mixins/entities.py +1 -7
  6. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/mixins/integrations.py +1 -5
  7. port_ocean-0.4.4/port_ocean/clients/port/retry_transport.py +51 -0
  8. port_ocean-0.4.4/port_ocean/clients/port/utils.py +51 -0
  9. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +10 -2
  10. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/helpers/retry.py +10 -8
  11. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/utils.py +33 -1
  12. {port_ocean-0.4.3 → port_ocean-0.4.4}/pyproject.toml +1 -1
  13. port_ocean-0.4.3/port_ocean/clients/port/utils.py +0 -79
  14. {port_ocean-0.4.3 → port_ocean-0.4.4}/LICENSE.md +0 -0
  15. {port_ocean-0.4.3 → port_ocean-0.4.4}/README.md +0 -0
  16. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/__init__.py +0 -0
  17. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/bootstrap.py +0 -0
  18. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/__init__.py +0 -0
  19. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cli.py +0 -0
  20. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/__init__.py +0 -0
  21. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  22. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/defaults/clean.py +0 -0
  23. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/defaults/dock.py +0 -0
  24. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/defaults/group.py +0 -0
  25. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/list_integrations.py +0 -0
  26. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/main.py +0 -0
  27. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/new.py +0 -0
  28. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/pull.py +0 -0
  29. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/sail.py +0 -0
  30. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/commands/version.py +0 -0
  31. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  32. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  33. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  34. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  35. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  36. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  37. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  38. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  39. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  40. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -0
  41. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  42. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  43. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -0
  44. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  45. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  46. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  47. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  48. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  49. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/cli/utils.py +0 -0
  50. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/__init__.py +0 -0
  51. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/__init__.py +0 -0
  52. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/mixins/__init__.py +0 -0
  53. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/mixins/migrations.py +0 -0
  54. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/clients/port/types.py +0 -0
  55. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/config/__init__.py +0 -0
  56. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/config/base.py +0 -0
  57. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/config/dynamic.py +0 -0
  58. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/config/settings.py +0 -0
  59. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/consumers/__init__.py +0 -0
  60. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/consumers/base_consumer.py +0 -0
  61. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/consumers/kafka_consumer.py +0 -0
  62. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/context/__init__.py +0 -0
  63. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/context/event.py +0 -0
  64. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/context/ocean.py +0 -0
  65. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/context/resource.py +0 -0
  66. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/context/utils.py +0 -0
  67. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/__init__.py +0 -0
  68. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/defaults/__init__.py +0 -0
  69. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/defaults/clean.py +0 -0
  70. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/defaults/common.py +0 -0
  71. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/defaults/initialize.py +0 -0
  72. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/__init__.py +0 -0
  73. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/base.py +0 -0
  74. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/factory.py +0 -0
  75. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/http.py +0 -0
  76. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/kafka.py +0 -0
  77. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/once.py +0 -0
  78. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/event_listener/polling.py +0 -0
  79. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/__init__.py +0 -0
  80. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/base.py +0 -0
  81. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  82. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  83. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  84. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  85. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  86. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -0
  87. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  88. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  89. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  90. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  91. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  92. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  93. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  94. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/__init__.py +0 -0
  95. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/base.py +0 -0
  96. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  97. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/events.py +0 -0
  98. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/handler.py +0 -0
  99. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/sync.py +0 -0
  100. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
  101. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/integrations/mixins/utils.py +0 -0
  102. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/models.py +0 -0
  103. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/ocean_types.py +0 -0
  104. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/core/utils.py +0 -0
  105. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/__init__.py +0 -0
  106. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/api.py +0 -0
  107. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/base.py +0 -0
  108. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/clients.py +0 -0
  109. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/context.py +0 -0
  110. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/core.py +0 -0
  111. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/exceptions/port_defaults.py +0 -0
  112. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/helpers/__init__.py +0 -0
  113. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/logger_setup.py +0 -0
  114. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/middlewares.py +0 -0
  115. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/ocean.py +0 -0
  116. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/py.typed +0 -0
  117. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/run.py +0 -0
  118. {port_ocean-0.4.3 → port_ocean-0.4.4}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Port Ocean is a CLI tool for managing your Port projects.
5
5
  Home-page: https://app.getport.io
6
6
  Keywords: ocean,port-ocean,port
@@ -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._last_token_object: TokenResponse | None = None
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._last_token_object or self._last_token_object.expired:
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._last_token_object:
76
+ if not self.last_token_object:
77
77
  msg = "No token found, fetching new token"
78
78
  logger.info(msg)
79
- self._last_token_object = await self._get_token(
79
+ self.last_token_object = await self._get_token(
80
80
  self.client_id, self.client_secret
81
81
  )
82
82
 
83
- return self._last_token_object.full_token
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
- async_client,
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 = async_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, retry_on_http_status
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, retry_on_http_status
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, retry_on_http_status
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)
@@ -0,0 +1,51 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ import httpx
4
+ from loguru import logger
5
+ from werkzeug.local import LocalStack, LocalProxy
6
+
7
+ from port_ocean.clients.port.retry_transport import TokenRetryTransport
8
+
9
+ if TYPE_CHECKING:
10
+ from port_ocean.clients.port.client import PortClient
11
+
12
+ _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
13
+
14
+
15
+ def _get_http_client_context(port_client: "PortClient") -> httpx.AsyncClient:
16
+ client = _http_client.top
17
+ if client is None:
18
+ client = httpx.AsyncClient(
19
+ transport=TokenRetryTransport(
20
+ port_client,
21
+ httpx.AsyncHTTPTransport(),
22
+ logger=logger,
23
+ )
24
+ )
25
+ _http_client.push(client)
26
+
27
+ return client
28
+
29
+
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
41
+
42
+
43
+ def handle_status_code(
44
+ response: httpx.Response, should_raise: bool = True, should_log: bool = True
45
+ ) -> None:
46
+ if should_log and response.is_error:
47
+ logger.error(
48
+ f"Request failed with status code: {response.status_code}, Error: {response.text}"
49
+ )
50
+ if should_raise:
51
+ response.raise_for_status()
@@ -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
- return [entities_map[item] for item in sort_op.static_order()]
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
@@ -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
- if (
232
- remaining_attempts < 1
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
- if (
259
- remaining_attempts < 1
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
@@ -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:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = "Port Ocean is a CLI tool for managing your Port projects."
5
5
  readme = "README.md"
6
6
  homepage = "https://app.getport.io"
@@ -1,79 +0,0 @@
1
- from functools import wraps
2
- from typing import Callable, Any
3
-
4
- import httpx
5
- from loguru import logger
6
- from werkzeug.local import LocalStack, LocalProxy
7
-
8
- from port_ocean.helpers.retry import RetryTransport
9
-
10
- _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
11
-
12
-
13
- def _get_http_client_context() -> httpx.AsyncClient:
14
- client = _http_client.top
15
- if client is None:
16
- client = httpx.AsyncClient(
17
- transport=RetryTransport(
18
- httpx.AsyncHTTPTransport(),
19
- logger=logger,
20
- )
21
- )
22
- _http_client.push(client)
23
-
24
- return client
25
-
26
-
27
- async_client: httpx.AsyncClient = LocalProxy(lambda: _get_http_client_context()) # type: ignore
28
-
29
-
30
- def handle_status_code(
31
- response: httpx.Response, should_raise: bool = True, should_log: bool = True
32
- ) -> None:
33
- if should_log and response.is_error:
34
- logger.error(
35
- f"Request failed with status code: {response.status_code}, Error: {response.text}"
36
- )
37
- if should_raise:
38
- 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
File without changes
File without changes
File without changes