port-ocean 0.24.17__py3-none-any.whl → 0.24.19__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.
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from loguru import logger
2
3
 
3
4
  from port_ocean.clients.port.types import UserAgentType
@@ -125,20 +126,27 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
125
126
  ) -> list[Entity]:
126
127
  logger.info(f"Upserting {len(entities)} entities")
127
128
  modified_entities: list[Entity] = []
128
- upserted_entities: list[tuple[bool, Entity]] = []
129
129
 
130
- upserted_entities = await self.context.port_client.upsert_entities_in_batches(
131
- entities,
132
- event.port_app_config.get_port_request_options(),
133
- user_agent_type,
134
- should_raise=False,
135
- )
130
+ blueprint_groups: dict[str, list[Entity]] = defaultdict(list)
131
+ for entity in entities:
132
+ blueprint_groups[entity.blueprint].append(entity)
133
+
134
+ for blueprint_entities in blueprint_groups.values():
135
+ upserted_entities = (
136
+ await self.context.port_client.upsert_entities_in_batches(
137
+ blueprint_entities,
138
+ event.port_app_config.get_port_request_options(),
139
+ user_agent_type,
140
+ should_raise=False,
141
+ )
142
+ )
143
+
144
+ for is_upserted, entity in upserted_entities:
145
+ if is_upserted:
146
+ modified_entities.append(entity)
147
+ else:
148
+ event.entity_topological_sorter.register_entity(entity)
136
149
 
137
- for is_upserted, entity in upserted_entities:
138
- if is_upserted:
139
- modified_entities.append(entity)
140
- else:
141
- event.entity_topological_sorter.register_entity(entity)
142
150
  return modified_entities
143
151
 
144
152
  async def delete(
@@ -735,6 +735,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
735
735
 
736
736
  logger.info("Finished executing resync_complete hooks")
737
737
 
738
+ return True
739
+
738
740
 
739
741
  @TimeMetric(MetricPhase.RESYNC)
740
742
  async def sync_raw_all(
@@ -805,7 +807,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
805
807
  logger.warning("Resync aborted successfully, skipping delete phase. This leads to an incomplete state")
806
808
  raise
807
809
  else:
808
- await self.resync_reconciliation(
810
+ success = await self.resync_reconciliation(
809
811
  creation_results,
810
812
  did_fetched_current_state,
811
813
  user_agent_type,
@@ -813,6 +815,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
813
815
  silent
814
816
  )
815
817
  await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RECONCILIATION])
818
+ return success
816
819
  finally:
817
820
  await ocean.app.cache_provider.clear()
818
821
  if ocean.app.process_execution_mode == ProcessExecutionMode.multi_process:
@@ -228,3 +228,61 @@ async def test_using_create_entity_helper(
228
228
 
229
229
  mock_upsert.assert_called_once()
230
230
  assert len(result) == 1
231
+
232
+
233
+ @pytest.mark.asyncio
234
+ async def test_upsert_groups_entities_by_blueprint(
235
+ mock_ocean: Ocean,
236
+ mock_context: PortOceanContext,
237
+ mock_port_app_config: PortAppConfig,
238
+ ) -> None:
239
+ """Test that upsert groups entities by blueprint and calls upsert_entities_in_batches separately for each group."""
240
+ applier = HttpEntitiesStateApplier(mock_context)
241
+
242
+ # Create entities with different blueprints
243
+ service_entity_1 = Entity(identifier="service_1", blueprint="service")
244
+ service_entity_2 = Entity(identifier="service_2", blueprint="service")
245
+ deployment_entity = Entity(identifier="deployment_1", blueprint="deployment")
246
+ user_entity = Entity(identifier="user_1", blueprint="user")
247
+
248
+ entities = [service_entity_1, deployment_entity, service_entity_2, user_entity]
249
+
250
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
251
+ event.port_app_config = mock_port_app_config
252
+
253
+ mock_ocean.config.upsert_entities_batch_max_length = 100
254
+ mock_ocean.config.upsert_entities_batch_max_size_in_bytes = 1000
255
+
256
+ # Mock the upsert_entities_in_batches method to track calls
257
+ mock_upsert = AsyncMock()
258
+ mock_upsert.side_effect = [
259
+ [
260
+ (True, service_entity_1),
261
+ (True, service_entity_2),
262
+ ], # First call for "service" blueprint
263
+ [(True, deployment_entity)], # Second call for "deployment" blueprint
264
+ [(True, user_entity)], # Third call for "user" blueprint
265
+ ]
266
+ setattr(mock_ocean.port_client, "upsert_entities_in_batches", mock_upsert)
267
+
268
+ result = await applier.upsert(entities, UserAgentType.exporter)
269
+
270
+ assert mock_upsert.call_count == 3
271
+
272
+ assert len(result) == 4
273
+
274
+ calls = mock_upsert.call_args_list
275
+
276
+ called_entities_groups = [call[0][0] for call in calls]
277
+ blueprint_counts = {}
278
+ for entities_group in called_entities_groups:
279
+ blueprint = entities_group[0].blueprint
280
+ for entity in entities_group:
281
+ assert entity.blueprint == blueprint
282
+ blueprint_counts[blueprint] = len(entities_group)
283
+
284
+ # Verify the expected blueprint counts
285
+ assert blueprint_counts["service"] == 2
286
+ assert blueprint_counts["deployment"] == 1
287
+ assert blueprint_counts["user"] == 1
288
+ assert len(blueprint_counts) == 3
@@ -139,9 +139,10 @@ async def test_sync_raw_mixin_self_dependency(
139
139
  mock_order_by_entities_dependencies,
140
140
  ):
141
141
 
142
- await mock_sync_raw_mixin.sync_raw_all(
142
+ res = await mock_sync_raw_mixin.sync_raw_all(
143
143
  trigger_type="machine", user_agent_type=UserAgentType.exporter
144
144
  )
145
+ assert res is True
145
146
 
146
147
  assert (
147
148
  len(event.entity_topological_sorter.entities) == 1
@@ -276,9 +277,10 @@ async def test_sync_raw_mixin_circular_dependency(
276
277
  mock_order_by_entities_dependencies,
277
278
  ):
278
279
 
279
- await mock_sync_raw_mixin.sync_raw_all(
280
+ res = await mock_sync_raw_mixin.sync_raw_all(
280
281
  trigger_type="machine", user_agent_type=UserAgentType.exporter
281
282
  )
283
+ assert res is True
282
284
 
283
285
  assert (
284
286
  len(event.entity_topological_sorter.entities) == 2
@@ -420,9 +422,10 @@ async def test_sync_raw_mixin_dependency(
420
422
  mock_order_by_entities_dependencies,
421
423
  ):
422
424
 
423
- await mock_sync_raw_mixin.sync_raw_all(
425
+ res = await mock_sync_raw_mixin.sync_raw_all(
424
426
  trigger_type="machine", user_agent_type=UserAgentType.exporter
425
427
  )
428
+ assert res is True
426
429
 
427
430
  assert event.entity_topological_sorter.register_entity.call_count == 5
428
431
  assert (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.24.17
3
+ Version: 0.24.19
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
@@ -95,7 +95,7 @@ port_ocean/core/handlers/base.py,sha256=cTarblazu8yh8xz2FpB-dzDKuXxtoi143XJgPbV_
95
95
  port_ocean/core/handlers/entities_state_applier/__init__.py,sha256=kgLZDCeCEzi4r-0nzW9k78haOZNf6PX7mJOUr34A4c8,173
96
96
  port_ocean/core/handlers/entities_state_applier/base.py,sha256=5wHL0icfFAYRPqk8iV_wN49GdJ3aRUtO8tumSxBi4Wo,2268
97
97
  port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
- port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=QzN8OWgp-i4bRwslnLo5qjS7rt93Stceq9C4FD11Vy4,6372
98
+ port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=NsLW35H1dhP5FFD7kHZxA2ndSBcD_-btan7qNWfvWrs,6683
99
99
  port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=1zncwCbE-Gej0xaWKlzZgoXxOBe9bgs_YxlZ8QW3NdI,1751
100
100
  port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=lyv6xKzhYfd6TioUgR3AVRSJqj7JpAaj1LxxU2xAqeo,1720
101
101
  port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
@@ -121,7 +121,7 @@ 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=0wFpyWSlKREIb3BNfkUg1Tj6ueivsig2YjPmYchb-mY,33823
124
+ port_ocean/core/integrations/mixins/sync_raw.py,sha256=NTclUHBYH_RK895zM3VGfd3MyPRYvfqnWudXYN5RFqQ,33885
125
125
  port_ocean/core/integrations/mixins/utils.py,sha256=N76dNu1Y6UEg0_pcSdF4RO6dQIZ8EBfX3xMelgWdMxM,3779
126
126
  port_ocean/core/models.py,sha256=DNbKpStMINI2lIekKprTqBevqkw_wFuFayN19w1aDfQ,2893
127
127
  port_ocean/core/ocean_types.py,sha256=4VipWFOHEh_d9LmWewQccwx1p2dtrRYW0YURVgNsAjo,1398
@@ -163,10 +163,10 @@ port_ocean/tests/clients/port/mixins/test_organization_mixin.py,sha256=zzKYz3h8d
163
163
  port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
164
164
  port_ocean/tests/core/conftest.py,sha256=7K_M1--wQ08VmiQRB0vo1nst2X00cwsuBS5UfERsnG8,7589
165
165
  port_ocean/tests/core/defaults/test_common.py,sha256=sR7RqB3ZYV6Xn6NIg-c8k5K6JcGsYZ2SCe_PYX5vLYM,5560
166
- port_ocean/tests/core/handlers/entities_state_applier/test_applier.py,sha256=eJYXc7AwrV0XRS6HpixwzghjB3pspT5Gxr9twvJE7fk,8290
166
+ port_ocean/tests/core/handlers/entities_state_applier/test_applier.py,sha256=_CZyViY9_gnxjY6ogWcDdmEDuejvpALogf9ESjVAwFY,10675
167
167
  port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=8WpMn559Mf0TFWmloRpZrVgr6yWwyA0C4n2lVHCtyq4,13596
168
168
  port_ocean/tests/core/handlers/mixins/test_live_events.py,sha256=iAwVpr3n3PIkXQLw7hxd-iB_SR_vyfletVXJLOmyz28,12480
169
- port_ocean/tests/core/handlers/mixins/test_sync_raw.py,sha256=-05ec3gRsmnMgmqzkIRjpm_yMdRZc3O3Br3RLFW2Kjw,44297
169
+ port_ocean/tests/core/handlers/mixins/test_sync_raw.py,sha256=p1AyIc6Rx18miL6Sln9gNvhC39SkXXIqYQstyYDww1c,44420
170
170
  port_ocean/tests/core/handlers/port_app_config/test_api.py,sha256=eJZ6SuFBLz71y4ca3DNqKag6d6HUjNJS0aqQPwiLMTI,1999
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
@@ -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.17.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
204
- port_ocean-0.24.17.dist-info/METADATA,sha256=-kHdBYUARFg7MopioSN5dMmVtqE327eyVKUhQ8QcE1M,6856
205
- port_ocean-0.24.17.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
206
- port_ocean-0.24.17.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
207
- port_ocean-0.24.17.dist-info/RECORD,,
203
+ port_ocean-0.24.19.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
204
+ port_ocean-0.24.19.dist-info/METADATA,sha256=HE9-dswSXnOaFu4p3hJkDWy6syqUIADmnYhVSN2F4Vg,6856
205
+ port_ocean-0.24.19.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
206
+ port_ocean-0.24.19.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
207
+ port_ocean-0.24.19.dist-info/RECORD,,