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

@@ -397,6 +397,15 @@ class EntityClientMixin:
397
397
  self._reduce_entity(entity),
398
398
  )
399
399
  entities_results.append(failed_result)
400
+ ocean.metrics.inc_metric(
401
+ name=MetricType.OBJECT_COUNT_NAME,
402
+ labels=[
403
+ ocean.metrics.current_resource_kind(),
404
+ MetricPhase.LOAD,
405
+ MetricPhase.LoadResult.FAILED,
406
+ ],
407
+ value=1,
408
+ )
400
409
  elif isinstance(bulk_result, list):
401
410
  for status, entity in bulk_result:
402
411
  if (
@@ -21,6 +21,7 @@ class IngestSearchQuery(BaseModel):
21
21
  class EntityMapping(BaseModel):
22
22
  identifier: str | IngestSearchQuery
23
23
  title: str | None
24
+ icon: str | None
24
25
  blueprint: str
25
26
  team: str | IngestSearchQuery | None
26
27
  properties: dict[str, str] = Field(default_factory=dict)
@@ -8,6 +8,7 @@ from port_ocean.context.event import EventType, event_context
8
8
  from port_ocean.core.handlers.port_app_config.models import ResourceConfig
9
9
  from port_ocean.core.integrations.mixins.events import EventsMixin
10
10
  from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
11
+ from port_ocean.exceptions.webhook_processor import WebhookEventNotSupportedError
11
12
  from .webhook_event import WebhookEvent, WebhookEventRawResults, LiveEventTimestamp
12
13
  from port_ocean.context.event import event
13
14
 
@@ -53,10 +54,12 @@ class LiveEventsProcessorManager(LiveEventsMixin, EventsMixin):
53
54
  """Find and extract the matching processor for an event"""
54
55
 
55
56
  created_processors: list[tuple[ResourceConfig, AbstractWebhookProcessor]] = []
57
+ event_processor_names = []
56
58
 
57
59
  for processor_class in self._processors_classes[path]:
58
60
  processor = processor_class(webhook_event.clone())
59
61
  if await processor.should_process_event(webhook_event):
62
+ event_processor_names.append(processor.__class__.__name__)
60
63
  kinds = await processor.get_matching_kinds(webhook_event)
61
64
  for kind in kinds:
62
65
  for resource in event.port_app_config.resources:
@@ -64,7 +67,22 @@ class LiveEventsProcessorManager(LiveEventsMixin, EventsMixin):
64
67
  created_processors.append((resource, processor))
65
68
 
66
69
  if not created_processors:
67
- raise ValueError("No matching processors found")
70
+ if event_processor_names:
71
+ logger.info(
72
+ "Webhook processors are available to handle this webhook event, but the corresponding kinds are not configured in the integration's mapping",
73
+ processors_available=event_processor_names,
74
+ webhook_path=path,
75
+ )
76
+ return []
77
+ else:
78
+ logger.warning(
79
+ "Unknown webhook event type received",
80
+ webhook_path=path,
81
+ message="No processors registered to handle this webhook event type.",
82
+ )
83
+ raise WebhookEventNotSupportedError(
84
+ "No matching processors found for webhook event"
85
+ )
68
86
 
69
87
  logger.info(
70
88
  "Found matching processors for webhook event",
@@ -257,16 +257,10 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
257
257
  labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.SKIPPED],
258
258
  value=len(objects_diff[0].entity_selector_diff.passed) - len(changed_entities)
259
259
  )
260
- upserted_entities = await self.entities_state_applier.upsert(
260
+ await self.entities_state_applier.upsert(
261
261
  changed_entities, user_agent_type
262
262
  )
263
263
 
264
- ocean.metrics.set_metric(
265
- name=MetricType.OBJECT_COUNT_NAME,
266
- labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.LOADED],
267
- value=len(upserted_entities)
268
- )
269
-
270
264
  else:
271
265
  logger.info("Entities in batch didn't changed since last sync, skipping", total_entities=len(objects_diff[0].entity_selector_diff.passed))
272
266
  ocean.metrics.inc_metric(
@@ -280,11 +274,6 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
280
274
  modified_objects = await self.entities_state_applier.upsert(
281
275
  objects_diff[0].entity_selector_diff.passed, user_agent_type
282
276
  )
283
- ocean.metrics.set_metric(
284
- name=MetricType.OBJECT_COUNT_NAME,
285
- labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.LOADED],
286
- value=len(upserted_entities)
287
- )
288
277
  else:
289
278
  modified_objects = await self.entities_state_applier.upsert(
290
279
  objects_diff[0].entity_selector_diff.passed, user_agent_type
port_ocean/core/models.py CHANGED
@@ -48,6 +48,7 @@ class PortAPIErrorMessage(Enum):
48
48
 
49
49
  class Entity(BaseModel):
50
50
  identifier: Any
51
+ icon: str | None
51
52
  blueprint: Any
52
53
  title: Any
53
54
  team: str | None | list[Any] | dict[str, Any] = []
@@ -1,4 +1,17 @@
1
+ from port_ocean.exceptions.base import BaseOceanException
2
+
3
+
1
4
  class RetryableError(Exception):
2
5
  """Base exception class for errors that should trigger a retry."""
3
6
 
4
7
  pass
8
+
9
+
10
+ class WebhookProcessingError(BaseOceanException):
11
+ """Base exception for webhook processing errors"""
12
+
13
+ pass
14
+
15
+
16
+ class WebhookEventNotSupportedError(WebhookProcessingError):
17
+ pass
@@ -37,7 +37,10 @@ from port_ocean.core.handlers.port_app_config.models import (
37
37
  )
38
38
  from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
39
39
  from port_ocean.core.models import Entity
40
- from port_ocean.exceptions.webhook_processor import RetryableError
40
+ from port_ocean.exceptions.webhook_processor import (
41
+ RetryableError,
42
+ WebhookEventNotSupportedError,
43
+ )
41
44
  from port_ocean.core.handlers.queue import LocalQueue
42
45
 
43
46
 
@@ -354,7 +357,9 @@ async def test_extractMatchingProcessors_noMatch(
354
357
  test_path = "/test"
355
358
  processor_manager.register_processor(test_path, MockProcessorFalse)
356
359
 
357
- with pytest.raises(ValueError, match="No matching processors found"):
360
+ with pytest.raises(
361
+ WebhookEventNotSupportedError, match="No matching processors found"
362
+ ):
358
363
  async with event_context(
359
364
  EventType.HTTP_REQUEST, trigger_type="request"
360
365
  ) as event:
@@ -409,6 +414,82 @@ async def test_extractMatchingProcessors_onlyOneMatches(
409
414
  assert processor.event.payload == webhook_event.payload
410
415
 
411
416
 
417
+ @pytest.mark.asyncio
418
+ async def test_extractMatchingProcessors_noProcessorsRegistered(
419
+ processor_manager: LiveEventsProcessorManager,
420
+ webhook_event: WebhookEvent,
421
+ mock_port_app_config: PortAppConfig,
422
+ ) -> None:
423
+ """Test that WebhookEventNotSupportedError is raised for unknown events without any registered processors"""
424
+ test_path = "/unknown_path"
425
+ # No processors registered for this path
426
+
427
+ # Manually add the path to _processors_classes to simulate a path with no processors
428
+ processor_manager._processors_classes[test_path] = []
429
+
430
+ with pytest.raises(
431
+ WebhookEventNotSupportedError, match="No matching processors found"
432
+ ):
433
+ async with event_context(
434
+ EventType.HTTP_REQUEST, trigger_type="request"
435
+ ) as event:
436
+ event.port_app_config = mock_port_app_config
437
+ await processor_manager._extract_matching_processors(
438
+ webhook_event, test_path
439
+ )
440
+
441
+
442
+ @pytest.mark.asyncio
443
+ async def test_extractMatchingProcessors_processorsAvailableButKindsNotConfigured(
444
+ processor_manager: LiveEventsProcessorManager,
445
+ webhook_event: WebhookEvent,
446
+ ) -> None:
447
+ """Test that processors available but kinds not configured returns empty list"""
448
+ test_path = "/test"
449
+
450
+ from port_ocean.core.handlers.port_app_config.models import (
451
+ PortAppConfig,
452
+ ResourceConfig,
453
+ )
454
+
455
+ # Create a mock processor that will match the event but return a kind not in the port app config
456
+ class MockProcessorWithUnmappedKind(AbstractWebhookProcessor):
457
+ async def authenticate(
458
+ self, payload: Dict[str, Any], headers: Dict[str, str]
459
+ ) -> bool:
460
+ return True
461
+
462
+ async def validate_payload(self, payload: Dict[str, Any]) -> bool:
463
+ return True
464
+
465
+ async def handle_event(
466
+ self, payload: EventPayload, resource: ResourceConfig
467
+ ) -> WebhookEventRawResults:
468
+ return WebhookEventRawResults(
469
+ updated_raw_results=[], deleted_raw_results=[]
470
+ )
471
+
472
+ async def should_process_event(self, event: WebhookEvent) -> bool:
473
+ return True # This processor will match
474
+
475
+ async def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
476
+ return ["unmapped_kind"] # This kind is not in the mock_port_app_config
477
+
478
+ processor_manager.register_processor(test_path, MockProcessorWithUnmappedKind)
479
+
480
+ empty_port_app_config = PortAppConfig(
481
+ resources=[],
482
+ )
483
+
484
+ async with event_context(EventType.HTTP_REQUEST, trigger_type="request") as event:
485
+ event.port_app_config = empty_port_app_config
486
+ processors = await processor_manager._extract_matching_processors(
487
+ webhook_event, test_path
488
+ )
489
+
490
+ assert len(processors) == 0
491
+
492
+
412
493
  def test_registerProcessor_registrationWorks(
413
494
  processor_manager: LiveEventsProcessorManager,
414
495
  ) -> None:
@@ -853,7 +934,10 @@ async def test_integrationTest_postRequestSent_noMatchingHandlers_entityNotUpser
853
934
  except asyncio.TimeoutError:
854
935
  pytest.fail("Event processing timed out")
855
936
 
856
- assert isinstance(test_state["exception_thrown"], ValueError) is True
937
+ assert (
938
+ isinstance(test_state["exception_thrown"], WebhookEventNotSupportedError)
939
+ is True
940
+ )
857
941
 
858
942
  mock_upsert.assert_not_called()
859
943
  mock_delete.assert_not_called()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.24.8
3
+ Version: 0.24.11
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
@@ -59,7 +59,7 @@ port_ocean/clients/port/authentication.py,sha256=r7r8Ag9WuwXy-CmgeOoj-PHbmJAQxhb
59
59
  port_ocean/clients/port/client.py,sha256=dv0mxIOde6J-wFi1FXXZkoNPVHrZzY7RSMhNkDD9xgA,3566
60
60
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  port_ocean/clients/port/mixins/blueprints.py,sha256=aMCG4zePsMSMjMLiGrU37h5z5_ElfMzTcTvqvOI5wXY,4683
62
- port_ocean/clients/port/mixins/entities.py,sha256=hg4xJCcYml2JLkqEwg7lJUhB5YLm_sadNfiKdf3rP6Q,22811
62
+ port_ocean/clients/port/mixins/entities.py,sha256=NSZCV-IAg5_gQ7LxCMzxKJu_LkFkz809CQ2y8dFhHqE,23239
63
63
  port_ocean/clients/port/mixins/integrations.py,sha256=s6paomK9bYWW-Tu3y2OIaEGSxsXCHyhapVi4JIhhO64,11162
64
64
  port_ocean/clients/port/mixins/migrations.py,sha256=vdL_A_NNUogvzujyaRLIoZEu5vmKDY2BxTjoGP94YzI,1467
65
65
  port_ocean/clients/port/mixins/organization.py,sha256=A2cP5V49KnjoAXxjmnm_XGth4ftPSU0qURNfnyUyS_Y,1041
@@ -104,7 +104,7 @@ port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=C6zJbS3m
104
104
  port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
105
105
  port_ocean/core/handlers/port_app_config/api.py,sha256=r_Th66NEw38IpRdnXZcRvI8ACfvxW_A6V62WLwjWXlQ,1044
106
106
  port_ocean/core/handlers/port_app_config/base.py,sha256=Sup4-X_a7JGa27rMy_OgqGIjFHMlKBpKevicaK3AeHU,2919
107
- port_ocean/core/handlers/port_app_config/models.py,sha256=Rf01rQpQZRse2qfr5PwgOz4pKwb53y0LuW9yp7v_ZoI,2912
107
+ port_ocean/core/handlers/port_app_config/models.py,sha256=pO7oI7GIYZ9c2ZxLu8EQ97U2IPqzsbJf3gRQxlizEjE,2933
108
108
  port_ocean/core/handlers/queue/__init__.py,sha256=1fICM0ZLATmmj6f7cdq_eV2kmw0_jy7y2INuLQIpzIE,121
109
109
  port_ocean/core/handlers/queue/abstract_queue.py,sha256=q_gpaWFFZHxM3XovEbgsDn8jEOLM45iAZWVC81Paxto,620
110
110
  port_ocean/core/handlers/queue/local_queue.py,sha256=EzqsGIX43xbVAcePwTcCg5QDrXATQpy-VzWxxN_OyAM,574
@@ -112,7 +112,7 @@ port_ocean/core/handlers/resync_state_updater/__init__.py,sha256=kG6y-JQGpPfuTHh
112
112
  port_ocean/core/handlers/resync_state_updater/updater.py,sha256=TRYq6QnTtPlJg6MvgZPtQdZPvkAhkvpcmWjtkxCnkg4,3764
113
113
  port_ocean/core/handlers/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
114
  port_ocean/core/handlers/webhook/abstract_webhook_processor.py,sha256=5KwZkdkDd5HdVkXPzKiqabodZKl-hOtMypkTKd8Hq3M,3891
115
- port_ocean/core/handlers/webhook/processor_manager.py,sha256=ipyAXoFtF84EGczyzRcZCzQG4Ippjo4eMsnVxMVz12A,12072
115
+ port_ocean/core/handlers/webhook/processor_manager.py,sha256=4u9Q_djZAzxgwGHlHBmVBG26svEigeSka6GajcETd20,12976
116
116
  port_ocean/core/handlers/webhook/webhook_event.py,sha256=9wHXLY6IGgjuqrwXXvZm_RbYEd-a9qIFNxWnGbfPv6o,3877
117
117
  port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
118
  port_ocean/core/integrations/base.py,sha256=dUhytVM9uUbcDRzG1QWyvBvEJOWZY0dPVV3hXuukOfg,3587
@@ -121,9 +121,9 @@ port_ocean/core/integrations/mixins/events.py,sha256=2L7P3Jhp8XBqddh2_o9Cn4N261n
121
121
  port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
122
122
  port_ocean/core/integrations/mixins/live_events.py,sha256=zM24dhNc7uHx9XYZ6toVhDADPA90EnpOmZxgDegFZbA,4196
123
123
  port_ocean/core/integrations/mixins/sync.py,sha256=Vm_898pLKBwfVewtwouDWsXoxcOLicnAy6pzyqqk6U8,4053
124
- port_ocean/core/integrations/mixins/sync_raw.py,sha256=mY9z6wexl1bC8c3LzOafOzC3RxjRv7NjCmVpQZUypvI,34289
124
+ port_ocean/core/integrations/mixins/sync_raw.py,sha256=AtS0Qr4mIY3vZZxSmnFOLMskiQ_kJUg0_f2ZT8O_Ejw,33670
125
125
  port_ocean/core/integrations/mixins/utils.py,sha256=N76dNu1Y6UEg0_pcSdF4RO6dQIZ8EBfX3xMelgWdMxM,3779
126
- port_ocean/core/models.py,sha256=NYsOBtAqRgmRTb2XYGDW31IxTHSXGRQLOF64apwUZ2Q,2872
126
+ port_ocean/core/models.py,sha256=DNbKpStMINI2lIekKprTqBevqkw_wFuFayN19w1aDfQ,2893
127
127
  port_ocean/core/ocean_types.py,sha256=4VipWFOHEh_d9LmWewQccwx1p2dtrRYW0YURVgNsAjo,1398
128
128
  port_ocean/core/utils/entity_topological_sorter.py,sha256=MDUjM6OuDy4Xj68o-7InNN0w1jqjxeDfeY8U02vySNI,3081
129
129
  port_ocean/core/utils/utils.py,sha256=XJ6ZZBR5hols19TcX4Bh49ygSNhPt3MLncLR-g41GTA,6858
@@ -136,7 +136,7 @@ port_ocean/exceptions/context.py,sha256=mA8HII6Rl4QxKUz98ppy1zX3kaziaen21h1ZWuU3
136
136
  port_ocean/exceptions/core.py,sha256=3LpQrOWdZ-xZ8zm90DmTnFnk0Nms2OgrVIzZBK0Xw5M,931
137
137
  port_ocean/exceptions/port_defaults.py,sha256=2a7Koy541KxMan33mU-gbauUxsumG3NT4itVxSpQqfw,666
138
138
  port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnqI,197
139
- port_ocean/exceptions/webhook_processor.py,sha256=yQYazg53Y-ohb7HfViwq1opH_ZUuUdhHSRxcUNveFpI,114
139
+ port_ocean/exceptions/webhook_processor.py,sha256=4SnkVzVwiacH_Ip4qs1hRHa6GanhnojW_TLTdQQtm7Y,363
140
140
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
141
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
142
142
  port_ocean/helpers/metric/metric.py,sha256=Aacz7bOd8ZCwEPpXAdwLbKRXf28Z4wiViG_GXiV_xWg,14529
@@ -171,7 +171,7 @@ port_ocean/tests/core/handlers/port_app_config/test_api.py,sha256=eJZ6SuFBLz71y4
171
171
  port_ocean/tests/core/handlers/port_app_config/test_base.py,sha256=hSh556bJM9zuELwhwnyKSfd9z06WqWXIfe-6hCl5iKI,9799
172
172
  port_ocean/tests/core/handlers/queue/test_local_queue.py,sha256=9Ly0HzZXbs6Rbl_bstsIdInC3h2bgABU3roP9S_PnJM,2582
173
173
  port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py,sha256=zKwHhPAYEZoZ5Z2UETp1t--mbkS8uyvlXThB0obZTTc,3340
174
- port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=6Zap3rKBd8Td7-a9af6_FWOGNDN4oZhNdWeeaICsLD0,49386
174
+ port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=rqNFc-S_ZnPyDTSFTdiGcRFKbeDGfWQCH_f2UPbfcAA,52310
175
175
  port_ocean/tests/core/handlers/webhook/test_webhook_event.py,sha256=oR4dEHLO65mp6rkfNfszZcfFoRZlB8ZWee4XetmsuIk,3181
176
176
  port_ocean/tests/core/test_utils.py,sha256=Z3kdhb5V7Svhcyy3EansdTpgHL36TL6erNtU-OPwAcI,2647
177
177
  port_ocean/tests/core/utils/test_entity_topological_sorter.py,sha256=zuq5WSPy_88PemG3mOUIHTxWMR_js1R7tOzUYlgBd68,3447
@@ -200,8 +200,8 @@ port_ocean/utils/repeat.py,sha256=U2OeCkHPWXmRTVoPV-VcJRlQhcYqPWI5NfmPlb1JIbc,32
200
200
  port_ocean/utils/signal.py,sha256=mMVq-1Ab5YpNiqN4PkiyTGlV_G0wkUDMMjTZp5z3pb0,1514
201
201
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
202
202
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
203
- port_ocean-0.24.8.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
204
- port_ocean-0.24.8.dist-info/METADATA,sha256=cu0CSxuGdnkD2bsnwo9sK6K2FrZP5sojiDhmFqfpE9Q,6852
205
- port_ocean-0.24.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
206
- port_ocean-0.24.8.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
207
- port_ocean-0.24.8.dist-info/RECORD,,
203
+ port_ocean-0.24.11.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
204
+ port_ocean-0.24.11.dist-info/METADATA,sha256=5SPsmnSpVV2tiRRGdbP0SeRJZ3SJ2EUiPR_t-4oGupI,6853
205
+ port_ocean-0.24.11.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
206
+ port_ocean-0.24.11.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
207
+ port_ocean-0.24.11.dist-info/RECORD,,