port-ocean 0.12.7__py3-none-any.whl → 0.12.9__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.

Potentially problematic release.


This version of port-ocean might be problematic. Click here for more details.

@@ -3,7 +3,6 @@ from urllib.parse import quote_plus
3
3
 
4
4
  import httpx
5
5
  from loguru import logger
6
-
7
6
  from port_ocean.clients.port.authentication import PortAuthentication
8
7
  from port_ocean.clients.port.utils import handle_status_code
9
8
  from port_ocean.log.sensetive import sensitive_log_filter
@@ -11,16 +11,22 @@ from loguru import logger
11
11
 
12
12
  from port_ocean import Ocean
13
13
  from port_ocean.context.ocean import ocean
14
+ from copy import deepcopy
15
+ from traceback import format_exception
14
16
 
15
17
 
16
18
  def _serialize_record(record: logging.LogRecord) -> dict[str, Any]:
19
+ extra = {**deepcopy(record.__dict__["extra"])}
20
+ if isinstance(extra.get("exc_info"), Exception):
21
+ serialized_exception = "".join(format_exception(extra.get("exc_info")))
22
+ extra["exc_info"] = serialized_exception
17
23
  return {
18
24
  "message": record.msg,
19
25
  "level": record.levelname,
20
26
  "timestamp": datetime.utcfromtimestamp(record.created).strftime(
21
27
  "%Y-%m-%dT%H:%M:%S.%fZ"
22
28
  ),
23
- "extra": record.__dict__["extra"],
29
+ "extra": extra,
24
30
  }
25
31
 
26
32
 
@@ -37,6 +43,7 @@ class HTTPMemoryHandler(MemoryHandler):
37
43
  self.flush_size = flush_size
38
44
  self.last_flush_time = time.time()
39
45
  self._serialized_buffer: list[dict[str, Any]] = []
46
+ self._thread_pool: list[threading.Thread] = []
40
47
 
41
48
  @property
42
49
  def ocean(self) -> Ocean | None:
@@ -46,6 +53,7 @@ class HTTPMemoryHandler(MemoryHandler):
46
53
  return None
47
54
 
48
55
  def emit(self, record: logging.LogRecord) -> None:
56
+
49
57
  self._serialized_buffer.append(_serialize_record(record))
50
58
  super().emit(record)
51
59
 
@@ -61,6 +69,11 @@ class HTTPMemoryHandler(MemoryHandler):
61
69
  return True
62
70
  return False
63
71
 
72
+ def wait_for_lingering_threads(self) -> None:
73
+ for thread in self._thread_pool:
74
+ if thread.is_alive():
75
+ thread.join()
76
+
64
77
  def flush(self) -> None:
65
78
  if self.ocean is None or not self.buffer:
66
79
  return
@@ -70,13 +83,21 @@ class HTTPMemoryHandler(MemoryHandler):
70
83
  loop.run_until_complete(self.send_logs(_ocean, logs_to_send))
71
84
  loop.close()
72
85
 
86
+ def clear_thread_pool() -> None:
87
+ for thread in self._thread_pool:
88
+ if not thread.is_alive():
89
+ self._thread_pool.remove(thread)
90
+
73
91
  self.acquire()
74
92
  logs = list(self._serialized_buffer)
75
93
  if logs:
76
94
  self.buffer.clear()
77
95
  self._serialized_buffer.clear()
78
96
  self.last_flush_time = time.time()
79
- threading.Thread(target=_wrap_event_loop, args=(self.ocean, logs)).start()
97
+ clear_thread_pool()
98
+ thread = threading.Thread(target=_wrap_event_loop, args=(self.ocean, logs))
99
+ thread.start()
100
+ self._thread_pool.append(thread)
80
101
  self.release()
81
102
 
82
103
  async def send_logs(
@@ -55,6 +55,7 @@ def _http_loguru_handler(level: LogLevelType) -> None:
55
55
  logger.configure(patcher=exception_deserializer)
56
56
 
57
57
  http_memory_handler = HTTPMemoryHandler()
58
+ signal_handler.register(http_memory_handler.wait_for_lingering_threads)
58
59
  signal_handler.register(http_memory_handler.flush)
59
60
 
60
61
  queue_listener = QueueListener(queue, http_memory_handler)
port_ocean/ocean.py CHANGED
@@ -123,6 +123,7 @@ class Ocean:
123
123
  yield None
124
124
  except Exception:
125
125
  logger.exception("Integration had a fatal error. Shutting down.")
126
+ logger.complete()
126
127
  sys.exit("Server stopped")
127
128
  finally:
128
129
  signal_handler.exit()
@@ -0,0 +1,71 @@
1
+ from port_ocean.log.handlers import _serialize_record
2
+ from loguru import logger
3
+ from logging import LogRecord
4
+ from queue import Queue
5
+ from logging.handlers import QueueHandler
6
+ from typing import Callable, Any
7
+
8
+
9
+ log_message = "This is a test log message."
10
+ exception_grouop_message = "Test Exception group"
11
+ exception_message = "Test Exception"
12
+ expected_keys = ["message", "level", "timestamp", "extra"]
13
+
14
+
15
+ def test_serialize_record_log_shape() -> None:
16
+ record = log_record(
17
+ lambda: logger.exception(
18
+ log_message,
19
+ exc_info=None,
20
+ )
21
+ )
22
+ serialized_record = _serialize_record(record)
23
+ assert all(key in serialized_record for key in expected_keys)
24
+ assert log_message in serialized_record.get("message", None)
25
+
26
+
27
+ def test_serialize_record_exc_info_single_exception() -> None:
28
+ record = log_record(
29
+ lambda: logger.exception(
30
+ log_message,
31
+ exc_info=ExceptionGroup(
32
+ exception_grouop_message, [Exception(exception_message)]
33
+ ),
34
+ )
35
+ )
36
+ serialized_record = _serialize_record(record)
37
+ exc_info = assert_extra(serialized_record.get("extra", {}))
38
+ assert exception_grouop_message in exc_info
39
+ assert exception_message in exc_info
40
+
41
+
42
+ def test_serialize_record_exc_info_group_exception() -> None:
43
+ record = log_record(
44
+ lambda: logger.exception(log_message, exc_info=Exception(exception_message))
45
+ )
46
+ serialized_record = _serialize_record(record)
47
+ exc_info = assert_extra(serialized_record.get("extra", {}))
48
+ assert exception_message in exc_info
49
+
50
+
51
+ def assert_extra(extra: dict[str, Any]) -> str:
52
+ exc_info = extra.get("exc_info", None)
53
+ assert type(exc_info) is str
54
+ return exc_info
55
+
56
+
57
+ def log_record(cb: Callable[[], None]) -> LogRecord:
58
+ queue = Queue[LogRecord]()
59
+ queue_handler = QueueHandler(queue)
60
+ logger_id = logger.add(
61
+ queue_handler,
62
+ level="DEBUG",
63
+ format="{message}",
64
+ diagnose=False,
65
+ enqueue=True,
66
+ )
67
+ cb()
68
+ logger.complete()
69
+ logger.remove(logger_id)
70
+ record = queue.get()
71
+ return record
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.12.7
3
+ Version: 0.12.9
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
@@ -26,7 +26,7 @@ Requires-Dist: aiostream (>=0.5.2,<0.7.0)
26
26
  Requires-Dist: click (>=8.1.3,<9.0.0) ; extra == "cli"
27
27
  Requires-Dist: confluent-kafka (>=2.1.1,<3.0.0)
28
28
  Requires-Dist: cookiecutter (>=2.1.1,<3.0.0) ; extra == "cli"
29
- Requires-Dist: fastapi (>=0.100,<0.112)
29
+ Requires-Dist: fastapi (>=0.115.3,<0.116.0)
30
30
  Requires-Dist: httpx (>=0.24.1,<0.28.0)
31
31
  Requires-Dist: jinja2-time (>=0.2.0,<0.3.0) ; extra == "cli"
32
32
  Requires-Dist: jq (>=1.8.0,<2.0.0)
@@ -44,7 +44,7 @@ port_ocean/clients/port/client.py,sha256=Xd8Jk25Uh4WXY_WW-z1Qbv6F3ZTBFPoOolsxHMf
44
44
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
45
  port_ocean/clients/port/mixins/blueprints.py,sha256=POBl4uDocrgJBw4rvCAzwRcD4jk-uBL6pDAuKMTajdg,4633
46
46
  port_ocean/clients/port/mixins/entities.py,sha256=WdqT1gyS81pByUl9xIfZz_xEHRaBfDuZ-ekKX53oBSE,8870
47
- port_ocean/clients/port/mixins/integrations.py,sha256=HnWXaJt41SUcha-bhvLdJW07j-l7xIo91GUzzwl2f_E,4859
47
+ port_ocean/clients/port/mixins/integrations.py,sha256=t8OSa7Iopnpp8IOEcp3a7WgwOcJEBdFow9UbGDKWxKI,4858
48
48
  port_ocean/clients/port/mixins/migrations.py,sha256=A6896oJF6WbFL2WroyTkMzr12yhVyWqGoq9dtLNSKBY,1457
49
49
  port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
50
50
  port_ocean/clients/port/types.py,sha256=nvlgiAq4WH5_F7wQbz_GAWl-faob84LVgIjZ2Ww5mTk,451
@@ -111,11 +111,11 @@ port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
111
111
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
112
112
  port_ocean/helpers/retry.py,sha256=IQ0RfQ2T5o6uoZh2WW2nrFH5TT6K_k3y2Im0HDp5j9Y,15059
113
113
  port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
- port_ocean/log/handlers.py,sha256=k9G_Mb4ga2-Jke9irpdlYqj6EYiwv0gEsh4TgyqqOmI,2853
115
- port_ocean/log/logger_setup.py,sha256=BaXt-mh9CVXhneh37H46d04lqOdIBixG1pFyGfotuZs,2328
114
+ port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,3631
115
+ port_ocean/log/logger_setup.py,sha256=CoEDowe5OwNOL_5clU6Z4faktfh0VWaOTS0VLmyhHjw,2404
116
116
  port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
117
117
  port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
118
- port_ocean/ocean.py,sha256=Oe4H3kKtkj52uNO4Rd_47iY3MBdrTtshXZ_16q7A8bM,5071
118
+ port_ocean/ocean.py,sha256=jwws4U21EwFDXzM517dOlGIuvmLMVdAeIrgCRR6se-8,5105
119
119
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  port_ocean/run.py,sha256=rTxBlrQd4yyrtgErCFJCHCEHs7d1OXrRiJehUYmIbN0,2212
121
121
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
@@ -129,6 +129,7 @@ port_ocean/tests/helpers/integration.py,sha256=_RxS-RHpu11lrbhUXYPZp862HLWx8AoD7
129
129
  port_ocean/tests/helpers/ocean_app.py,sha256=8BysIhNqtTwhjnya5rr0AtrjulfJnJJMFz5cPUxIpLk,2167
130
130
  port_ocean/tests/helpers/port_client.py,sha256=5d6GNr8vNNSOkrz1AdOhxBUKuusr_-UPDP7AVpHasQw,599
131
131
  port_ocean/tests/helpers/smoke_test.py,sha256=_9aJJFRfuGJEg2D2YQJVJRmpreS6gEPHHQq8Q01x4aQ,2697
132
+ port_ocean/tests/log/test_handlers.py,sha256=bTOGnuj8fMIEXepwYblRvcg0FKqApCdyCBtAQZ2BlXM,2115
132
133
  port_ocean/tests/test_smoke.py,sha256=uix2uIg_yOm8BHDgHw2hTFPy1fiIyxBGW3ENU_KoFlo,2557
133
134
  port_ocean/tests/utils/test_async_iterators.py,sha256=3PLk1emEXekb8LcC5GgVh3OicaX15i5WyaJT_eFnu_4,1336
134
135
  port_ocean/utils/__init__.py,sha256=KMGnCPXZJbNwtgxtyMycapkDz8tpSyw23MSYT3iVeHs,91
@@ -141,8 +142,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
141
142
  port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
142
143
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
143
144
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
144
- port_ocean-0.12.7.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
145
- port_ocean-0.12.7.dist-info/METADATA,sha256=_MT7aiNNRduYEGrRxeeCYTA0-K-CF303XDvPXUQWj3o,6669
146
- port_ocean-0.12.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
147
- port_ocean-0.12.7.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
148
- port_ocean-0.12.7.dist-info/RECORD,,
145
+ port_ocean-0.12.9.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
146
+ port_ocean-0.12.9.dist-info/METADATA,sha256=FUFO0qXa5dwEcFuTWCUzFhLr0n__XluKDNyP7qVsdWE,6673
147
+ port_ocean-0.12.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
148
+ port_ocean-0.12.9.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
149
+ port_ocean-0.12.9.dist-info/RECORD,,