port-ocean 0.18.9__py3-none-any.whl → 0.19.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,10 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import httpx
4
+
5
+
6
+ class AuthClient(ABC):
7
+
8
+ @abstractmethod
9
+ def refresh_request_auth_creds(self, request: httpx.Request) -> httpx.Request:
10
+ pass
@@ -0,0 +1,22 @@
1
+ from port_ocean.clients.auth.auth_client import AuthClient
2
+ from port_ocean.context.ocean import ocean
3
+ from port_ocean.helpers.retry import register_on_retry_callback
4
+
5
+
6
+ class OAuthClient(AuthClient):
7
+ def __init__(self) -> None:
8
+ """
9
+ A client that can refresh a request using an access token.
10
+ """
11
+ if self.is_oauth_enabled():
12
+ register_on_retry_callback(self.refresh_request_auth_creds)
13
+
14
+ def is_oauth_enabled(self) -> bool:
15
+ return ocean.app.load_external_oauth_access_token() is not None
16
+
17
+ @property
18
+ def external_access_token(self) -> str:
19
+ access_token = ocean.app.load_external_oauth_access_token()
20
+ if access_token is None:
21
+ raise ValueError("No external access token found")
22
+ return access_token
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any, Dict, TYPE_CHECKING, Optional, TypedDict
2
+ from typing import Any, Dict, List, TYPE_CHECKING, Optional, TypedDict
3
3
  from urllib.parse import quote_plus
4
4
 
5
5
  import httpx
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
14
14
 
15
15
 
16
16
  INTEGRATION_POLLING_INTERVAL_INITIAL_SECONDS = 3
17
- INTEGRATION_POLLING_INTERVAL_BACKOFF_FACTOR = 1.15
17
+ INTEGRATION_POLLING_INTERVAL_BACKOFF_FACTOR = 1.55
18
18
  INTEGRATION_POLLING_RETRY_LIMIT = 30
19
19
  CREATE_RESOURCES_PARAM_NAME = "integration_modes"
20
20
  CREATE_RESOURCES_PARAM_VALUE = ["create_resources"]
@@ -38,6 +38,27 @@ class IntegrationClientMixin:
38
38
  self.client = client
39
39
  self._log_attributes: LogAttributes | None = None
40
40
 
41
+ async def is_integration_provision_enabled(
42
+ self, integration_type: str, should_raise: bool = True, should_log: bool = True
43
+ ) -> bool:
44
+ enabled_integrations = await self.get_provision_enabled_integrations(
45
+ should_raise, should_log
46
+ )
47
+ return integration_type in enabled_integrations
48
+
49
+ async def get_provision_enabled_integrations(
50
+ self, should_raise: bool = True, should_log: bool = True
51
+ ) -> List[str]:
52
+ logger.info("Fetching provision enabled integrations")
53
+ response = await self.client.get(
54
+ f"{self.auth.api_url}/integration/provision-enabled",
55
+ headers=await self.auth.headers(),
56
+ )
57
+
58
+ handle_status_code(response, should_raise, should_log)
59
+
60
+ return response.json().get("integrations", [])
61
+
41
62
  async def _get_current_integration(self) -> httpx.Response:
42
63
  logger.info(f"Fetching integration with id: {self.integration_identifier}")
43
64
  response = await self.client.get(
@@ -51,7 +72,27 @@ class IntegrationClientMixin:
51
72
  ) -> dict[str, Any]:
52
73
  response = await self._get_current_integration()
53
74
  handle_status_code(response, should_raise, should_log)
54
- return response.json().get("integration", {})
75
+ integration = response.json().get("integration", {})
76
+ if integration.get("config", None) or not integration:
77
+ return integration
78
+ is_provision_enabled_for_integration = integration.get(
79
+ "installationAppType", None
80
+ ) and (
81
+ await self.is_integration_provision_enabled(
82
+ integration.get("installationAppType", ""),
83
+ should_raise,
84
+ should_log,
85
+ )
86
+ )
87
+
88
+ if is_provision_enabled_for_integration:
89
+ logger.info(
90
+ "integration type is enabled, polling until provisioning is complete"
91
+ )
92
+ integration = (
93
+ await self._poll_integration_until_default_provisioning_is_complete()
94
+ )
95
+ return integration
55
96
 
56
97
  async def get_log_attributes(self) -> LogAttributes:
57
98
  if self._log_attributes is None:
@@ -72,7 +113,7 @@ class IntegrationClientMixin:
72
113
  )
73
114
  response = await self._get_current_integration()
74
115
  integration_json = response.json()
75
- if integration_json.get("integration", {}).get("config", {}):
116
+ if integration_json.get("integration", {}).get("config", {}) != {}:
76
117
  return integration_json
77
118
 
78
119
  logger.info(
@@ -1,12 +1,12 @@
1
1
  from typing import Any, Literal, Type, cast
2
2
 
3
- from pydantic import Extra, AnyHttpUrl, parse_obj_as, parse_raw_as
3
+ from pydantic import AnyHttpUrl, Extra, parse_obj_as, parse_raw_as
4
4
  from pydantic.class_validators import root_validator, validator
5
- from pydantic.env_settings import InitSettingsSource, EnvSettingsSource, BaseSettings
5
+ from pydantic.env_settings import BaseSettings, EnvSettingsSource, InitSettingsSource
6
6
  from pydantic.fields import Field
7
7
  from pydantic.main import BaseModel
8
8
 
9
- from port_ocean.config.base import BaseOceanSettings, BaseOceanModel
9
+ from port_ocean.config.base import BaseOceanModel, BaseOceanSettings
10
10
  from port_ocean.core.event_listener import EventListenerSettingsType
11
11
  from port_ocean.core.models import CreatePortResourcesOrigin, Runtime
12
12
  from port_ocean.utils.misc import get_integration_name, get_spec_file
@@ -71,6 +71,7 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
71
71
  # Determines if Port should generate resources such as blueprints and pages instead of ocean
72
72
  create_port_resources_origin: CreatePortResourcesOrigin | None = None
73
73
  send_raw_data_examples: bool = True
74
+ oauth_access_token_file_path: str | None = None
74
75
  port: PortSettings
75
76
  event_listener: EventListenerSettingsType = Field(
76
77
  default=cast(EventListenerSettingsType, {"type": "POLLING"})
@@ -75,7 +75,7 @@ async def _initialize_required_integration_settings(
75
75
  create_port_resources_origin_in_port=integration_config.create_port_resources_origin
76
76
  == CreatePortResourcesOrigin.Port,
77
77
  )
78
- elif not integration.get("config"):
78
+ elif not integration.get("config", None):
79
79
  logger.info(
80
80
  "Encountered that the integration's mapping is empty, Initializing to default mapping"
81
81
  )
@@ -213,11 +213,20 @@ async def _initialize_defaults(
213
213
  config_class, integration_config.resources_path
214
214
  )
215
215
 
216
+ is_integration_provision_enabled = (
217
+ await port_client.is_integration_provision_enabled(
218
+ integration_config.integration.type
219
+ )
220
+ )
221
+
216
222
  if (
217
223
  not integration_config.create_port_resources_origin
218
- and integration_config.runtime.is_saas_runtime
224
+ and is_integration_provision_enabled
219
225
  ):
220
- logger.info("Setting resources origin to be Port")
226
+ # Need to set default since spec is missing
227
+ logger.info(
228
+ f"Setting resources origin to be Port (integration {integration_config.integration.type} is supported)"
229
+ )
221
230
  integration_config.create_port_resources_origin = CreatePortResourcesOrigin.Port
222
231
 
223
232
  if (
@@ -228,7 +237,10 @@ async def _initialize_defaults(
228
237
  "Resources origin is set to be Port, verifying integration is supported"
229
238
  )
230
239
  org_feature_flags = await port_client.get_organization_feature_flags()
231
- if ORG_USE_PROVISIONED_DEFAULTS_FEATURE_FLAG not in org_feature_flags:
240
+ if (
241
+ not is_integration_provision_enabled
242
+ or ORG_USE_PROVISIONED_DEFAULTS_FEATURE_FLAG not in org_feature_flags
243
+ ):
232
244
  logger.info(
233
245
  "Port origin for Integration is not supported, changing resources origin to use Ocean"
234
246
  )
@@ -9,6 +9,15 @@ from typing import Any, Callable, Coroutine, Iterable, Mapping, Union
9
9
  import httpx
10
10
  from dateutil.parser import isoparse
11
11
 
12
+ _ON_RETRY_CALLBACK: Callable[[httpx.Request], httpx.Request] | None = None
13
+
14
+
15
+ def register_on_retry_callback(
16
+ _on_retry_callback: Callable[[httpx.Request], httpx.Request]
17
+ ) -> None:
18
+ global _ON_RETRY_CALLBACK
19
+ _ON_RETRY_CALLBACK = _on_retry_callback
20
+
12
21
 
13
22
  # Adapted from https://github.com/encode/httpx/issues/108#issuecomment-1434439481
14
23
  class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
@@ -43,7 +52,6 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
43
52
  _retry_status_codes (frozenset): The HTTP status codes that can be retried.
44
53
  _jitter_ratio (float): The amount of jitter to add to the backoff time.
45
54
  _max_backoff_wait (float): The maximum time to wait between retries in seconds.
46
-
47
55
  """
48
56
 
49
57
  RETRYABLE_METHODS = frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
@@ -53,6 +61,8 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
53
61
  HTTPStatus.BAD_GATEWAY,
54
62
  HTTPStatus.SERVICE_UNAVAILABLE,
55
63
  HTTPStatus.GATEWAY_TIMEOUT,
64
+ HTTPStatus.UNAUTHORIZED,
65
+ HTTPStatus.BAD_REQUEST,
56
66
  ]
57
67
  )
58
68
  MAX_BACKOFF_WAIT_IN_SECONDS = 60
@@ -316,6 +326,8 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
316
326
  if remaining_attempts < 1:
317
327
  self._log_error(request, error)
318
328
  raise
329
+ if _ON_RETRY_CALLBACK:
330
+ request = _ON_RETRY_CALLBACK(request)
319
331
  attempts_made += 1
320
332
  remaining_attempts -= 1
321
333
 
@@ -357,5 +369,7 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
357
369
  if remaining_attempts < 1:
358
370
  self._log_error(request, error)
359
371
  raise
372
+ if _ON_RETRY_CALLBACK:
373
+ request = _ON_RETRY_CALLBACK(request)
360
374
  attempts_made += 1
361
375
  remaining_attempts -= 1
port_ocean/ocean.py CHANGED
@@ -1,31 +1,31 @@
1
1
  import asyncio
2
2
  import sys
3
- import threading
4
3
  from contextlib import asynccontextmanager
5
- from typing import Callable, Any, Dict, AsyncIterator, Type
4
+ import threading
5
+ from typing import Any, AsyncIterator, Callable, Dict, Type
6
6
 
7
- from fastapi import FastAPI, APIRouter
7
+ from fastapi import APIRouter, FastAPI
8
8
  from loguru import logger
9
9
  from pydantic import BaseModel
10
- from starlette.types import Scope, Receive, Send
10
+ from starlette.types import Receive, Scope, Send
11
11
 
12
- from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
13
12
  from port_ocean.clients.port.client import PortClient
14
13
  from port_ocean.config.settings import (
15
14
  IntegrationConfiguration,
16
15
  )
17
16
  from port_ocean.context.ocean import (
18
17
  PortOceanContext,
19
- ocean,
20
18
  initialize_port_ocean_context,
19
+ ocean,
21
20
  )
21
+ from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
22
22
  from port_ocean.core.integrations.base import BaseIntegration
23
23
  from port_ocean.log.sensetive import sensitive_log_filter
24
24
  from port_ocean.middlewares import request_handler
25
+ from port_ocean.utils.misc import IntegrationStateStatus
25
26
  from port_ocean.utils.repeat import repeat_every
26
27
  from port_ocean.utils.signal import signal_handler
27
28
  from port_ocean.version import __integration_version__
28
- from port_ocean.utils.misc import IntegrationStateStatus
29
29
  from port_ocean.core.handlers.webhook.processor_manager import WebhookProcessorManager
30
30
 
31
31
 
@@ -118,6 +118,18 @@ class Ocean:
118
118
  )
119
119
  await repeated_function()
120
120
 
121
+ def load_external_oauth_access_token(self) -> str | None:
122
+ if self.config.oauth_access_token_file_path is not None:
123
+ try:
124
+ with open(self.config.oauth_access_token_file_path, "r") as f:
125
+ return f.read()
126
+ except Exception:
127
+ logger.exception(
128
+ "Failed to load external oauth access token from file",
129
+ file_path=self.config.oauth_access_token_file_path,
130
+ )
131
+ return None
132
+
121
133
  def initialize_app(self) -> None:
122
134
  self.fast_api_app.include_router(self.integration_router, prefix="/integration")
123
135
 
File without changes
File without changes
@@ -0,0 +1,96 @@
1
+ import pytest
2
+ import httpx
3
+ from unittest.mock import MagicMock, patch
4
+ from port_ocean.ocean import Ocean
5
+
6
+ from port_ocean.clients.auth.oauth_client import OAuthClient
7
+ from port_ocean.config.settings import IntegrationConfiguration
8
+
9
+
10
+ @pytest.fixture
11
+ def mock_ocean() -> Ocean:
12
+ with patch("port_ocean.ocean.Ocean.__init__", return_value=None):
13
+ ocean_mock = Ocean()
14
+ ocean_mock.config = MagicMock(spec=IntegrationConfiguration)
15
+ return ocean_mock
16
+
17
+
18
+ class MockOAuthClient(OAuthClient):
19
+ def __init__(self, is_oauth_enabled_value: bool = True):
20
+ self._is_oauth_enabled = is_oauth_enabled_value
21
+ self._access_token = "mock_access_token"
22
+ super().__init__()
23
+
24
+ def is_oauth_enabled(self) -> bool:
25
+ return self._is_oauth_enabled
26
+
27
+ def refresh_request_auth_creds(self, request: httpx.Request) -> httpx.Request:
28
+ headers = dict(request.headers)
29
+ headers["Authorization"] = f"Bearer {self.access_token}"
30
+ return httpx.Request(
31
+ method=request.method,
32
+ url=request.url,
33
+ headers=headers,
34
+ content=request.content,
35
+ )
36
+
37
+ @property
38
+ def access_token(self) -> str:
39
+ return self._access_token
40
+
41
+
42
+ @pytest.fixture
43
+ def mock_oauth_client() -> MockOAuthClient:
44
+ return MockOAuthClient()
45
+
46
+
47
+ @pytest.fixture
48
+ def disabled_oauth_client() -> MockOAuthClient:
49
+ return MockOAuthClient(is_oauth_enabled_value=False)
50
+
51
+
52
+ def test_oauth_client_initialization(mock_oauth_client: MockOAuthClient) -> None:
53
+ assert isinstance(mock_oauth_client, OAuthClient)
54
+ assert mock_oauth_client.is_oauth_enabled() is True
55
+
56
+
57
+ def test_oauth_client_disabled_initialization(
58
+ disabled_oauth_client: MockOAuthClient,
59
+ ) -> None:
60
+ assert isinstance(disabled_oauth_client, OAuthClient)
61
+ assert disabled_oauth_client.is_oauth_enabled() is False
62
+
63
+
64
+ def test_refresh_request_auth_creds(mock_oauth_client: MockOAuthClient) -> None:
65
+ # Create request with some content and existing headers
66
+ original_headers = {"Accept": "application/json", "X-Custom": "value"}
67
+ original_content = b'{"key": "value"}'
68
+ original_request = httpx.Request(
69
+ "GET",
70
+ "https://api.example.com",
71
+ headers=original_headers,
72
+ content=original_content,
73
+ )
74
+
75
+ refreshed_request = mock_oauth_client.refresh_request_auth_creds(original_request)
76
+
77
+ # Verify all attributes are identical except headers
78
+ assert refreshed_request.method == original_request.method
79
+ assert refreshed_request.url == original_request.url
80
+ assert refreshed_request.content == original_request.content
81
+
82
+ # Verify headers: should contain all original headers plus the new Authorization
83
+ for key, value in original_headers.items():
84
+ assert refreshed_request.headers[key] == value
85
+ assert refreshed_request.headers["Authorization"] == "Bearer mock_access_token"
86
+ # New headers should be:
87
+ # {'host': 'api.example.com',
88
+ # 'accept': 'application/json',
89
+ # 'x-custom': 'value',
90
+ # 'content-length': '16',
91
+ # 'authorization': '[secure]'}
92
+ assert len(refreshed_request.headers) == 5
93
+
94
+
95
+ def test_access_token_property(mock_oauth_client: MockOAuthClient) -> None:
96
+ assert mock_oauth_client.access_token == "mock_access_token"
@@ -0,0 +1,49 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock, mock_open, patch
3
+ from port_ocean.ocean import Ocean
4
+ from port_ocean.config.settings import IntegrationConfiguration
5
+
6
+
7
+ @pytest.fixture
8
+ def mock_ocean() -> Ocean:
9
+ with patch("port_ocean.ocean.Ocean.__init__", return_value=None):
10
+ ocean_mock = Ocean()
11
+ ocean_mock.config = MagicMock(spec=IntegrationConfiguration)
12
+ return ocean_mock
13
+
14
+
15
+ def test_load_external_oauth_access_token_no_file(mock_ocean: Ocean) -> None:
16
+ # Setup
17
+ mock_ocean.config.oauth_access_token_file_path = None
18
+
19
+ # Execute
20
+ result = mock_ocean.load_external_oauth_access_token()
21
+
22
+ # Assert
23
+ assert result is None
24
+
25
+
26
+ def test_load_external_oauth_access_token_with_file(mock_ocean: Ocean) -> None:
27
+ # Setup
28
+ mock_ocean.config.oauth_access_token_file_path = "/path/to/token.txt"
29
+ mock_file_content = "test_access_token"
30
+
31
+ with patch("builtins.open", mock_open(read_data=mock_file_content)):
32
+ # Execute
33
+ result = mock_ocean.load_external_oauth_access_token()
34
+
35
+ # Assert
36
+ assert result == "test_access_token"
37
+
38
+
39
+ def test_load_external_oauth_access_token_with_empty_file(mock_ocean: Ocean) -> None:
40
+ # Setup
41
+ mock_ocean.config.oauth_access_token_file_path = "/path/to/token.txt"
42
+ mock_file_content = ""
43
+
44
+ with patch("builtins.open", mock_open(read_data=mock_file_content)):
45
+ # Execute
46
+ result = mock_ocean.load_external_oauth_access_token()
47
+
48
+ # Assert
49
+ assert result == ""
@@ -18,7 +18,7 @@ def _get_http_client_context() -> httpx.AsyncClient:
18
18
 
19
19
 
20
20
  """
21
- Utilize this client for all outbound integration requests to the third-party application. It functions as a wrapper
21
+ Utilize this client for all outbound integration requests to the third-party application. It functions as a wrapper
22
22
  around the httpx.AsyncClient, incorporating retry logic at the transport layer for handling retries on 5xx errors and
23
23
  connection errors.
24
24
 
@@ -22,10 +22,8 @@ def repeat_every(
22
22
  ) -> NoArgsNoReturnDecorator:
23
23
  """
24
24
  This function returns a decorator that modifies a function so it is periodically re-executed after its first call.
25
-
26
25
  The function it decorates should accept no arguments and return nothing. If necessary, this can be accomplished
27
26
  by using `functools.partial` or otherwise wrapping the target function prior to decoration.
28
-
29
27
  Parameters
30
28
  ----------
31
29
  seconds: float
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.18.9
3
+ Version: 0.19.2
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
@@ -44,13 +44,16 @@ port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py,
44
44
  port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py,sha256=Ew5LA_G1k6DC5a2ygU2FoyjZQa0fRmPy73N0bio0d14,46
45
45
  port_ocean/cli/utils.py,sha256=IUK2UbWqjci-lrcDdynZXqVP5B5TcjF0w5CpEVUks-k,54
46
46
  port_ocean/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ port_ocean/clients/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
+ port_ocean/clients/auth/auth_client.py,sha256=scxx7AYqvXoRAd8_K-Ww26oErzi5l8ZCGPc0sVKgIfA,192
49
+ port_ocean/clients/auth/oauth_client.py,sha256=RGMigXP8XOQECvEccXjF-EYPBXaxFDpA2aniN_vM3d0,794
47
50
  port_ocean/clients/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
51
  port_ocean/clients/port/authentication.py,sha256=6-uDMWsJ0xLe1-9IoYXHWmwtufj8rJR4BCRXJlSkCSQ,3447
49
52
  port_ocean/clients/port/client.py,sha256=OaNeN3U7Hw0tK4jYE6ESJEPKbTf9nGp2jcJVq00gnf8,3546
50
53
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
54
  port_ocean/clients/port/mixins/blueprints.py,sha256=POBl4uDocrgJBw4rvCAzwRcD4jk-uBL6pDAuKMTajdg,4633
52
55
  port_ocean/clients/port/mixins/entities.py,sha256=PJzVZTBW_OheFRGPRCZ6yPbVGEAKsMO9CNDNJUI1l48,10770
53
- port_ocean/clients/port/mixins/integrations.py,sha256=R7ZTdU7UKh1IHOqbOJzK76c_JwAyEosRvIWWs4PO_8k,7543
56
+ port_ocean/clients/port/mixins/integrations.py,sha256=0ht8nfjsRBu_dbnlMZxSH0jqKv0PF8U8EkllRXgnrWA,9122
54
57
  port_ocean/clients/port/mixins/migrations.py,sha256=A6896oJF6WbFL2WroyTkMzr12yhVyWqGoq9dtLNSKBY,1457
55
58
  port_ocean/clients/port/mixins/organization.py,sha256=fCo_ZS8UlQXsyIx-odTuWkbnfcYmVnQfIsSyJuPOPjM,1031
56
59
  port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
@@ -59,7 +62,7 @@ port_ocean/clients/port/utils.py,sha256=SjhgmJXAqH2JqXfGy8GoGwzUYiJvUhWDrJyxQcen
59
62
  port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
63
  port_ocean/config/base.py,sha256=x1gFbzujrxn7EJudRT81C6eN9WsYAb3vOHwcpcpX8Tc,6370
61
64
  port_ocean/config/dynamic.py,sha256=qOFkRoJsn_BW7581omi_AoMxoHqasf_foxDQ_G11_SI,2030
62
- port_ocean/config/settings.py,sha256=q5KgDIr8snIxejoKyWSyf21R_AYEEXiEd3-ry9qf3ss,5227
65
+ port_ocean/config/settings.py,sha256=C1hQJsvHz-411-CtT0DvMGkImcO-E_zgmbzpI3TUO64,5279
63
66
  port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
67
  port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
65
68
  port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -70,7 +73,7 @@ port_ocean/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
73
  port_ocean/core/defaults/__init__.py,sha256=8qCZg8n06WAdMu9s_FiRtDYLGPGHbOuS60vapeUoAks,142
71
74
  port_ocean/core/defaults/clean.py,sha256=_rL-NCl6Q_x3lUxDW5ACOM27IYilTCWl6ISUfRleuL0,2891
72
75
  port_ocean/core/defaults/common.py,sha256=zJsj7jvlqIMLGXhdASUlbKS8GIAf-FDKKB0O7jB6nx0,4166
73
- port_ocean/core/defaults/initialize.py,sha256=IYgc6XS5ARdcPM4IQrCSi4cdAomHxJVnP_OWOU9f05U,10382
76
+ port_ocean/core/defaults/initialize.py,sha256=3Ezn63YwxVnByegRPqNXt057Tx9D0IxqrZQHgkrbKWA,10760
74
77
  port_ocean/core/event_listener/__init__.py,sha256=T3E52MKs79fNEW381p7zU9F2vOMvIiiTYWlqRUqnsg0,1135
75
78
  port_ocean/core/event_listener/base.py,sha256=VdIdp7RLOSxH3ICyV-wCD3NiJoUzsh2KkJ0a9B29GeI,2847
76
79
  port_ocean/core/event_listener/factory.py,sha256=M4Qi05pI840sjDIbdjUEgYe9Gp5ckoCkX-KgLBxUpZg,4096
@@ -127,17 +130,20 @@ port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnq
127
130
  port_ocean/exceptions/webhook_processor.py,sha256=yQYazg53Y-ohb7HfViwq1opH_ZUuUdhHSRxcUNveFpI,114
128
131
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
132
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
130
- port_ocean/helpers/retry.py,sha256=fmvaUFLIW6PICgYH4RI5rrKBXDxCAhR1Gtl0dsb2kMs,15625
133
+ port_ocean/helpers/retry.py,sha256=1SxeRPkaH3K1BDvcdZbze2ila7SyOMYD8DQ3CwrruYk,16135
131
134
  port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
132
135
  port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,3631
133
136
  port_ocean/log/logger_setup.py,sha256=CoEDowe5OwNOL_5clU6Z4faktfh0VWaOTS0VLmyhHjw,2404
134
137
  port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
135
138
  port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
136
- port_ocean/ocean.py,sha256=MmLGxx0EC3Q6FEpbnZc9_5A7DculsvN2nAz3aZkj2kI,5536
139
+ port_ocean/ocean.py,sha256=jFNVykBy01b4A7er16bzQoiMYZqfRgcs8TR0Snd3FKY,6046
137
140
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
141
  port_ocean/run.py,sha256=COoRSmLG4hbsjIW5DzhV0NYVegI9xHd1POv6sg4U1No,2217
139
142
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
140
143
  port_ocean/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
144
+ port_ocean/tests/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
145
+ port_ocean/tests/clients/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
+ port_ocean/tests/clients/oauth/test_oauth_client.py,sha256=2XVMQUalDpiD539Z7_dk5BK_ngXQzsTmb2lNBsfEm9c,3266
141
147
  port_ocean/tests/clients/port/mixins/test_entities.py,sha256=A9myrnkLhKSQrnOLv1Zz2wiOVSxW65Q9RIUIRbn_V7w,1586
142
148
  port_ocean/tests/clients/port/mixins/test_organization_mixin.py,sha256=-8iHM33Oe8PuyEfj3O_6Yob8POp4fSmB0hnIT0Gv-8Y,868
143
149
  port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
@@ -162,21 +168,22 @@ port_ocean/tests/helpers/ocean_app.py,sha256=N06vcNI1klqdcNFq-PXL5vm77u-hODsOSXn
162
168
  port_ocean/tests/helpers/port_client.py,sha256=5d6GNr8vNNSOkrz1AdOhxBUKuusr_-UPDP7AVpHasQw,599
163
169
  port_ocean/tests/helpers/smoke_test.py,sha256=_9aJJFRfuGJEg2D2YQJVJRmpreS6gEPHHQq8Q01x4aQ,2697
164
170
  port_ocean/tests/log/test_handlers.py,sha256=uxgYCEQLP9U5qf-zUN9SgWFogMbYdnBeOVzXZ7E_yFw,2119
171
+ port_ocean/tests/test_ocean.py,sha256=bsXKGTVEjwLSbR7-qSmI4GZ-EzDo0eBE3TNSMsWzYxM,1502
165
172
  port_ocean/tests/test_smoke.py,sha256=uix2uIg_yOm8BHDgHw2hTFPy1fiIyxBGW3ENU_KoFlo,2557
166
173
  port_ocean/tests/utils/test_async_iterators.py,sha256=3PLk1emEXekb8LcC5GgVh3OicaX15i5WyaJT_eFnu_4,1336
167
174
  port_ocean/tests/utils/test_cache.py,sha256=GzoS8xGCBDbBcPwSDbdimsMMkRvJATrBC7UmFhdW3fw,4906
168
175
  port_ocean/utils/__init__.py,sha256=KMGnCPXZJbNwtgxtyMycapkDz8tpSyw23MSYT3iVeHs,91
169
- port_ocean/utils/async_http.py,sha256=arnH458TExn2Dju_Sy6pHas_vF5RMWnOp-jBz5WAAcE,1226
176
+ port_ocean/utils/async_http.py,sha256=pdXUs5VxvWcX0i6wJXb_LjN0OrunEkCGdoM_TNXcSO0,1225
170
177
  port_ocean/utils/async_iterators.py,sha256=CPXskYWkhkZtAG-ducEwM8537t3z5usPEqXR9vcivzw,3715
171
178
  port_ocean/utils/cache.py,sha256=RgfN4SjjHrEkbqUChyboeD1mrXomolUUjsJtvbkmr3U,3353
172
179
  port_ocean/utils/misc.py,sha256=0q2cJ5psqxn_5u_56pT7vOVQ3shDM02iC1lzyWQ_zl0,2098
173
180
  port_ocean/utils/queue_utils.py,sha256=KWWl8YVnG-glcfIHhM6nefY-2sou_C6DVP1VynQwzB4,2762
174
- port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,3231
181
+ port_ocean/utils/repeat.py,sha256=U2OeCkHPWXmRTVoPV-VcJRlQhcYqPWI5NfmPlb1JIbc,3229
175
182
  port_ocean/utils/signal.py,sha256=mMVq-1Ab5YpNiqN4PkiyTGlV_G0wkUDMMjTZp5z3pb0,1514
176
183
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
177
184
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
178
- port_ocean-0.18.9.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
179
- port_ocean-0.18.9.dist-info/METADATA,sha256=wNsXnfPpKx_dLVdrt9CSVl8oLrrHNM4-T0y6j27iHM4,6669
180
- port_ocean-0.18.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
181
- port_ocean-0.18.9.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
182
- port_ocean-0.18.9.dist-info/RECORD,,
185
+ port_ocean-0.19.2.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
186
+ port_ocean-0.19.2.dist-info/METADATA,sha256=-jaMpn-0NtgOvDlvVbIwTPuRwbRMh2kDmbGYRLzdEqQ,6669
187
+ port_ocean-0.19.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
188
+ port_ocean-0.19.2.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
189
+ port_ocean-0.19.2.dist-info/RECORD,,