port-ocean 0.18.5__py3-none-any.whl → 0.18.7__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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,,