port-ocean 0.18.5__py3-none-any.whl → 0.18.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.
Files changed (26) hide show
  1. port_ocean/context/ocean.py +32 -0
  2. port_ocean/core/handlers/entities_state_applier/port/applier.py +18 -2
  3. port_ocean/core/handlers/port_app_config/models.py +3 -0
  4. port_ocean/core/handlers/queue/__init__.py +4 -0
  5. port_ocean/core/handlers/queue/abstract_queue.py +28 -0
  6. port_ocean/core/handlers/queue/local_queue.py +25 -0
  7. port_ocean/core/handlers/webhook/__init__.py +0 -0
  8. port_ocean/core/handlers/webhook/abstract_webhook_processor.py +101 -0
  9. port_ocean/core/handlers/webhook/processor_manager.py +237 -0
  10. port_ocean/core/handlers/webhook/webhook_event.py +77 -0
  11. port_ocean/core/integrations/mixins/sync.py +2 -1
  12. port_ocean/core/integrations/mixins/sync_raw.py +26 -20
  13. port_ocean/exceptions/webhook_processor.py +4 -0
  14. port_ocean/ocean.py +7 -1
  15. port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +86 -0
  16. port_ocean/tests/core/handlers/mixins/test_sync_raw.py +149 -124
  17. port_ocean/tests/core/handlers/queue/test_local_queue.py +90 -0
  18. port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +114 -0
  19. port_ocean/tests/core/handlers/webhook/test_processor_manager.py +391 -0
  20. port_ocean/tests/core/handlers/webhook/test_webhook_event.py +65 -0
  21. port_ocean/utils/signal.py +6 -2
  22. {port_ocean-0.18.5.dist-info → port_ocean-0.18.7.dist-info}/METADATA +1 -1
  23. {port_ocean-0.18.5.dist-info → port_ocean-0.18.7.dist-info}/RECORD +26 -13
  24. {port_ocean-0.18.5.dist-info → port_ocean-0.18.7.dist-info}/LICENSE.md +0 -0
  25. {port_ocean-0.18.5.dist-info → port_ocean-0.18.7.dist-info}/WHEEL +0 -0
  26. {port_ocean-0.18.5.dist-info → port_ocean-0.18.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,391 @@
1
+ import asyncio
2
+ import pytest
3
+ from fastapi import APIRouter
4
+ from typing import Dict, Any
5
+
6
+ from port_ocean.exceptions.webhook_processor import RetryableError
7
+ from port_ocean.core.handlers.webhook.processor_manager import WebhookProcessorManager
8
+ from port_ocean.core.handlers.webhook.abstract_webhook_processor import (
9
+ AbstractWebhookProcessor,
10
+ )
11
+ from port_ocean.core.handlers.webhook.webhook_event import (
12
+ WebhookEvent,
13
+ )
14
+ from port_ocean.core.handlers.queue import LocalQueue
15
+ from port_ocean.utils.signal import SignalHandler
16
+
17
+
18
+ class MockWebhookProcessor(AbstractWebhookProcessor):
19
+ def __init__(self, event: WebhookEvent) -> None:
20
+ super().__init__(event)
21
+ self.processed = False
22
+ self.cancel_called = False
23
+ self.error_to_raise: Exception | asyncio.CancelledError | None = None
24
+ self.retry_count = 0
25
+ self.max_retries = 3
26
+
27
+ async def authenticate(
28
+ self, payload: Dict[str, Any], headers: Dict[str, str]
29
+ ) -> bool:
30
+ return True
31
+
32
+ async def validate_payload(self, payload: Dict[str, Any]) -> bool:
33
+ return True
34
+
35
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
36
+ if self.error_to_raise:
37
+ raise self.error_to_raise
38
+ self.processed = True
39
+
40
+ async def cancel(self) -> None:
41
+ self.cancel_called = True
42
+
43
+
44
+ class RetryableProcessor(MockWebhookProcessor):
45
+ def __init__(self, event: WebhookEvent) -> None:
46
+ super().__init__(event)
47
+ self.attempt_count = 0
48
+
49
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
50
+ self.attempt_count += 1
51
+ if self.attempt_count < 3: # Succeed on third attempt
52
+ raise RetryableError("Temporary failure")
53
+ self.processed = True
54
+
55
+
56
+ class TestableWebhookProcessorManager(WebhookProcessorManager):
57
+ __test__ = False
58
+
59
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
60
+ super().__init__(*args, **kwargs)
61
+ self.running_processors: list[AbstractWebhookProcessor] = []
62
+ self.no_matching_processors: bool = False
63
+
64
+ def _extract_matching_processors(
65
+ self, event: WebhookEvent, path: str
66
+ ) -> list[AbstractWebhookProcessor]:
67
+ try:
68
+ return super()._extract_matching_processors(event, path)
69
+ except ValueError:
70
+ self.no_matching_processors = True
71
+ raise
72
+
73
+ async def _process_single_event(
74
+ self, processor: AbstractWebhookProcessor, path: str
75
+ ) -> None:
76
+ self.running_processors.append(processor)
77
+ await super()._process_single_event(processor, path)
78
+
79
+
80
+ class TestWebhookProcessorManager:
81
+ @pytest.fixture
82
+ def router(self) -> APIRouter:
83
+ return APIRouter()
84
+
85
+ @pytest.fixture
86
+ def signal_handler(self) -> SignalHandler:
87
+ return SignalHandler()
88
+
89
+ @pytest.fixture
90
+ def processor_manager(
91
+ self, router: APIRouter, signal_handler: SignalHandler
92
+ ) -> TestableWebhookProcessorManager:
93
+ return TestableWebhookProcessorManager(router, signal_handler)
94
+
95
+ @pytest.fixture
96
+ def mock_event(self) -> WebhookEvent:
97
+ return WebhookEvent.from_dict(
98
+ {
99
+ "payload": {"test": "data"},
100
+ "headers": {"content-type": "application/json"},
101
+ "trace_id": "test-trace",
102
+ }
103
+ )
104
+
105
+ @staticmethod
106
+ def assert_event_processed_successfully(
107
+ processor: MockWebhookProcessor,
108
+ ) -> None:
109
+ """Assert that a processor's event was processed successfully"""
110
+ assert processor.processed, "Event was not processed successfully"
111
+
112
+ @staticmethod
113
+ def assert_event_processed_with_error(processor: MockWebhookProcessor) -> None:
114
+ """Assert that an event was processed with an error"""
115
+ assert not processor.processed, "Event did not fail as expected"
116
+
117
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
118
+ async def test_register_handler(
119
+ self, processor_manager: TestableWebhookProcessorManager
120
+ ) -> None:
121
+ """Test registering a processor for a path."""
122
+ processor_manager.register_processor("/test", MockWebhookProcessor)
123
+ assert "/test" in processor_manager._processors
124
+ assert len(processor_manager._processors["/test"]) == 1
125
+ assert isinstance(processor_manager._event_queues["/test"], LocalQueue)
126
+
127
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
128
+ async def test_register_multiple_handlers_with_filters(
129
+ self, processor_manager: TestableWebhookProcessorManager
130
+ ) -> None:
131
+ """Test registering multiple processors with different filters."""
132
+
133
+ def filter1(e: WebhookEvent) -> bool:
134
+ return e.payload.get("type") == "type1"
135
+
136
+ def filter2(e: WebhookEvent) -> bool:
137
+ return e.payload.get("type") == "type2"
138
+
139
+ processor_manager.register_processor("/test", MockWebhookProcessor, filter1)
140
+ processor_manager.register_processor("/test", MockWebhookProcessor, filter2)
141
+
142
+ assert len(processor_manager._processors["/test"]) == 2
143
+
144
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
145
+ async def test_successful_event_processing(
146
+ self,
147
+ processor_manager: TestableWebhookProcessorManager,
148
+ mock_event: WebhookEvent,
149
+ ) -> None:
150
+ """Test successful processing of an event."""
151
+ processed_events: list[MockWebhookProcessor] = []
152
+
153
+ class SuccessProcessor(MockWebhookProcessor):
154
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
155
+ self.processed = True
156
+ processed_events.append(self)
157
+
158
+ processor_manager.register_processor("/test", SuccessProcessor)
159
+
160
+ await processor_manager.start_processing_event_messages()
161
+ await processor_manager._event_queues["/test"].put(mock_event)
162
+
163
+ # Allow time for processing
164
+ await asyncio.sleep(0.1)
165
+
166
+ # Verify at least one processor ran and completed successfully
167
+ assert len(processed_events) > 0
168
+ for processor in processed_events:
169
+ self.assert_event_processed_successfully(processor)
170
+
171
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
172
+ async def test_graceful_shutdown(
173
+ self,
174
+ processor_manager: TestableWebhookProcessorManager,
175
+ mock_event: WebhookEvent,
176
+ ) -> None:
177
+ """Test graceful shutdown with in-flight requests"""
178
+ processor_manager.register_processor("/test", MockWebhookProcessor)
179
+
180
+ await processor_manager.start_processing_event_messages()
181
+ await processor_manager._event_queues["/test"].put(mock_event)
182
+
183
+ # Start shutdown
184
+ await processor_manager.shutdown()
185
+
186
+ # Verify all tasks are cleaned up
187
+ assert len(processor_manager._webhook_processor_tasks) == 0
188
+ self.assert_event_processed_successfully(
189
+ processor_manager.running_processors[0] # type: ignore
190
+ )
191
+
192
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
193
+ async def test_handler_filter_matching(
194
+ self, processor_manager: TestableWebhookProcessorManager
195
+ ) -> None:
196
+ """Test that processors are selected based on their filters."""
197
+ type1_event = WebhookEvent.from_dict(
198
+ {"payload": {"type": "type1"}, "headers": {}, "trace_id": "test-trace-1"}
199
+ )
200
+
201
+ type2_event = WebhookEvent.from_dict(
202
+ {"payload": {"type": "type2"}, "headers": {}, "trace_id": "test-trace-2"}
203
+ )
204
+
205
+ def filter1(e: WebhookEvent) -> bool:
206
+ return e.payload.get("type") == "type1"
207
+
208
+ def filter2(e: WebhookEvent) -> bool:
209
+ return e.payload.get("type") == "type2"
210
+
211
+ processor_manager.register_processor("/test", MockWebhookProcessor, filter1)
212
+ processor_manager.register_processor("/test", MockWebhookProcessor, filter2)
213
+
214
+ await processor_manager.start_processing_event_messages()
215
+
216
+ # Process both events
217
+ await processor_manager._event_queues["/test"].put(type1_event)
218
+ await processor_manager._event_queues["/test"].put(type2_event)
219
+
220
+ await asyncio.sleep(0.1)
221
+
222
+ # Verify both events were processed
223
+ self.assert_event_processed_successfully(
224
+ processor_manager.running_processors[0] # type: ignore
225
+ )
226
+ self.assert_event_processed_successfully(
227
+ processor_manager.running_processors[1] # type: ignore
228
+ )
229
+
230
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
231
+ async def test_handler_timeout(
232
+ self, router: APIRouter, signal_handler: SignalHandler, mock_event: WebhookEvent
233
+ ) -> None:
234
+ """Test processor timeout behavior."""
235
+
236
+ # Set a short timeout for testing
237
+ processor_manager = TestableWebhookProcessorManager(
238
+ router, signal_handler, max_event_processing_seconds=0.1
239
+ )
240
+
241
+ class TimeoutHandler(MockWebhookProcessor):
242
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
243
+ await asyncio.sleep(2) # Longer than max_handler_processing_seconds
244
+
245
+ processor_manager.register_processor("/test", TimeoutHandler)
246
+ await processor_manager.start_processing_event_messages()
247
+ await processor_manager._event_queues["/test"].put(mock_event)
248
+
249
+ # Wait long enough for the timeout to occur
250
+ await asyncio.sleep(0.2)
251
+
252
+ self.assert_event_processed_with_error(
253
+ processor_manager.running_processors[0] # type: ignore
254
+ )
255
+
256
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
257
+ async def test_handler_cancellation(
258
+ self,
259
+ processor_manager: TestableWebhookProcessorManager,
260
+ mock_event: WebhookEvent,
261
+ ) -> None:
262
+ """Test processor cancellation during shutdown."""
263
+ cancelled_events: list[WebhookEvent] = []
264
+
265
+ class CanceledHandler(MockWebhookProcessor):
266
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
267
+ await asyncio.sleep(0.2)
268
+
269
+ async def cancel(self) -> None:
270
+ cancelled_events.append(self.event)
271
+ self.event.payload["canceled"] = True
272
+
273
+ processor_manager.register_processor("/test", CanceledHandler)
274
+ await processor_manager.start_processing_event_messages()
275
+ await processor_manager._event_queues["/test"].put(mock_event)
276
+
277
+ await asyncio.sleep(0.1)
278
+
279
+ # Wait for the event to be processed
280
+ await processor_manager._cancel_all_tasks()
281
+
282
+ # Verify at least one event was cancelled
283
+ assert len(cancelled_events) > 0
284
+ assert any(event.payload.get("canceled") for event in cancelled_events)
285
+
286
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
287
+ async def test_invalid_handler_registration(self) -> None:
288
+ """Test registration of invalid processor type."""
289
+ handler_manager = WebhookProcessorManager(APIRouter(), SignalHandler())
290
+
291
+ with pytest.raises(ValueError):
292
+ handler_manager.register_processor("/test", object) # type: ignore
293
+
294
+ async def test_no_matching_handlers(
295
+ self,
296
+ processor_manager: TestableWebhookProcessorManager,
297
+ mock_event: WebhookEvent,
298
+ ) -> None:
299
+ """Test behavior when no processors match the event."""
300
+ processor_manager.register_processor(
301
+ "/test", MockWebhookProcessor, lambda e: False
302
+ )
303
+
304
+ await processor_manager.start_processing_event_messages()
305
+ await processor_manager._event_queues["/test"].put(mock_event)
306
+
307
+ await asyncio.sleep(0.1)
308
+
309
+ assert processor_manager.no_matching_processors
310
+ assert len(processor_manager.running_processors) == 0
311
+
312
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
313
+ async def test_multiple_processors(
314
+ self, processor_manager: TestableWebhookProcessorManager
315
+ ) -> None:
316
+ # Test multiple processors for same path
317
+ processor_manager.register_processor("/test", MockWebhookProcessor)
318
+ processor_manager.register_processor("/test", MockWebhookProcessor)
319
+ assert len(processor_manager._processors["/test"]) == 2
320
+
321
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
322
+ async def test_all_matching_processors_execute(
323
+ self,
324
+ processor_manager: TestableWebhookProcessorManager,
325
+ mock_event: WebhookEvent,
326
+ ) -> None:
327
+ """Test that all matching processors are executed even if some fail."""
328
+ processed_count = 0
329
+
330
+ class SuccessProcessor(MockWebhookProcessor):
331
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
332
+ nonlocal processed_count
333
+ processed_count += 1
334
+ self.processed = True
335
+
336
+ class FailingProcessor(MockWebhookProcessor):
337
+ async def handle_event(self, payload: Dict[str, Any]) -> None:
338
+ raise Exception("Simulated failure")
339
+
340
+ # Register mix of successful and failing processors
341
+ processor_manager.register_processor("/test", SuccessProcessor)
342
+ processor_manager.register_processor("/test", FailingProcessor)
343
+ processor_manager.register_processor("/test", SuccessProcessor)
344
+
345
+ await processor_manager.start_processing_event_messages()
346
+ await processor_manager._event_queues["/test"].put(mock_event)
347
+
348
+ # Wait for processing to complete
349
+ await asyncio.sleep(0.1)
350
+
351
+ # Verify successful processors ran despite failing one
352
+ assert processed_count == 2
353
+
354
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
355
+ async def test_retry_mechanism(
356
+ self,
357
+ processor_manager: TestableWebhookProcessorManager,
358
+ mock_event: WebhookEvent,
359
+ ) -> None:
360
+ """Test retry mechanism with temporary failures."""
361
+ processor = MockWebhookProcessor(mock_event)
362
+ processor.error_to_raise = RetryableError("Temporary failure")
363
+
364
+ # Simulate 2 failures before success
365
+ async def handle_event(payload: Dict[str, Any]) -> None:
366
+ if processor.retry_count < 2:
367
+ raise RetryableError("Temporary failure")
368
+ processor.processed = True
369
+
370
+ processor.handle_event = handle_event # type: ignore
371
+
372
+ await processor_manager._process_webhook_request(processor)
373
+
374
+ assert processor.processed
375
+ assert processor.retry_count == 2
376
+
377
+ @pytest.mark.skip(reason="Temporarily ignoring this test")
378
+ async def test_max_retries_exceeded(
379
+ self,
380
+ processor_manager: TestableWebhookProcessorManager,
381
+ mock_event: WebhookEvent,
382
+ ) -> None:
383
+ """Test behavior when max retries are exceeded."""
384
+ processor = MockWebhookProcessor(mock_event)
385
+ processor.max_retries = 1
386
+ processor.error_to_raise = RetryableError("Temporary failure")
387
+
388
+ with pytest.raises(RetryableError):
389
+ await processor_manager._process_webhook_request(processor)
390
+
391
+ assert processor.retry_count == processor.max_retries
@@ -0,0 +1,65 @@
1
+ import pytest
2
+ from fastapi import Request
3
+ from port_ocean.core.handlers.webhook.webhook_event import (
4
+ EventHeaders,
5
+ EventPayload,
6
+ WebhookEvent,
7
+ )
8
+
9
+
10
+ class TestWebhookEvent:
11
+ @pytest.fixture
12
+ def sample_payload(self) -> EventPayload:
13
+ return {"test": "data", "nested": {"value": 123}}
14
+
15
+ @pytest.fixture
16
+ def sample_headers(self) -> EventHeaders:
17
+ return {"content-type": "application/json", "x-test-header": "test-value"}
18
+
19
+ @pytest.fixture
20
+ def mock_request(
21
+ self, sample_payload: EventPayload, sample_headers: EventHeaders
22
+ ) -> Request:
23
+ scope = {
24
+ "type": "http",
25
+ "headers": [(k.encode(), v.encode()) for k, v in sample_headers.items()],
26
+ }
27
+ mock_request = Request(scope)
28
+ mock_request._json = sample_payload
29
+ return mock_request
30
+
31
+ @pytest.fixture
32
+ def webhook_event(
33
+ self, sample_payload: EventPayload, sample_headers: EventHeaders
34
+ ) -> WebhookEvent:
35
+ return WebhookEvent(
36
+ trace_id="test-trace-id",
37
+ payload=sample_payload,
38
+ headers=sample_headers,
39
+ )
40
+
41
+ async def test_create_from_request(self, mock_request: Request) -> None:
42
+ """Test creating WebhookEvent from a request."""
43
+ event = await WebhookEvent.from_request(mock_request)
44
+
45
+ assert event.trace_id is not None
46
+ assert len(event.trace_id) > 0
47
+ assert event.headers == dict(mock_request.headers)
48
+ assert event._original_request == mock_request
49
+
50
+ def test_create_from_dict(
51
+ self, sample_payload: EventPayload, sample_headers: EventHeaders
52
+ ) -> None:
53
+ """Test creating WebhookEvent from a dictionary."""
54
+ data = {
55
+ "trace_id": "test-trace-id",
56
+ "payload": sample_payload,
57
+ "headers": sample_headers,
58
+ }
59
+
60
+ event = WebhookEvent.from_dict(data)
61
+
62
+ assert event.trace_id == "test-trace-id"
63
+ assert event.payload == sample_payload
64
+ assert event.headers == sample_headers
65
+ assert event._original_request is None
@@ -1,3 +1,4 @@
1
+ from asyncio import iscoroutinefunction
1
2
  from typing import Callable, Any
2
3
 
3
4
  from werkzeug.local import LocalProxy, LocalStack
@@ -13,13 +14,16 @@ class SignalHandler:
13
14
  def __init__(self) -> None:
14
15
  self._handlers: dict[str, Callable[[], Any]] = {}
15
16
 
16
- def exit(self) -> None:
17
+ async def exit(self) -> None:
17
18
  """
18
19
  Handles the exit signal.
19
20
  """
20
21
  while self._handlers:
21
22
  _, handler = self._handlers.popitem()
22
- handler()
23
+ if iscoroutinefunction(handler):
24
+ await handler()
25
+ else:
26
+ handler()
23
27
 
24
28
  def register(self, callback: Callable[[], Any]) -> str:
25
29
  _id = generate_uuid()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.18.5
3
+ Version: 0.18.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
@@ -64,7 +64,7 @@ port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
64
64
  port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
65
65
  port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
66
  port_ocean/context/event.py,sha256=QK2ben4fJtxdorq_yRroATttP0DRc4wLtlUJ1as5D58,6208
67
- port_ocean/context/ocean.py,sha256=0kgIi0zAsGarF52Qehu4bOxnAFEb0yayzG7xZioMHJc,4993
67
+ port_ocean/context/ocean.py,sha256=-T8Z_xcUNMeobr33TTABiOQjxC4KCVvVrP0IytRfen8,6425
68
68
  port_ocean/context/resource.py,sha256=yDj63URzQelj8zJPh4BAzTtPhpKr9Gw9DRn7I_0mJ1s,1692
69
69
  port_ocean/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
70
  port_ocean/core/defaults/__init__.py,sha256=8qCZg8n06WAdMu9s_FiRtDYLGPGHbOuS60vapeUoAks,142
@@ -84,7 +84,7 @@ port_ocean/core/handlers/base.py,sha256=cTarblazu8yh8xz2FpB-dzDKuXxtoi143XJgPbV_
84
84
  port_ocean/core/handlers/entities_state_applier/__init__.py,sha256=kgLZDCeCEzi4r-0nzW9k78haOZNf6PX7mJOUr34A4c8,173
85
85
  port_ocean/core/handlers/entities_state_applier/base.py,sha256=5wHL0icfFAYRPqk8iV_wN49GdJ3aRUtO8tumSxBi4Wo,2268
86
86
  port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
- port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=FSJ77o-lilv5tF5WgpPNjA5lAw13CseFilL-kMHIF3A,5564
87
+ port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=6YketOyFDYrqK9tGM7FWZLO0bTXiYz0lQB-u3dyJvWw,6271
88
88
  port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=1zncwCbE-Gej0xaWKlzZgoXxOBe9bgs_YxlZ8QW3NdI,1751
89
89
  port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=lyv6xKzhYfd6TioUgR3AVRSJqj7JpAaj1LxxU2xAqeo,1720
90
90
  port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
@@ -93,16 +93,23 @@ port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=C6zJbS3m
93
93
  port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
94
94
  port_ocean/core/handlers/port_app_config/api.py,sha256=r_Th66NEw38IpRdnXZcRvI8ACfvxW_A6V62WLwjWXlQ,1044
95
95
  port_ocean/core/handlers/port_app_config/base.py,sha256=4Nxt2g8voEIHJ4Y1Km5NJcaG2iSbCklw5P8-Kus7Y9k,3007
96
- port_ocean/core/handlers/port_app_config/models.py,sha256=YvYtf_44KD_rN4xK-3xHtdpRZ1M8Qo-m9K4LDtH7FYU,2344
96
+ port_ocean/core/handlers/port_app_config/models.py,sha256=5Tt2BWHOJ4nnkh4xVrwyVG4KO5Tp4e64Cu_eFF-9jBg,2449
97
+ port_ocean/core/handlers/queue/__init__.py,sha256=1fICM0ZLATmmj6f7cdq_eV2kmw0_jy7y2INuLQIpzIE,121
98
+ port_ocean/core/handlers/queue/abstract_queue.py,sha256=q_gpaWFFZHxM3XovEbgsDn8jEOLM45iAZWVC81Paxto,620
99
+ port_ocean/core/handlers/queue/local_queue.py,sha256=EzqsGIX43xbVAcePwTcCg5QDrXATQpy-VzWxxN_OyAM,574
97
100
  port_ocean/core/handlers/resync_state_updater/__init__.py,sha256=kG6y-JQGpPfuTHh912L_bctIDCzAK4DN-d00S7rguWU,81
98
101
  port_ocean/core/handlers/resync_state_updater/updater.py,sha256=Yg9ET6ZV5B9GW7u6zZA6GlB_71kmvxvYX2FWgQNzMvo,3182
102
+ port_ocean/core/handlers/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
+ port_ocean/core/handlers/webhook/abstract_webhook_processor.py,sha256=oZvYgE3rqirx7yN0PAL-9Nls1EHLwOHQaNtFzla3Heo,3500
104
+ port_ocean/core/handlers/webhook/processor_manager.py,sha256=B1NjvuWD5MXaW0PWxxOAkbkNfgC9oeuQreusna0EUBA,9166
105
+ port_ocean/core/handlers/webhook/webhook_event.py,sha256=9Lk4JzHfgWNruts3pYe-t6OgaG1xP4EkD6lFr_78yUk,2210
99
106
  port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
107
  port_ocean/core/integrations/base.py,sha256=eS0WDOfCTim1UOQQrNuP14I6hvT_fr8dof_cr1ls01s,3107
101
108
  port_ocean/core/integrations/mixins/__init__.py,sha256=FA1FEKMM6P-L2_m7Q4L20mFa4_RgZnwSRmTCreKcBVM,220
102
109
  port_ocean/core/integrations/mixins/events.py,sha256=0jKRsBw6lU8Mqs7MaQK4n-t_H6Z4NEkXZ5VWzqTrKEc,2396
103
110
  port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
104
- port_ocean/core/integrations/mixins/sync.py,sha256=B9fEs8faaYLLikH9GBjE_E61vo0bQDjIGQsQ1SRXOlA,3931
105
- port_ocean/core/integrations/mixins/sync_raw.py,sha256=XLxH9_OOudLW4Q5lrUWWpcXIM3KdEDnYwEEP661Rfao,24465
111
+ port_ocean/core/integrations/mixins/sync.py,sha256=GHiFbnw0XrBfl7aCTH_w67f_N7EZbcUgssc-0fPujNU,4047
112
+ port_ocean/core/integrations/mixins/sync_raw.py,sha256=7kk2p5lLKq9oivqqintZumuaIHSbcSmoUfWrE346l7g,24821
106
113
  port_ocean/core/integrations/mixins/utils.py,sha256=oN4Okz6xlaefpid1_Pud8HPSw9BwwjRohyNsknq-Myg,2309
107
114
  port_ocean/core/models.py,sha256=FvTp-BlpbvLbMbngE0wsiimsCfmIhUR1PvsE__Z--1I,2206
108
115
  port_ocean/core/ocean_types.py,sha256=j_-or1VxDy22whLLxwxgzIsE4wAhFLH19Xff9l4oJA8,1124
@@ -117,6 +124,7 @@ port_ocean/exceptions/context.py,sha256=mA8HII6Rl4QxKUz98ppy1zX3kaziaen21h1ZWuU3
117
124
  port_ocean/exceptions/core.py,sha256=Zmb1m6NnkSPWpAiQA5tgejm3zpDMt1WQEN47OJNo54A,856
118
125
  port_ocean/exceptions/port_defaults.py,sha256=2a7Koy541KxMan33mU-gbauUxsumG3NT4itVxSpQqfw,666
119
126
  port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnqI,197
127
+ port_ocean/exceptions/webhook_processor.py,sha256=yQYazg53Y-ohb7HfViwq1opH_ZUuUdhHSRxcUNveFpI,114
120
128
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
129
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
122
130
  port_ocean/helpers/retry.py,sha256=fmvaUFLIW6PICgYH4RI5rrKBXDxCAhR1Gtl0dsb2kMs,15625
@@ -125,7 +133,7 @@ port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,36
125
133
  port_ocean/log/logger_setup.py,sha256=CoEDowe5OwNOL_5clU6Z4faktfh0VWaOTS0VLmyhHjw,2404
126
134
  port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
127
135
  port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
128
- port_ocean/ocean.py,sha256=XxO-aRExs1hcy6aJY_nceu-QXRWB2ZLpkIPPuBkp-bQ,5247
136
+ port_ocean/ocean.py,sha256=MmLGxx0EC3Q6FEpbnZc9_5A7DculsvN2nAz3aZkj2kI,5536
129
137
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
138
  port_ocean/run.py,sha256=COoRSmLG4hbsjIW5DzhV0NYVegI9xHd1POv6sg4U1No,2217
131
139
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
@@ -134,10 +142,15 @@ port_ocean/tests/clients/port/mixins/test_entities.py,sha256=A9myrnkLhKSQrnOLv1Z
134
142
  port_ocean/tests/clients/port/mixins/test_organization_mixin.py,sha256=-8iHM33Oe8PuyEfj3O_6Yob8POp4fSmB0hnIT0Gv-8Y,868
135
143
  port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
136
144
  port_ocean/tests/core/defaults/test_common.py,sha256=sR7RqB3ZYV6Xn6NIg-c8k5K6JcGsYZ2SCe_PYX5vLYM,5560
145
+ port_ocean/tests/core/handlers/entities_state_applier/test_applier.py,sha256=R9bqyJocUWTh0NW0s-5ttD_SYYeM5EbYILgVmgWa7qA,2776
137
146
  port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=FnEnaDjuoAbKvKyv6xJ46n3j0ZcaT70Sg2zc7oy7HAA,13596
138
- port_ocean/tests/core/handlers/mixins/test_sync_raw.py,sha256=SSf1ZRZVcIta9zfx2-SpYFc_-MoHPDJSa1MkbIx3icI,31172
147
+ port_ocean/tests/core/handlers/mixins/test_sync_raw.py,sha256=gxQ4e9hQuMS8-o5UbiUSt1I1uaK0DCO3yCFDVigpZvo,31740
139
148
  port_ocean/tests/core/handlers/port_app_config/test_api.py,sha256=eJZ6SuFBLz71y4ca3DNqKag6d6HUjNJS0aqQPwiLMTI,1999
140
149
  port_ocean/tests/core/handlers/port_app_config/test_base.py,sha256=s3D98JP3YV9V6T5PCDPE2852gkqGiDmo03UyexESX_I,6872
150
+ port_ocean/tests/core/handlers/queue/test_local_queue.py,sha256=9Ly0HzZXbs6Rbl_bstsIdInC3h2bgABU3roP9S_PnJM,2582
151
+ port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py,sha256=pMETxEW98FCpPs28VGKTtueRwA5ka4Z3--4CJpfNi2U,3911
152
+ port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=k7ZjegY4qSfkf5uRqdSpc_v8RhObE_20wd1AwrO08Q4,14976
153
+ port_ocean/tests/core/handlers/webhook/test_webhook_event.py,sha256=yfsXuJzwEe9WYzyhWwT2IOEwwD9dHtKp_ufREM5v_DI,2090
141
154
  port_ocean/tests/core/test_utils.py,sha256=Z3kdhb5V7Svhcyy3EansdTpgHL36TL6erNtU-OPwAcI,2647
142
155
  port_ocean/tests/core/utils/test_entity_topological_sorter.py,sha256=zuq5WSPy_88PemG3mOUIHTxWMR_js1R7tOzUYlgBd68,3447
143
156
  port_ocean/tests/core/utils/test_resolve_entities_diff.py,sha256=4kTey1c0dWKbLXjJ-9m2GXrHyAWZeLQ2asdtYRRUdRs,16573
@@ -159,11 +172,11 @@ port_ocean/utils/cache.py,sha256=RgfN4SjjHrEkbqUChyboeD1mrXomolUUjsJtvbkmr3U,335
159
172
  port_ocean/utils/misc.py,sha256=0q2cJ5psqxn_5u_56pT7vOVQ3shDM02iC1lzyWQ_zl0,2098
160
173
  port_ocean/utils/queue_utils.py,sha256=KWWl8YVnG-glcfIHhM6nefY-2sou_C6DVP1VynQwzB4,2762
161
174
  port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,3231
162
- port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
175
+ port_ocean/utils/signal.py,sha256=mMVq-1Ab5YpNiqN4PkiyTGlV_G0wkUDMMjTZp5z3pb0,1514
163
176
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
164
177
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
165
- port_ocean-0.18.5.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
166
- port_ocean-0.18.5.dist-info/METADATA,sha256=qIcD5yXlV1899BUdBrFNoPzqda6yrqyrQFG27dZoOoc,6669
167
- port_ocean-0.18.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
168
- port_ocean-0.18.5.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
169
- port_ocean-0.18.5.dist-info/RECORD,,
178
+ port_ocean-0.18.7.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
179
+ port_ocean-0.18.7.dist-info/METADATA,sha256=ZWM3fXr54v89kiXWYwF3y6PHl28sjSUuf6BRV2PVAyI,6669
180
+ port_ocean-0.18.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
181
+ port_ocean-0.18.7.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
182
+ port_ocean-0.18.7.dist-info/RECORD,,