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.
- integrations/_infra/Dockerfile.Deb +4 -1
- integrations/_infra/Dockerfile.local +3 -1
- port_ocean/clients/port/authentication.py +2 -0
- port_ocean/clients/port/client.py +5 -2
- port_ocean/clients/port/mixins/integrations.py +1 -1
- port_ocean/config/settings.py +12 -1
- port_ocean/core/event_listener/kafka.py +14 -0
- port_ocean/core/handlers/entities_state_applier/port/applier.py +9 -0
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +12 -9
- port_ocean/core/handlers/port_app_config/models.py +1 -0
- port_ocean/core/handlers/resync_state_updater/updater.py +2 -0
- port_ocean/core/integrations/mixins/sync_raw.py +22 -4
- port_ocean/core/integrations/mixins/utils.py +26 -4
- port_ocean/helpers/async_client.py +7 -0
- port_ocean/helpers/metric/metric.py +13 -11
- port_ocean/helpers/stream.py +71 -0
- port_ocean/log/handlers.py +3 -4
- port_ocean/ocean.py +11 -12
- port_ocean/tests/core/conftest.py +7 -1
- port_ocean/tests/core/event_listener/test_kafka.py +70 -0
- port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +2 -2
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +1 -1
- port_ocean/tests/core/handlers/mixins/test_live_events.py +23 -13
- port_ocean/tests/core/handlers/webhook/test_processor_manager.py +32 -27
- port_ocean/tests/helpers/port_client.py +1 -0
- {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/METADATA +5 -2
- {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/RECORD +30 -28
- {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.27.0.dist-info → port_ocean-0.27.10.dist-info}/WHEEL +0 -0
- {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.
|
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,
|
port_ocean/config/settings.py
CHANGED
@@ -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
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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):
|
@@ -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
|
-
|
1023
|
-
|
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
|
-
|
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
|
3
|
-
|
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
|
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
|
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
|
port_ocean/log/handlers.py
CHANGED
@@ -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.
|
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
|
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(),
|
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(
|
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(),
|
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
|
-
|
344
|
-
|
345
|
-
|
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
|
-
|
371
|
-
|
372
|
-
|
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
|
-
|
9
|
+
|
28
10
|
from port_ocean import Ocean
|
29
|
-
from port_ocean.
|
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.
|
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(),
|
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(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: port-ocean
|
3
|
-
Version: 0.27.
|
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:
|
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=
|
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=
|
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=
|
60
|
-
port_ocean/clients/port/client.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
127
|
-
port_ocean/core/integrations/mixins/utils.py,sha256=
|
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=
|
144
|
-
port_ocean/helpers/metric/metric.py,sha256
|
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=
|
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=
|
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=
|
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/
|
171
|
-
port_ocean/tests/core/handlers/
|
172
|
-
port_ocean/tests/core/handlers/
|
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=
|
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=
|
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.
|
209
|
-
port_ocean-0.27.
|
210
|
-
port_ocean-0.27.
|
211
|
-
port_ocean-0.27.
|
212
|
-
port_ocean-0.27.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|