port-ocean 0.27.5__py3-none-any.whl → 0.27.7__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.
@@ -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
 
@@ -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
 
@@ -114,6 +121,8 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
114
121
  yield_items_to_parse: bool = False
115
122
  yield_items_to_parse_batch_size: int = 10
116
123
 
124
+ streaming: StreamingSettings = Field(default_factory=lambda: StreamingSettings())
125
+
117
126
  @validator("process_execution_mode")
118
127
  def validate_process_execution_mode(
119
128
  cls, process_execution_mode: ProcessExecutionMode
@@ -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)
@@ -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}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.27.5
3
+ Version: 0.27.7
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"
30
+ Requires-Dist: cryptography (>=44.0.1,<45.0.0)
29
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
@@ -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=-jgVUBiFbeVjd9GjkMH6cRLLQHhtvI9x1WNdVE5s_p0,7311
73
+ port_ocean/config/settings.py,sha256=Zz_D40EXZEm0hzNdYgwdUy_s5LbJ6iMg3Zcl2n5NLUY,7686
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
@@ -140,12 +140,13 @@ 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
143
+ port_ocean/helpers/async_client.py,sha256=LOgUlZ5Cs_WUSc8XujCVjPGvzZ_3AuFJNKPy0FKV3fA,1987
144
144
  port_ocean/helpers/metric/metric.py,sha256=Aacz7bOd8ZCwEPpXAdwLbKRXf28Z4wiViG_GXiV_xWg,14529
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
@@ -206,8 +207,8 @@ port_ocean/utils/repeat.py,sha256=U2OeCkHPWXmRTVoPV-VcJRlQhcYqPWI5NfmPlb1JIbc,32
206
207
  port_ocean/utils/signal.py,sha256=mMVq-1Ab5YpNiqN4PkiyTGlV_G0wkUDMMjTZp5z3pb0,1514
207
208
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
208
209
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
209
- port_ocean-0.27.5.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
210
- port_ocean-0.27.5.dist-info/METADATA,sha256=YxYbuOrrT9MffCzL-2qz0CxfqVKSVokwwolQfOHxSsI,6887
211
- port_ocean-0.27.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
212
- port_ocean-0.27.5.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
213
- port_ocean-0.27.5.dist-info/RECORD,,
210
+ port_ocean-0.27.7.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
211
+ port_ocean-0.27.7.dist-info/METADATA,sha256=hVAPvuv7qLhu-zJL8PTCzKljO3G_WK8QXHCW3P3OxBM,7015
212
+ port_ocean-0.27.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
213
+ port_ocean-0.27.7.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
214
+ port_ocean-0.27.7.dist-info/RECORD,,