port-ocean 0.27.0__py3-none-any.whl → 0.27.10__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.
Files changed (30) hide show
  1. integrations/_infra/Dockerfile.Deb +4 -1
  2. integrations/_infra/Dockerfile.local +3 -1
  3. port_ocean/clients/port/authentication.py +2 -0
  4. port_ocean/clients/port/client.py +5 -2
  5. port_ocean/clients/port/mixins/integrations.py +1 -1
  6. port_ocean/config/settings.py +12 -1
  7. port_ocean/core/event_listener/kafka.py +14 -0
  8. port_ocean/core/handlers/entities_state_applier/port/applier.py +9 -0
  9. port_ocean/core/handlers/entity_processor/jq_entity_processor.py +12 -9
  10. port_ocean/core/handlers/port_app_config/models.py +1 -0
  11. port_ocean/core/handlers/resync_state_updater/updater.py +2 -0
  12. port_ocean/core/integrations/mixins/sync_raw.py +22 -4
  13. port_ocean/core/integrations/mixins/utils.py +26 -4
  14. port_ocean/helpers/async_client.py +7 -0
  15. port_ocean/helpers/metric/metric.py +13 -11
  16. port_ocean/helpers/stream.py +71 -0
  17. port_ocean/log/handlers.py +3 -4
  18. port_ocean/ocean.py +11 -12
  19. port_ocean/tests/core/conftest.py +7 -1
  20. port_ocean/tests/core/event_listener/test_kafka.py +70 -0
  21. port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +2 -2
  22. port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +1 -1
  23. port_ocean/tests/core/handlers/mixins/test_live_events.py +23 -13
  24. port_ocean/tests/core/handlers/webhook/test_processor_manager.py +32 -27
  25. port_ocean/tests/helpers/port_client.py +1 -0
  26. {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/METADATA +5 -2
  27. {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/RECORD +30 -28
  28. {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/LICENSE.md +0 -0
  29. {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/WHEEL +0 -0
  30. {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/entry_points.txt +0 -0
@@ -28,11 +28,14 @@ ARG INTEGRATION_VERSION
28
28
  ARG BUILD_CONTEXT
29
29
  ARG PROMETHEUS_MULTIPROC_DIR=/tmp/ocean/prometheus/metrics
30
30
  ARG OAUTH_CONFIG_DIR=/app/.config
31
+ ARG STREAMING_LOCATION=/tmp/ocean/streaming
31
32
 
32
33
  ENV LIBRDKAFKA_VERSION=2.8.2 \
33
- PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR}
34
+ PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR} \
35
+ STREAMING_LOCATION=${STREAMING_LOCATION}
34
36
 
35
37
  RUN mkdir -p ${PROMETHEUS_MULTIPROC_DIR}
38
+ RUN mkdir -p ${STREAMING_LOCATION}
36
39
  RUN chown -R ocean:appgroup /tmp/ocean && chmod -R 755 /tmp/ocean
37
40
 
38
41
  RUN mkdir -p ${OAUTH_CONFIG_DIR}
@@ -33,13 +33,15 @@ RUN apt-get update \
33
33
 
34
34
  ARG BUILD_CONTEXT
35
35
  ARG PROMETHEUS_MULTIPROC_DIR=/tmp/ocean/prometheus/metrics
36
+ ARG STREAMING_LOCATION=/tmp/ocean/streaming
36
37
 
37
38
  ENV PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR}
38
-
39
+ ENV STREAMING_LOCATION=${STREAMING_LOCATION}
39
40
  # Create /tmp/ocean directory and set permissions
40
41
 
41
42
 
42
43
  RUN mkdir -p ${PROMETHEUS_MULTIPROC_DIR}
44
+ RUN mkdir -p ${STREAMING_LOCATION}
43
45
 
44
46
  WORKDIR /app
45
47
 
@@ -35,6 +35,7 @@ class PortAuthentication:
35
35
  integration_identifier: str,
36
36
  integration_type: str,
37
37
  integration_version: str,
38
+ ingest_url: str,
38
39
  ):
39
40
  self.client = client
40
41
  self.api_url = api_url
@@ -43,6 +44,7 @@ class PortAuthentication:
43
44
  self.integration_identifier = integration_identifier
44
45
  self.integration_type = integration_type
45
46
  self.integration_version = integration_version
47
+ self.ingest_url = ingest_url
46
48
  self.last_token_object: TokenResponse | None = None
47
49
 
48
50
  async def _get_token(self, client_id: str, client_secret: str) -> TokenResponse:
@@ -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
@@ -10,11 +12,10 @@ from port_ocean.clients.port.types import (
10
12
  KafkaCreds,
11
13
  )
12
14
  from port_ocean.clients.port.utils import (
13
- handle_port_status_code,
14
15
  get_internal_http_client,
16
+ handle_port_status_code,
15
17
  )
16
18
  from port_ocean.exceptions.clients import KafkaCredentialsNotFound
17
- from typing import Any
18
19
 
19
20
 
20
21
  class PortClient(
@@ -32,6 +33,7 @@ class PortClient(
32
33
  integration_identifier: str,
33
34
  integration_type: str,
34
35
  integration_version: str,
36
+ ingest_url: str,
35
37
  ):
36
38
  self.api_url = f"{base_url}/v1"
37
39
  self.client = get_internal_http_client(self)
@@ -43,6 +45,7 @@ class PortClient(
43
45
  integration_identifier,
44
46
  integration_type,
45
47
  integration_version,
48
+ ingest_url,
46
49
  )
47
50
  EntityClientMixin.__init__(self, self.auth, self.client)
48
51
  IntegrationClientMixin.__init__(
@@ -296,7 +296,7 @@ class IntegrationClientMixin:
296
296
  logger.debug("starting POST raw data request", raw_data=raw_data)
297
297
  headers = await self.auth.headers()
298
298
  response = await self.client.post(
299
- f"{self.auth.api_url}/lakehouse/integration/{self.integration_identifier}/sync/{sync_id}/kind/{kind}/items",
299
+ f"{self.auth.ingest_url}/lakehouse/integration-type/{self.auth.integration_type}/integration/{self.integration_identifier}/sync/{sync_id}/kind/{kind}/items",
300
300
  headers=headers,
301
301
  json={
302
302
  "items": raw_data,
@@ -9,7 +9,6 @@ from pydantic.main import BaseModel
9
9
 
10
10
  from port_ocean.config.base import BaseOceanModel, BaseOceanSettings
11
11
  from port_ocean.core.event_listener import EventListenerSettingsType
12
-
13
12
  from port_ocean.core.models import (
14
13
  CachingStorageMode,
15
14
  CreatePortResourcesOrigin,
@@ -47,6 +46,7 @@ class PortSettings(BaseOceanModel, extra=Extra.allow):
47
46
  client_secret: str = Field(..., sensitive=True)
48
47
  base_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://api.getport.io")
49
48
  port_app_config_cache_ttl: int = 60
49
+ ingest_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://ingest.getport.io")
50
50
 
51
51
 
52
52
  class IntegrationSettings(BaseOceanModel, extra=Extra.allow):
@@ -73,6 +73,13 @@ class MetricsSettings(BaseOceanModel, extra=Extra.allow):
73
73
  webhook_url: str | None = Field(default=None)
74
74
 
75
75
 
76
+ class StreamingSettings(BaseOceanModel, extra=Extra.allow):
77
+ enabled: bool = Field(default=False)
78
+ max_buffer_size_mb: int = Field(default=1024 * 1024 * 20) # 20 mb
79
+ chunk_size: int = Field(default=1024 * 64) # 64 kb
80
+ location: str = Field(default="/tmp/ocean/streaming")
81
+
82
+
76
83
  class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
77
84
  _integration_config_model: BaseModel | None = None
78
85
 
@@ -111,6 +118,10 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
111
118
  upsert_entities_batch_max_length: int = 20
112
119
  upsert_entities_batch_max_size_in_bytes: int = 1024 * 1024
113
120
  lakehouse_enabled: bool = False
121
+ yield_items_to_parse: bool = False
122
+ yield_items_to_parse_batch_size: int = 10
123
+
124
+ streaming: StreamingSettings = Field(default_factory=lambda: StreamingSettings())
114
125
 
115
126
  @validator("process_execution_mode")
116
127
  def validate_process_execution_mode(
@@ -16,6 +16,7 @@ from port_ocean.core.event_listener.base import (
16
16
  EventListenerEvents,
17
17
  EventListenerSettings,
18
18
  )
19
+ from pydantic import validator
19
20
 
20
21
 
21
22
  class KafkaEventListenerSettings(EventListenerSettings):
@@ -46,6 +47,19 @@ class KafkaEventListenerSettings(EventListenerSettings):
46
47
  kafka_security_enabled: bool = True
47
48
  consumer_poll_timeout: int = 1
48
49
 
50
+ @validator("brokers")
51
+ @classmethod
52
+ def parse_brokers(cls, v: str) -> str:
53
+ # If it's a JSON array string, parse and join
54
+ if v.strip().startswith("[") and v.strip().endswith("]"):
55
+ try:
56
+ parsed = json.loads(v)
57
+ if isinstance(parsed, list):
58
+ return ",".join(parsed)
59
+ except json.JSONDecodeError:
60
+ pass
61
+ return v
62
+
49
63
  def get_changelog_destination_details(self) -> dict[str, Any]:
50
64
  """
51
65
  Returns the changelog destination configuration for the Kafka event listener.
@@ -87,6 +87,15 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
87
87
  diff = get_port_diff(entities["before"], entities["after"])
88
88
 
89
89
  if not diff.deleted:
90
+ ocean.metrics.inc_metric(
91
+ name=MetricType.OBJECT_COUNT_NAME,
92
+ labels=[
93
+ ocean.metrics.current_resource_kind(),
94
+ MetricPhase.DELETE,
95
+ MetricPhase.DeletionResult.DELETED,
96
+ ],
97
+ value=0,
98
+ )
90
99
  return
91
100
 
92
101
  kept_entities = diff.created + diff.modified
@@ -242,19 +242,21 @@ class JQEntityProcessor(BaseEntityProcessor):
242
242
  data: dict[str, Any],
243
243
  raw_entity_mappings: dict[str, Any],
244
244
  items_to_parse: str | None,
245
+ items_to_parse_name: str,
245
246
  selector_query: str,
246
247
  parse_all: bool = False,
247
248
  ) -> tuple[list[MappedEntity], list[Exception]]:
248
249
  raw_data = [data.copy()]
249
- if items_to_parse:
250
- items = await self._search(data, items_to_parse)
251
- if not isinstance(items, list):
252
- logger.warning(
253
- f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
254
- f" Skipping..."
255
- )
256
- return [], []
257
- raw_data = [{"item": item, **data} for item in items]
250
+ if not ocean.config.yield_items_to_parse:
251
+ if items_to_parse:
252
+ items = await self._search(data, items_to_parse)
253
+ if not isinstance(items, list):
254
+ logger.warning(
255
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
256
+ f" Skipping..."
257
+ )
258
+ return [], []
259
+ raw_data = [{items_to_parse_name: item, **data} for item in items]
258
260
 
259
261
  entities, errors = await gather_and_split_errors_from_results(
260
262
  [
@@ -303,6 +305,7 @@ class JQEntityProcessor(BaseEntityProcessor):
303
305
  self._calculate_entity,
304
306
  raw_entity_mappings,
305
307
  mapping.port.items_to_parse,
308
+ mapping.port.items_to_parse_name,
306
309
  mapping.selector.query,
307
310
  parse_all,
308
311
  )
@@ -39,6 +39,7 @@ class MappingsConfig(BaseModel):
39
39
  class PortResourceConfig(BaseModel):
40
40
  entity: MappingsConfig
41
41
  items_to_parse: str | None = Field(alias="itemsToParse")
42
+ items_to_parse_name: str | None = Field(alias="itemsToParseName", default="item")
42
43
 
43
44
 
44
45
  class Selector(BaseModel):
@@ -91,6 +91,8 @@ class ResyncStateUpdater:
91
91
  value=int(status == IntegrationStateStatus.Completed),
92
92
  )
93
93
 
94
+ ocean.metrics.sync_state = status.value
95
+
94
96
  await ocean.metrics.send_metrics_to_webhook(
95
97
  kind=ocean.metrics.current_resource_kind()
96
98
  )
@@ -116,7 +116,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
116
116
  logger.info(
117
117
  f"Found async generator function for {resource_config.kind} name: {task.__qualname__}"
118
118
  )
119
- results.append(resync_generator_wrapper(task, resource_config.kind))
119
+ results.append(resync_generator_wrapper(task, resource_config.kind,resource_config.port.items_to_parse))
120
120
  else:
121
121
  logger.info(
122
122
  f"Found sync function for {resource_config.kind} name: {task.__qualname__}"
@@ -976,6 +976,11 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
976
976
  ocean.metrics.initialize_metrics(kinds)
977
977
  await ocean.metrics.report_sync_metrics(kinds=kinds, blueprints=blueprints)
978
978
 
979
+ async with metric_resource_context(MetricResourceKind.RUNTIME):
980
+ ocean.metrics.sync_state = SyncState.SYNCING
981
+ await ocean.metrics.send_metrics_to_webhook(kind=MetricResourceKind.RUNTIME)
982
+ await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RUNTIME])
983
+
979
984
  # Clear cache
980
985
  await ocean.app.cache_provider.clear()
981
986
 
@@ -1010,8 +1015,18 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
1010
1015
  logger.warning(
1011
1016
  "Resync aborted successfully, skipping delete phase. This leads to an incomplete state"
1012
1017
  )
1018
+
1019
+ async with metric_resource_context(MetricResourceKind.RUNTIME):
1020
+ ocean.metrics.sync_state = SyncState.FAILED
1021
+ await ocean.metrics.send_metrics_to_webhook(kind=MetricResourceKind.RUNTIME)
1022
+ await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RUNTIME])
1013
1023
  raise
1014
1024
  else:
1025
+ async with metric_resource_context(MetricResourceKind.RECONCILIATION):
1026
+ ocean.metrics.sync_state = SyncState.SYNCING
1027
+ await ocean.metrics.send_metrics_to_webhook(kind=MetricResourceKind.RECONCILIATION)
1028
+ await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RECONCILIATION])
1029
+
1015
1030
  success = await self.resync_reconciliation(
1016
1031
  creation_results,
1017
1032
  did_fetched_current_state,
@@ -1019,9 +1034,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
1019
1034
  app_config,
1020
1035
  silent,
1021
1036
  )
1022
- await ocean.metrics.report_sync_metrics(
1023
- kinds=[MetricResourceKind.RECONCILIATION]
1024
- )
1037
+
1038
+ async with metric_resource_context(MetricResourceKind.RECONCILIATION):
1039
+ ocean.metrics.sync_state = SyncState.COMPLETED if success else SyncState.FAILED
1040
+ await ocean.metrics.send_metrics_to_webhook(kind=MetricResourceKind.RECONCILIATION)
1041
+ await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RECONCILIATION])
1042
+
1025
1043
  return success
1026
1044
  finally:
1027
1045
  await ocean.app.cache_provider.clear()
@@ -1,11 +1,13 @@
1
1
  from contextlib import contextmanager
2
- from typing import Awaitable, Generator, Callable
2
+ from typing import Awaitable, Generator, Callable, cast
3
3
 
4
4
  from loguru import logger
5
5
 
6
6
  import asyncio
7
7
  import multiprocessing
8
8
 
9
+ from port_ocean.core.handlers.entity_processor.jq_entity_processor import JQEntityProcessor
10
+ from port_ocean.core.handlers.port_app_config.models import ResourceConfig
9
11
  from port_ocean.core.ocean_types import (
10
12
  ASYNC_GENERATOR_RESYNC_TYPE,
11
13
  RAW_RESULT,
@@ -49,7 +51,7 @@ async def resync_function_wrapper(
49
51
 
50
52
 
51
53
  async def resync_generator_wrapper(
52
- fn: Callable[[str], ASYNC_GENERATOR_RESYNC_TYPE], kind: str
54
+ fn: Callable[[str], ASYNC_GENERATOR_RESYNC_TYPE], kind: str, items_to_parse: str | None = None
53
55
  ) -> ASYNC_GENERATOR_RESYNC_TYPE:
54
56
  generator = fn(kind)
55
57
  errors = []
@@ -58,7 +60,28 @@ async def resync_generator_wrapper(
58
60
  try:
59
61
  with resync_error_handling():
60
62
  result = await anext(generator)
61
- yield validate_result(result)
63
+ if not ocean.config.yield_items_to_parse:
64
+ yield validate_result(result)
65
+ else:
66
+ batch_size = ocean.config.yield_items_to_parse_batch_size
67
+ if items_to_parse:
68
+ for data in result:
69
+ items = await cast(JQEntityProcessor, ocean.app.integration.entity_processor)._search(data, items_to_parse)
70
+ if not isinstance(items, list):
71
+ logger.warning(
72
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
73
+ f" Skipping..."
74
+ )
75
+ yield []
76
+ raw_data = [{"item": item, **data} for item in items]
77
+ while True:
78
+ raw_data_batch = raw_data[:batch_size]
79
+ yield raw_data_batch
80
+ raw_data = raw_data[batch_size:]
81
+ if len(raw_data) == 0:
82
+ break
83
+ else:
84
+ yield validate_result(result)
62
85
  except OceanAbortException as error:
63
86
  errors.append(error)
64
87
  ocean.metrics.inc_metric(
@@ -97,7 +120,6 @@ class ProcessWrapper(multiprocessing.Process):
97
120
  logger.error(f"Process {self.pid} failed with exit code {self.exitcode}")
98
121
  else:
99
122
  logger.info(f"Process {self.pid} finished with exit code {self.exitcode}")
100
- ocean.metrics.cleanup_prometheus_metrics(self.pid)
101
123
  return super().join()
102
124
 
103
125
  def clear_http_client_context() -> None:
@@ -4,6 +4,7 @@ import httpx
4
4
  from loguru import logger
5
5
 
6
6
  from port_ocean.helpers.retry import RetryTransport
7
+ from port_ocean.helpers.stream import Stream
7
8
 
8
9
 
9
10
  class OceanAsyncClient(httpx.AsyncClient):
@@ -50,3 +51,9 @@ class OceanAsyncClient(httpx.AsyncClient):
50
51
  logger=logger,
51
52
  **(self._transport_kwargs or {}),
52
53
  )
54
+
55
+ async def get_stream(self, url: str, **kwargs: Any) -> Stream:
56
+ req = self.build_request("GET", url, **kwargs)
57
+ response = await self.send(req, stream=True)
58
+ response.raise_for_status()
59
+ return Stream(response)
@@ -1,21 +1,22 @@
1
1
  import os
2
- from typing import Any, TYPE_CHECKING, Optional, Dict, List, Tuple
3
- from fastapi import APIRouter
4
- from port_ocean.exceptions.context import ResourceContextNotFoundError
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
3
+
5
4
  import prometheus_client
6
- from httpx import AsyncClient
7
- from fastapi.responses import PlainTextResponse
8
- from loguru import logger
9
- from port_ocean.context import metric_resource, resource
10
- from prometheus_client import Gauge
11
5
  import prometheus_client.openmetrics
12
6
  import prometheus_client.openmetrics.exposition
13
7
  import prometheus_client.parser
14
- from prometheus_client import multiprocess
8
+ from fastapi import APIRouter
9
+ from fastapi.responses import PlainTextResponse
10
+ from httpx import AsyncClient
11
+ from loguru import logger
12
+ from prometheus_client import Gauge, multiprocess
13
+
14
+ from port_ocean.context import metric_resource, resource
15
+ from port_ocean.exceptions.context import ResourceContextNotFoundError
15
16
 
16
17
  if TYPE_CHECKING:
17
- from port_ocean.config.settings import MetricsSettings, IntegrationSettings
18
18
  from port_ocean.clients.port.client import PortClient
19
+ from port_ocean.config.settings import IntegrationSettings, MetricsSettings
19
20
 
20
21
 
21
22
  class MetricPhase:
@@ -61,6 +62,7 @@ class SyncState:
61
62
  class MetricResourceKind:
62
63
  RECONCILIATION = "__reconciliation__"
63
64
  RESYNC = "__resync__"
65
+ RUNTIME = "__runtime__"
64
66
 
65
67
 
66
68
  # Registry for core and custom metrics
@@ -278,7 +280,7 @@ class Metrics:
278
280
  try:
279
281
  return metric_resource.metric_resource.metric_resource_kind
280
282
  except ResourceContextNotFoundError:
281
- return "__runtime__"
283
+ return MetricResourceKind.RUNTIME
282
284
 
283
285
  def generate_latest(self) -> str:
284
286
  return prometheus_client.openmetrics.exposition.generate_latest(
@@ -0,0 +1,71 @@
1
+ import os
2
+ from typing import Any, AsyncGenerator
3
+ import uuid
4
+
5
+ import aiofiles
6
+ import httpx
7
+ import ijson # type: ignore[import-untyped]
8
+ from cryptography.fernet import Fernet
9
+
10
+ import port_ocean.context.ocean as ocean_context
11
+
12
+
13
+ class Stream:
14
+ def __init__(self, response: httpx.Response):
15
+ self.response = response
16
+ self.headers = response.headers
17
+ self.status_code = response.status_code
18
+
19
+ async def _byte_stream(
20
+ self, chunk_size: int | None = None
21
+ ) -> AsyncGenerator[bytes, None]:
22
+ if chunk_size is None:
23
+ chunk_size = ocean_context.ocean.config.streaming.chunk_size
24
+
25
+ file_name = f"{ocean_context.ocean.config.streaming.location}/{uuid.uuid4()}"
26
+
27
+ crypt = Fernet(Fernet.generate_key())
28
+
29
+ try:
30
+ async for chunk in self.response.aiter_bytes(chunk_size=chunk_size):
31
+ async with aiofiles.open(f"{file_name}", "ab") as f:
32
+ if len(chunk) > 0:
33
+ await f.write(crypt.encrypt(chunk))
34
+ await f.write(b"\n")
35
+ finally:
36
+ await self.response.aclose()
37
+
38
+ try:
39
+ async with aiofiles.open(f"{file_name}", mode="rb") as f:
40
+ while True:
41
+ line = await f.readline()
42
+ if not line:
43
+ break
44
+ data = crypt.decrypt(line)
45
+ yield data
46
+ finally:
47
+ try:
48
+ os.remove(file_name)
49
+ except FileNotFoundError:
50
+ pass
51
+
52
+ async def get_json_stream(
53
+ self,
54
+ target_items: str = "",
55
+ max_buffer_size_mb: int | None = None,
56
+ ) -> AsyncGenerator[list[dict[str, Any]], None]:
57
+ if max_buffer_size_mb is None:
58
+ max_buffer_size_mb = ocean_context.ocean.config.streaming.max_buffer_size_mb
59
+
60
+ events = ijson.sendable_list()
61
+ coro = ijson.items_coro(events, target_items)
62
+ current_buffer_size = 0
63
+ async for chunk in self._byte_stream():
64
+ coro.send(chunk)
65
+ current_buffer_size += len(chunk)
66
+ if current_buffer_size >= max_buffer_size_mb:
67
+ if len(events) > 0:
68
+ yield events
69
+ events.clear()
70
+ current_buffer_size = 0
71
+ yield events
@@ -3,16 +3,16 @@ import logging
3
3
  import sys
4
4
  import threading
5
5
  import time
6
+ from copy import deepcopy
6
7
  from datetime import datetime
7
8
  from logging.handlers import MemoryHandler
9
+ from traceback import format_exception
8
10
  from typing import Any
9
11
 
10
12
  from loguru import logger
11
13
 
12
14
  from port_ocean import Ocean
13
15
  from port_ocean.context.ocean import ocean
14
- from copy import deepcopy
15
- from traceback import format_exception
16
16
 
17
17
 
18
18
  def _serialize_record(record: logging.LogRecord) -> dict[str, Any]:
@@ -53,7 +53,6 @@ class HTTPMemoryHandler(MemoryHandler):
53
53
  return None
54
54
 
55
55
  def emit(self, record: logging.LogRecord) -> None:
56
-
57
56
  self._serialized_buffer.append(_serialize_record(record))
58
57
  super().emit(record)
59
58
 
@@ -106,4 +105,4 @@ class HTTPMemoryHandler(MemoryHandler):
106
105
  try:
107
106
  await _ocean.port_client.ingest_integration_logs(logs_to_send)
108
107
  except Exception as e:
109
- logger.debug(f"Failed to send logs to Port with error: {e}")
108
+ logger.error(f"Failed to send logs to Port with error: {e}")
port_ocean/ocean.py CHANGED
@@ -1,21 +1,18 @@
1
1
  import asyncio
2
2
  import sys
3
- from contextlib import asynccontextmanager
4
3
  import threading
4
+ from contextlib import asynccontextmanager
5
5
  from typing import Any, AsyncIterator, Callable, Dict, Type
6
6
 
7
- from port_ocean.cache.base import CacheProvider
8
- from port_ocean.cache.disk import DiskCacheProvider
9
- from port_ocean.cache.memory import InMemoryCacheProvider
10
- from port_ocean.core.models import ProcessExecutionMode
11
- import port_ocean.helpers.metric.metric
12
-
13
- from fastapi import FastAPI, APIRouter
14
-
7
+ from fastapi import APIRouter, FastAPI
15
8
  from loguru import logger
16
9
  from pydantic import BaseModel
17
10
  from starlette.types import Receive, Scope, Send
18
11
 
12
+ import port_ocean.helpers.metric.metric
13
+ from port_ocean.cache.base import CacheProvider
14
+ from port_ocean.cache.disk import DiskCacheProvider
15
+ from port_ocean.cache.memory import InMemoryCacheProvider
19
16
  from port_ocean.clients.port.client import PortClient
20
17
  from port_ocean.config.settings import (
21
18
  IntegrationConfiguration,
@@ -26,16 +23,17 @@ from port_ocean.context.ocean import (
26
23
  ocean,
27
24
  )
28
25
  from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
26
+ from port_ocean.core.handlers.webhook.processor_manager import (
27
+ LiveEventsProcessorManager,
28
+ )
29
29
  from port_ocean.core.integrations.base import BaseIntegration
30
+ from port_ocean.core.models import ProcessExecutionMode
30
31
  from port_ocean.log.sensetive import sensitive_log_filter
31
32
  from port_ocean.middlewares import request_handler
32
33
  from port_ocean.utils.misc import IntegrationStateStatus
33
34
  from port_ocean.utils.repeat import repeat_every
34
35
  from port_ocean.utils.signal import signal_handler
35
36
  from port_ocean.version import __integration_version__
36
- from port_ocean.core.handlers.webhook.processor_manager import (
37
- LiveEventsProcessorManager,
38
- )
39
37
 
40
38
 
41
39
  class Ocean:
@@ -69,6 +67,7 @@ class Ocean:
69
67
  integration_identifier=self.config.integration.identifier,
70
68
  integration_type=self.config.integration.type,
71
69
  integration_version=__integration_version__,
70
+ ingest_url=self.config.port.ingest_url,
72
71
  )
73
72
  self.cache_provider: CacheProvider = self._get_caching_provider()
74
73
  self.process_execution_mode: ProcessExecutionMode = (
@@ -96,7 +96,13 @@ def mock_http_client() -> MagicMock:
96
96
  @pytest.fixture
97
97
  def mock_port_client(mock_http_client: MagicMock) -> PortClient:
98
98
  mock_port_client = PortClient(
99
- MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
99
+ MagicMock(),
100
+ MagicMock(),
101
+ MagicMock(),
102
+ MagicMock(),
103
+ MagicMock(),
104
+ MagicMock(),
105
+ MagicMock(),
100
106
  )
101
107
  mock_port_client.auth = AsyncMock()
102
108
  mock_port_client.auth.headers = AsyncMock(
@@ -0,0 +1,70 @@
1
+ from port_ocean.core.event_listener.kafka import KafkaEventListenerSettings
2
+ import pytest
3
+ from pydantic import ValidationError
4
+
5
+
6
+ def test_default_kafka_settings() -> None:
7
+ """Test default values are properly set"""
8
+ config = KafkaEventListenerSettings(type="KAFKA")
9
+ assert config.type == "KAFKA"
10
+ assert config.security_protocol == "SASL_SSL"
11
+ assert config.authentication_mechanism == "SCRAM-SHA-512"
12
+ assert config.kafka_security_enabled is True
13
+ assert config.consumer_poll_timeout == 1
14
+ assert "b-1-public.publicclusterprod" in config.brokers
15
+
16
+
17
+ def test_brokers_json_array_parsing() -> None:
18
+ """Test that JSON array strings get converted to comma-separated"""
19
+ json_brokers = '["broker1:9092", "broker2:9092", "broker3:9092"]'
20
+ config = KafkaEventListenerSettings(type="KAFKA", brokers=json_brokers)
21
+ assert config.brokers == "broker1:9092,broker2:9092,broker3:9092"
22
+
23
+
24
+ def test_brokers_regular_string_unchanged() -> None:
25
+ """Test that regular comma-separated strings pass through unchanged"""
26
+ regular_brokers = "broker1:9092,broker2:9092"
27
+ config = KafkaEventListenerSettings(type="KAFKA", brokers=regular_brokers)
28
+ assert config.brokers == regular_brokers
29
+
30
+
31
+ def test_brokers_malformed_json_unchanged() -> None:
32
+ """Test that malformed JSON strings don't break validation"""
33
+ bad_json = "[broker1:9092, broker2:9092"
34
+ config = KafkaEventListenerSettings(type="KAFKA", brokers=bad_json)
35
+ assert config.brokers == bad_json
36
+
37
+
38
+ def test_custom_values() -> None:
39
+ """Test overriding default values"""
40
+ config = KafkaEventListenerSettings(
41
+ type="KAFKA",
42
+ brokers="custom:9092",
43
+ security_protocol="PLAINTEXT",
44
+ authentication_mechanism="PLAIN",
45
+ kafka_security_enabled=False,
46
+ consumer_poll_timeout=5,
47
+ )
48
+ assert config.brokers == "custom:9092"
49
+ assert config.security_protocol == "PLAINTEXT"
50
+ assert config.authentication_mechanism == "PLAIN"
51
+ assert config.kafka_security_enabled is False
52
+ assert config.consumer_poll_timeout == 5
53
+
54
+
55
+ def test_type_literal_validation() -> None:
56
+ """Test that type field only accepts KAFKA"""
57
+ with pytest.raises(ValidationError):
58
+ KafkaEventListenerSettings(type="RABBITMQ") # type: ignore[arg-type]
59
+
60
+
61
+ def test_empty_brokers_array() -> None:
62
+ """Test empty JSON array becomes empty string"""
63
+ config = KafkaEventListenerSettings(type="KAFKA", brokers="[]")
64
+ assert config.brokers == ""
65
+
66
+
67
+ def test_single_broker_array() -> None:
68
+ """Test single broker in JSON array"""
69
+ config = KafkaEventListenerSettings(type="KAFKA", brokers='["single:9092"]')
70
+ assert config.brokers == "single:9092"
@@ -14,8 +14,8 @@ from port_ocean.context.event import event_context, EventType
14
14
 
15
15
 
16
16
  @pytest.mark.asyncio
17
- async def test_delete_diff_no_deleted_entities() -> None:
18
- applier = HttpEntitiesStateApplier(Mock())
17
+ async def test_delete_diff_no_deleted_entities(mock_context: PortOceanContext) -> None:
18
+ applier = HttpEntitiesStateApplier(mock_context)
19
19
  entities = EntityDiff(
20
20
  before=[Entity(identifier="1", blueprint="test")],
21
21
  after=[Entity(identifier="1", blueprint="test")],
@@ -60,7 +60,7 @@ class TestJQEntityProcessor:
60
60
  raw_entity_mappings = {"foo": ".foo"}
61
61
  selector_query = '.foo == "bar"'
62
62
  result, errors = await mocked_processor._calculate_entity(
63
- data, raw_entity_mappings, None, selector_query
63
+ data, raw_entity_mappings, None, "item", selector_query
64
64
  )
65
65
  assert len(result) == 1
66
66
  assert result[0].entity == {"foo": "bar"}
@@ -1,7 +1,9 @@
1
1
  from typing import Any
2
- from httpx import Response
3
- import pytest
4
2
  from unittest.mock import AsyncMock, MagicMock, patch
3
+
4
+ import pytest
5
+ from httpx import Response
6
+
5
7
  from port_ocean.clients.port.client import PortClient
6
8
  from port_ocean.clients.port.types import UserAgentType
7
9
  from port_ocean.context.ocean import PortOceanContext
@@ -11,8 +13,6 @@ from port_ocean.core.handlers.entities_state_applier.port.applier import (
11
13
  from port_ocean.core.handlers.entity_processor.jq_entity_processor import (
12
14
  JQEntityProcessor,
13
15
  )
14
- from port_ocean.core.handlers.webhook.webhook_event import WebhookEventRawResults
15
- from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
16
16
  from port_ocean.core.handlers.port_app_config.models import (
17
17
  EntityMapping,
18
18
  MappingsConfig,
@@ -21,6 +21,8 @@ from port_ocean.core.handlers.port_app_config.models import (
21
21
  ResourceConfig,
22
22
  Selector,
23
23
  )
24
+ from port_ocean.core.handlers.webhook.webhook_event import WebhookEventRawResults
25
+ from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
24
26
  from port_ocean.core.models import Entity
25
27
  from port_ocean.core.ocean_types import CalculationResult, EntitySelectorDiff
26
28
  from port_ocean.ocean import Ocean
@@ -253,7 +255,13 @@ def mock_port_app_config_handler(
253
255
  @pytest.fixture
254
256
  def mock_port_client(mock_http_client: MagicMock) -> PortClient:
255
257
  mock_port_client = PortClient(
256
- MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
258
+ MagicMock(),
259
+ MagicMock(),
260
+ MagicMock(),
261
+ MagicMock(),
262
+ MagicMock(),
263
+ MagicMock(),
264
+ MagicMock(),
257
265
  )
258
266
  mock_port_client.auth = AsyncMock()
259
267
  mock_port_client.auth.headers = AsyncMock(
@@ -340,10 +348,11 @@ async def test_parse_raw_event_results_to_entities_creation(
340
348
  calculation_result
341
349
  )
342
350
 
343
- entities_to_create, entities_to_delete = (
344
- await mock_live_events_mixin._parse_raw_event_results_to_entities(
345
- [one_webhook_event_raw_results_for_creation]
346
- )
351
+ (
352
+ entities_to_create,
353
+ entities_to_delete,
354
+ ) = await mock_live_events_mixin._parse_raw_event_results_to_entities(
355
+ [one_webhook_event_raw_results_for_creation]
347
356
  )
348
357
 
349
358
  assert entities_to_create == [entity]
@@ -367,10 +376,11 @@ async def test_parse_raw_event_results_to_entities_deletion(
367
376
  calculation_result
368
377
  )
369
378
 
370
- entities_to_create, entities_to_delete = (
371
- await mock_live_events_mixin._parse_raw_event_results_to_entities(
372
- [one_webhook_event_raw_results_for_deletion]
373
- )
379
+ (
380
+ entities_to_create,
381
+ entities_to_delete,
382
+ ) = await mock_live_events_mixin._parse_raw_event_results_to_entities(
383
+ [one_webhook_event_raw_results_for_deletion]
374
384
  )
375
385
 
376
386
  assert entities_to_create == []
@@ -1,32 +1,16 @@
1
- import pytest
2
- from port_ocean.core.handlers.webhook.processor_manager import (
3
- LiveEventsProcessorManager,
4
- )
5
- from port_ocean.core.handlers.webhook.abstract_webhook_processor import (
6
- AbstractWebhookProcessor,
7
- )
8
- from port_ocean.core.handlers.webhook.webhook_event import (
9
- EventHeaders,
10
- WebhookEvent,
11
- WebhookEventRawResults,
12
- EventPayload,
13
- )
14
- from fastapi import APIRouter
15
- from port_ocean.core.integrations.mixins.handler import HandlerMixin
16
- from port_ocean.utils.signal import SignalHandler
17
- from typing import Dict, Any
18
1
  import asyncio
2
+ from typing import Any, Dict
3
+ from unittest.mock import AsyncMock, MagicMock, patch
4
+
5
+ import pytest
6
+ from fastapi import APIRouter, FastAPI
19
7
  from fastapi.testclient import TestClient
20
- from fastapi import FastAPI
21
- from port_ocean.context.ocean import PortOceanContext
22
- from unittest.mock import AsyncMock
23
- from port_ocean.context.event import EventContext, event_context, EventType
24
- from port_ocean.context.ocean import ocean
25
- from unittest.mock import MagicMock, patch
26
8
  from httpx import Response
27
- from port_ocean.clients.port.client import PortClient
9
+
28
10
  from port_ocean import Ocean
29
- from port_ocean.core.integrations.base import BaseIntegration
11
+ from port_ocean.clients.port.client import PortClient
12
+ from port_ocean.context.event import EventContext, EventType, event_context
13
+ from port_ocean.context.ocean import PortOceanContext, ocean
30
14
  from port_ocean.core.handlers.port_app_config.models import (
31
15
  EntityMapping,
32
16
  MappingsConfig,
@@ -35,13 +19,28 @@ from port_ocean.core.handlers.port_app_config.models import (
35
19
  ResourceConfig,
36
20
  Selector,
37
21
  )
22
+ from port_ocean.core.handlers.queue import LocalQueue
23
+ from port_ocean.core.handlers.webhook.abstract_webhook_processor import (
24
+ AbstractWebhookProcessor,
25
+ )
26
+ from port_ocean.core.handlers.webhook.processor_manager import (
27
+ LiveEventsProcessorManager,
28
+ )
29
+ from port_ocean.core.handlers.webhook.webhook_event import (
30
+ EventHeaders,
31
+ EventPayload,
32
+ WebhookEvent,
33
+ WebhookEventRawResults,
34
+ )
35
+ from port_ocean.core.integrations.base import BaseIntegration
36
+ from port_ocean.core.integrations.mixins.handler import HandlerMixin
38
37
  from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
39
38
  from port_ocean.core.models import Entity
40
39
  from port_ocean.exceptions.webhook_processor import (
41
40
  RetryableError,
42
41
  WebhookEventNotSupportedError,
43
42
  )
44
- from port_ocean.core.handlers.queue import LocalQueue
43
+ from port_ocean.utils.signal import SignalHandler
45
44
 
46
45
 
47
46
  class MockProcessor(AbstractWebhookProcessor):
@@ -275,7 +274,13 @@ def mock_http_client() -> MagicMock:
275
274
  @pytest.fixture
276
275
  def mock_port_client(mock_http_client: MagicMock) -> PortClient:
277
276
  mock_port_client = PortClient(
278
- MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock(), MagicMock()
277
+ MagicMock(),
278
+ MagicMock(),
279
+ MagicMock(),
280
+ MagicMock(),
281
+ MagicMock(),
282
+ MagicMock(),
283
+ MagicMock(),
279
284
  )
280
285
  mock_port_client.auth = AsyncMock()
281
286
  mock_port_client.auth.headers = AsyncMock(
@@ -18,4 +18,5 @@ def get_port_client_for_integration(
18
18
  integration_identifier=integration_identifier,
19
19
  integration_type=integration_type,
20
20
  integration_version=integration_version,
21
+ ingest_url="https://ingest.getport.io",
21
22
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.27.0
3
+ Version: 0.27.10
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
@@ -22,12 +22,15 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Utilities
24
24
  Provides-Extra: cli
25
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
25
26
  Requires-Dist: aiostream (>=0.5.2,<0.7.0)
26
27
  Requires-Dist: click (>=8.1.3,<9.0.0) ; extra == "cli"
27
28
  Requires-Dist: confluent-kafka (>=2.10.1,<3.0.0)
28
29
  Requires-Dist: cookiecutter (>=2.1.1,<3.0.0) ; extra == "cli"
29
- Requires-Dist: fastapi (>=0.115.3,<0.116.0)
30
+ Requires-Dist: cryptography (>=44.0.1,<45.0.0)
31
+ Requires-Dist: fastapi (>=0.116.0,<0.117.0)
30
32
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
33
+ Requires-Dist: ijson (>=3.4.0,<4.0.0)
31
34
  Requires-Dist: jinja2 (>=3.1.6)
32
35
  Requires-Dist: jinja2-time (>=0.2.0,<0.3.0) ; extra == "cli"
33
36
  Requires-Dist: jq (>=1.8.0,<2.0.0)
@@ -1,9 +1,9 @@
1
- integrations/_infra/Dockerfile.Deb,sha256=ovmwNBRNrblTW6K9ru2FNTiT9qDgI7_zY28O__VMW6I,2367
1
+ integrations/_infra/Dockerfile.Deb,sha256=weK3VrskDEsuvYaxerak-XIqDgTx_wPorAFQQjYH_FU,2493
2
2
  integrations/_infra/Dockerfile.alpine,sha256=7E4Sb-8supsCcseerHwTkuzjHZoYcaHIyxiBZ-wewo0,3482
3
3
  integrations/_infra/Dockerfile.base.builder,sha256=ESe1PKC6itp_AuXawbLI75k1Kruny6NTANaTinxOgVs,743
4
4
  integrations/_infra/Dockerfile.base.runner,sha256=uAcs2IsxrAAUHGXt_qULA5INr-HFguf5a5fCKiqEzbY,384
5
5
  integrations/_infra/Dockerfile.dockerignore,sha256=CM1Fxt3I2AvSvObuUZRmy5BNLSGC7ylnbpWzFgD4cso,1163
6
- integrations/_infra/Dockerfile.local,sha256=v-K0jgDj5vWyF4HCL8-wluUPB04pf9ycEjusRBmIVnc,1527
6
+ integrations/_infra/Dockerfile.local,sha256=FFX9RvFqlaHvhUrRnnzUl0zQp2oKDFVRGkXJQPMQ7cI,1650
7
7
  integrations/_infra/Makefile,sha256=YgLKvuF_Dw4IA7X98Nus6zIW_3cJ60M1QFGs3imj5c4,2430
8
8
  integrations/_infra/README.md,sha256=ZtJFSMCTU5zTeM8ddRuW1ZL1ga8z7Ic2F3mxmgOSjgo,1195
9
9
  integrations/_infra/entry_local.sh,sha256=Sn2TexTEpruH2ixIAGsk-fZV6Y7pT3jd2Pi9TxBeFuw,633
@@ -56,12 +56,12 @@ port_ocean/clients/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
56
56
  port_ocean/clients/auth/auth_client.py,sha256=scxx7AYqvXoRAd8_K-Ww26oErzi5l8ZCGPc0sVKgIfA,192
57
57
  port_ocean/clients/auth/oauth_client.py,sha256=FjexH-T3v4ssRWhkHtvHXhi1EH1Vxu8vwHp3HxqfYN8,795
58
58
  port_ocean/clients/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- port_ocean/clients/port/authentication.py,sha256=r7r8Ag9WuwXy-CmgeOoj-PHbmJAQxhb2NMx8wrNzF3g,3457
60
- port_ocean/clients/port/client.py,sha256=dv0mxIOde6J-wFi1FXXZkoNPVHrZzY7RSMhNkDD9xgA,3566
59
+ port_ocean/clients/port/authentication.py,sha256=ZO1Vw1nm-NlVUPPtPS5O4GGDvRmyS3vnayWKyVyuuKc,3519
60
+ port_ocean/clients/port/client.py,sha256=hBXgU0CDseN2F-vn20JqowfVkcd6oSVmYrjn6t4TI6c,3616
61
61
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  port_ocean/clients/port/mixins/blueprints.py,sha256=aMCG4zePsMSMjMLiGrU37h5z5_ElfMzTcTvqvOI5wXY,4683
63
63
  port_ocean/clients/port/mixins/entities.py,sha256=X2NqH00eK6TMJ3a3QEQRVQlKHzyj5l1FiPkIhonnbPg,24234
64
- port_ocean/clients/port/mixins/integrations.py,sha256=9G1vo3n9pG1t6siUmPdYtxXbfhGXKhWAWwKHr8x7tU4,11891
64
+ port_ocean/clients/port/mixins/integrations.py,sha256=5OK21zU9vBk1-SuweiQzkXP_VxofVO1cqL7Ipw-X-YM,11940
65
65
  port_ocean/clients/port/mixins/migrations.py,sha256=vdL_A_NNUogvzujyaRLIoZEu5vmKDY2BxTjoGP94YzI,1467
66
66
  port_ocean/clients/port/mixins/organization.py,sha256=A2cP5V49KnjoAXxjmnm_XGth4ftPSU0qURNfnyUyS_Y,1041
67
67
  port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
@@ -70,7 +70,7 @@ port_ocean/clients/port/utils.py,sha256=osFyAjw7Y5Qf2uVSqC7_RTCQfijiL1zS74JJM0go
70
70
  port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  port_ocean/config/base.py,sha256=x1gFbzujrxn7EJudRT81C6eN9WsYAb3vOHwcpcpX8Tc,6370
72
72
  port_ocean/config/dynamic.py,sha256=Lrk4JRGtR-0YKQ9DDGexX5NGFE7EJ6VoHya19YYhssM,2687
73
- port_ocean/config/settings.py,sha256=keRT2FJyzQ2G0LNlNP4mqYSwwTjbybL7Ire0oggwScw,7226
73
+ port_ocean/config/settings.py,sha256=pknMyy3T8FP8laCxYNfIIGEjeYFd8kQA1eSPreerBAE,7768
74
74
  port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
76
76
  port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -87,7 +87,7 @@ port_ocean/core/event_listener/__init__.py,sha256=T3E52MKs79fNEW381p7zU9F2vOMvIi
87
87
  port_ocean/core/event_listener/base.py,sha256=GFBTHiYhCzps50phzopQFUlTGAluQkCRlyaRqOG4g1Y,2995
88
88
  port_ocean/core/event_listener/factory.py,sha256=M4Qi05pI840sjDIbdjUEgYe9Gp5ckoCkX-KgLBxUpZg,4096
89
89
  port_ocean/core/event_listener/http.py,sha256=_hkQmi9nNh8YG6hbfLrhkATsmGVO8y3qBWvrBHX5Nhk,2992
90
- port_ocean/core/event_listener/kafka.py,sha256=pVQfGtElIJN2P5lPBrMxyl05B8c2Q2wp5WvA6Pqjnyc,7501
90
+ port_ocean/core/event_listener/kafka.py,sha256=7bUq4EoVv5sIBxiJnfhi7zdAI7whKoSIaS64Ic9B4Ys,7963
91
91
  port_ocean/core/event_listener/once.py,sha256=6DhxQLSz5hjEGhmgLU7aA2ZVRQw3tuwh8sh7N63gRdU,5855
92
92
  port_ocean/core/event_listener/polling.py,sha256=iZt59z4Dyus_wVvNfIPbq-G1CozAW9kfPU7uRDLPJVE,3604
93
93
  port_ocean/core/event_listener/webhooks_only.py,sha256=PTWnmbLtbJb3ySfotMpTWMYgDVy9zOSYIIGqNbWK0UU,1214
@@ -96,22 +96,22 @@ port_ocean/core/handlers/base.py,sha256=cTarblazu8yh8xz2FpB-dzDKuXxtoi143XJgPbV_
96
96
  port_ocean/core/handlers/entities_state_applier/__init__.py,sha256=kgLZDCeCEzi4r-0nzW9k78haOZNf6PX7mJOUr34A4c8,173
97
97
  port_ocean/core/handlers/entities_state_applier/base.py,sha256=5wHL0icfFAYRPqk8iV_wN49GdJ3aRUtO8tumSxBi4Wo,2268
98
98
  port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
- port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=NsLW35H1dhP5FFD7kHZxA2ndSBcD_-btan7qNWfvWrs,6683
99
+ port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=xNfDAstW1z9r46BCiuXApeNbEEfel4qfMzB2ssuZJQ8,7010
100
100
  port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=XKDIexoDPVWT2-Y1UTKqJJyRUWC3qiHKQD8bozIrni4,2038
101
101
  port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=lyv6xKzhYfd6TioUgR3AVRSJqj7JpAaj1LxxU2xAqeo,1720
102
102
  port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
103
103
  port_ocean/core/handlers/entity_processor/base.py,sha256=PsnpNRqjHth9xwOvDRe7gKu8cjnVV0XGmTIHGvOelX0,1867
104
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=GxK5BM0yzAoeYTK2-ALKh5qlAe9pebcN7vZfQCZjksM,12787
104
+ port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=qvPMbIH1XRvaZ-TvW7lw9k4W27ZPCHcXGSdqnZ0wblw,12970
105
105
  port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
106
106
  port_ocean/core/handlers/port_app_config/api.py,sha256=r_Th66NEw38IpRdnXZcRvI8ACfvxW_A6V62WLwjWXlQ,1044
107
107
  port_ocean/core/handlers/port_app_config/base.py,sha256=Sup4-X_a7JGa27rMy_OgqGIjFHMlKBpKevicaK3AeHU,2919
108
- port_ocean/core/handlers/port_app_config/models.py,sha256=pO7oI7GIYZ9c2ZxLu8EQ97U2IPqzsbJf3gRQxlizEjE,2933
108
+ port_ocean/core/handlers/port_app_config/models.py,sha256=M9NJRRacvpZQYCFgHGlRwrdgumbIIJ82WpCyvtOUf5w,3019
109
109
  port_ocean/core/handlers/queue/__init__.py,sha256=yzgicE_jAR1wtljFKxgyG6j-HbLcG_Zze5qw1kkALUI,171
110
110
  port_ocean/core/handlers/queue/abstract_queue.py,sha256=SaivrYbqg8qsX6wtQlJZyxgcbdMD5B9NZG3byN9AvrI,782
111
111
  port_ocean/core/handlers/queue/group_queue.py,sha256=JvvJOwz9z_aI4CjPr7yQX-0rOgqLI5wMdxWk2x5x-34,4989
112
112
  port_ocean/core/handlers/queue/local_queue.py,sha256=Y6qabDbrQ8aOPTN6Ct3lnMU7JnT8O8iTpoxMoVt6lFs,643
113
113
  port_ocean/core/handlers/resync_state_updater/__init__.py,sha256=kG6y-JQGpPfuTHh912L_bctIDCzAK4DN-d00S7rguWU,81
114
- port_ocean/core/handlers/resync_state_updater/updater.py,sha256=TRYq6QnTtPlJg6MvgZPtQdZPvkAhkvpcmWjtkxCnkg4,3764
114
+ port_ocean/core/handlers/resync_state_updater/updater.py,sha256=R7K0n-MljKtOFEh2XZKi_fBUOWIdt4X6IZIw-2WMtv0,3813
115
115
  port_ocean/core/handlers/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
116
  port_ocean/core/handlers/webhook/abstract_webhook_processor.py,sha256=5KwZkdkDd5HdVkXPzKiqabodZKl-hOtMypkTKd8Hq3M,3891
117
117
  port_ocean/core/handlers/webhook/processor_manager.py,sha256=0KRPD1ae-7w0na2AZY-rq9_gY0IaMv9LdwEh6y4_OiQ,13282
@@ -123,8 +123,8 @@ port_ocean/core/integrations/mixins/events.py,sha256=2L7P3Jhp8XBqddh2_o9Cn4N261n
123
123
  port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
124
124
  port_ocean/core/integrations/mixins/live_events.py,sha256=zM24dhNc7uHx9XYZ6toVhDADPA90EnpOmZxgDegFZbA,4196
125
125
  port_ocean/core/integrations/mixins/sync.py,sha256=Vm_898pLKBwfVewtwouDWsXoxcOLicnAy6pzyqqk6U8,4053
126
- port_ocean/core/integrations/mixins/sync_raw.py,sha256=6AHuroSoqIKvdx2BAKtILxfQFeYB_wikYaM4JaTeoGw,39315
127
- port_ocean/core/integrations/mixins/utils.py,sha256=N76dNu1Y6UEg0_pcSdF4RO6dQIZ8EBfX3xMelgWdMxM,3779
126
+ port_ocean/core/integrations/mixins/sync_raw.py,sha256=50tklPFBYvVoucIcCx1yYU-gwzliXCOX7kJP6_dgsNI,40631
127
+ port_ocean/core/integrations/mixins/utils.py,sha256=ytnFX7Lyv6N3CgBnOXxYaI1cRDq5Z4NDrVFiwE6bn-M,5250
128
128
  port_ocean/core/models.py,sha256=DNbKpStMINI2lIekKprTqBevqkw_wFuFayN19w1aDfQ,2893
129
129
  port_ocean/core/ocean_types.py,sha256=bkLlTd8XfJK6_JDl0eXUHfE_NygqgiInSMwJ4YJH01Q,1399
130
130
  port_ocean/core/utils/entity_topological_sorter.py,sha256=MDUjM6OuDy4Xj68o-7InNN0w1jqjxeDfeY8U02vySNI,3081
@@ -140,16 +140,17 @@ port_ocean/exceptions/port_defaults.py,sha256=2a7Koy541KxMan33mU-gbauUxsumG3NT4i
140
140
  port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnqI,197
141
141
  port_ocean/exceptions/webhook_processor.py,sha256=4SnkVzVwiacH_Ip4qs1hRHa6GanhnojW_TLTdQQtm7Y,363
142
142
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
- port_ocean/helpers/async_client.py,sha256=0MkFY46MuEYkju7gJ8HYeUTMBsjwAzESvATryJy2DaM,1698
144
- port_ocean/helpers/metric/metric.py,sha256=Aacz7bOd8ZCwEPpXAdwLbKRXf28Z4wiViG_GXiV_xWg,14529
143
+ port_ocean/helpers/async_client.py,sha256=LOgUlZ5Cs_WUSc8XujCVjPGvzZ_3AuFJNKPy0FKV3fA,1987
144
+ port_ocean/helpers/metric/metric.py,sha256=-dw7-Eqr65AZwv0M-xPaAk98g_JS16ICBc9_UkycFbE,14543
145
145
  port_ocean/helpers/metric/utils.py,sha256=1lAgrxnZLuR_wUNDyPOPzLrm32b8cDdioob2lvnPQ1A,1619
146
146
  port_ocean/helpers/retry.py,sha256=VHAp6j9-Vid6aNR5sca3S0aW6b1S2oYw9vT9hi1N22U,18556
147
+ port_ocean/helpers/stream.py,sha256=_UwsThzXynxWzL8OlBT1pmb2evZBi9HaaqeAGNuTuOI,2338
147
148
  port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
148
- port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,3631
149
+ port_ocean/log/handlers.py,sha256=LJ1WAfq7wYCrBpeLPihMKmWjdSahKKXNHFMRYkbk0Co,3630
149
150
  port_ocean/log/logger_setup.py,sha256=0K3zVG0YYrYOWEV8-rCGks1o-bMRxgHXlqawu9w_tSw,2656
150
151
  port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
151
152
  port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
152
- port_ocean/ocean.py,sha256=83zgTEI5o2wfl8mq-iIC9DzPOZbWyCqNIy1BEjv9TOk,8824
153
+ port_ocean/ocean.py,sha256=IhsLnPqEJ2SflnBAt-byxGl9w_ULkdvd6-sxBbVQtJw,8874
153
154
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
154
155
  port_ocean/run.py,sha256=CmKz14bxfdOooNbQ5QqH1MwX-XLYVG4NgT4KbrzFaqI,2216
155
156
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
@@ -165,18 +166,19 @@ port_ocean/tests/clients/port/mixins/test_integrations.py,sha256=vRt_EMsLozQC1LJ
165
166
  port_ocean/tests/clients/port/mixins/test_organization_mixin.py,sha256=zzKYz3h8dl4Z5A2QG_924m0y9U6XTth1XYOfwNrd_24,914
166
167
  port_ocean/tests/config/test_config.py,sha256=Rk4N-ldVSOfn1p23NzdVdfqUpPrqG2cMut4Sv-sAOrw,1023
167
168
  port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
168
- port_ocean/tests/core/conftest.py,sha256=5shShx81LRuQTBwS2sDIjNJO2LSD6cUNz46SgNYzjGY,7686
169
+ port_ocean/tests/core/conftest.py,sha256=0Oql7R1iTbjPyNdUoO6M21IKknLwnCIgDRz2JQ7nf0w,7748
169
170
  port_ocean/tests/core/defaults/test_common.py,sha256=sR7RqB3ZYV6Xn6NIg-c8k5K6JcGsYZ2SCe_PYX5vLYM,5560
170
- port_ocean/tests/core/handlers/entities_state_applier/test_applier.py,sha256=_CZyViY9_gnxjY6ogWcDdmEDuejvpALogf9ESjVAwFY,10675
171
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=AyzLclJFKz8YzSi2hiFYVdXRzCFmqQwTxnwNFTo9roU,14091
172
- port_ocean/tests/core/handlers/mixins/test_live_events.py,sha256=6yUsYooBYchiZP_eYa8PN1IhiztJShBdPguoseyNMzY,12482
171
+ port_ocean/tests/core/event_listener/test_kafka.py,sha256=PH90qk2fvdrQOSZD2QrvkGy8w_WoYb_KHGnqJ6PLHAo,2681
172
+ port_ocean/tests/core/handlers/entities_state_applier/test_applier.py,sha256=7XWgwUB9uVYRov4VbIz1A-7n2YLbHTTYT-4rKJxjB0A,10711
173
+ port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=TjSj8ssIqH23VJlO5PGovbudCqDbuE2-54iNQsD9K-I,14099
174
+ port_ocean/tests/core/handlers/mixins/test_live_events.py,sha256=Sbv9IZAGQoZDhf27xDjMMVYxUSie9mHltDtxLSqckmM,12548
173
175
  port_ocean/tests/core/handlers/mixins/test_sync_raw.py,sha256=-Jd2rUG63fZM8LuyKtCp1tt4WEqO2m5woESjs1c91sU,44428
174
176
  port_ocean/tests/core/handlers/port_app_config/test_api.py,sha256=eJZ6SuFBLz71y4ca3DNqKag6d6HUjNJS0aqQPwiLMTI,1999
175
177
  port_ocean/tests/core/handlers/port_app_config/test_base.py,sha256=hSh556bJM9zuELwhwnyKSfd9z06WqWXIfe-6hCl5iKI,9799
176
178
  port_ocean/tests/core/handlers/queue/test_group_queue.py,sha256=Y1BrQi5xwhk5bYDlKRWw9PenF5cqxIF2TIU_hldqji0,22801
177
179
  port_ocean/tests/core/handlers/queue/test_local_queue.py,sha256=9Ly0HzZXbs6Rbl_bstsIdInC3h2bgABU3roP9S_PnJM,2582
178
180
  port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py,sha256=zKwHhPAYEZoZ5Z2UETp1t--mbkS8uyvlXThB0obZTTc,3340
179
- port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=rqNFc-S_ZnPyDTSFTdiGcRFKbeDGfWQCH_f2UPbfcAA,52310
181
+ port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=16q-5NagXBv8J1TZC75h-9N4MW_8KB4GX2mmPD6gr4s,52294
180
182
  port_ocean/tests/core/handlers/webhook/test_webhook_event.py,sha256=oR4dEHLO65mp6rkfNfszZcfFoRZlB8ZWee4XetmsuIk,3181
181
183
  port_ocean/tests/core/test_utils.py,sha256=Z3kdhb5V7Svhcyy3EansdTpgHL36TL6erNtU-OPwAcI,2647
182
184
  port_ocean/tests/core/utils/test_entity_topological_sorter.py,sha256=zuq5WSPy_88PemG3mOUIHTxWMR_js1R7tOzUYlgBd68,3447
@@ -186,7 +188,7 @@ port_ocean/tests/helpers/fake_port_api.py,sha256=9rtjC6iTQMfzWK6WipkDzzG0b1IIaRm
186
188
  port_ocean/tests/helpers/fixtures.py,sha256=dinEucKDTGAD2tKwbOqqHZSHNPsDLN2HxnLqA-WXGeI,1443
187
189
  port_ocean/tests/helpers/integration.py,sha256=_RxS-RHpu11lrbhUXYPZp862HLWx8AoD7iZM6iXN8rs,1104
188
190
  port_ocean/tests/helpers/ocean_app.py,sha256=N06vcNI1klqdcNFq-PXL5vm77u-hODsOSXnj9p8d1AI,2249
189
- port_ocean/tests/helpers/port_client.py,sha256=5d6GNr8vNNSOkrz1AdOhxBUKuusr_-UPDP7AVpHasQw,599
191
+ port_ocean/tests/helpers/port_client.py,sha256=S0CXvZWUoHFWWQUOEgdkDammK9Fs3R06wx0flaMrTsg,647
190
192
  port_ocean/tests/helpers/smoke_test.py,sha256=_9aJJFRfuGJEg2D2YQJVJRmpreS6gEPHHQq8Q01x4aQ,2697
191
193
  port_ocean/tests/log/test_handlers.py,sha256=x2P2Hd6Cb3sQafIE3TRGltbbHeiFHaiEjwRn9py_03g,2165
192
194
  port_ocean/tests/test_metric.py,sha256=gDdeJcqJDQ_o3VrYrW23iZyw2NuUsyATdrygSXhcDuQ,8096
@@ -205,8 +207,8 @@ port_ocean/utils/repeat.py,sha256=U2OeCkHPWXmRTVoPV-VcJRlQhcYqPWI5NfmPlb1JIbc,32
205
207
  port_ocean/utils/signal.py,sha256=mMVq-1Ab5YpNiqN4PkiyTGlV_G0wkUDMMjTZp5z3pb0,1514
206
208
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
207
209
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
208
- port_ocean-0.27.0.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
209
- port_ocean-0.27.0.dist-info/METADATA,sha256=dWJb3IOgu_hp6Q1I5G4VxB3b31owoTzF7xSSgSpsGAM,6887
210
- port_ocean-0.27.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
211
- port_ocean-0.27.0.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
212
- port_ocean-0.27.0.dist-info/RECORD,,
210
+ port_ocean-0.27.10.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
211
+ port_ocean-0.27.10.dist-info/METADATA,sha256=Igj6QUVBOykcFVAw94SzlNlVos1FrGhIAINmZnLK1GM,7016
212
+ port_ocean-0.27.10.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
213
+ port_ocean-0.27.10.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
214
+ port_ocean-0.27.10.dist-info/RECORD,,