port-ocean 0.28.2__py3-none-any.whl → 0.29.0__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 (53) hide show
  1. integrations/_infra/Dockerfile.Deb +6 -1
  2. integrations/_infra/Dockerfile.local +1 -0
  3. port_ocean/clients/port/authentication.py +19 -0
  4. port_ocean/clients/port/client.py +3 -0
  5. port_ocean/clients/port/mixins/actions.py +93 -0
  6. port_ocean/clients/port/mixins/blueprints.py +0 -12
  7. port_ocean/clients/port/mixins/entities.py +79 -44
  8. port_ocean/clients/port/mixins/integrations.py +7 -2
  9. port_ocean/config/settings.py +35 -3
  10. port_ocean/context/ocean.py +7 -5
  11. port_ocean/core/defaults/initialize.py +12 -5
  12. port_ocean/core/event_listener/__init__.py +7 -0
  13. port_ocean/core/event_listener/actions_only.py +42 -0
  14. port_ocean/core/event_listener/base.py +4 -1
  15. port_ocean/core/event_listener/factory.py +18 -9
  16. port_ocean/core/event_listener/http.py +4 -3
  17. port_ocean/core/event_listener/kafka.py +3 -2
  18. port_ocean/core/event_listener/once.py +5 -2
  19. port_ocean/core/event_listener/polling.py +4 -3
  20. port_ocean/core/event_listener/webhooks_only.py +3 -2
  21. port_ocean/core/handlers/actions/__init__.py +7 -0
  22. port_ocean/core/handlers/actions/abstract_executor.py +150 -0
  23. port_ocean/core/handlers/actions/execution_manager.py +434 -0
  24. port_ocean/core/handlers/entity_processor/jq_entity_processor.py +479 -17
  25. port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +137 -0
  26. port_ocean/core/handlers/port_app_config/models.py +4 -2
  27. port_ocean/core/handlers/resync_state_updater/updater.py +4 -2
  28. port_ocean/core/handlers/webhook/abstract_webhook_processor.py +16 -0
  29. port_ocean/core/handlers/webhook/processor_manager.py +30 -12
  30. port_ocean/core/integrations/mixins/sync_raw.py +10 -5
  31. port_ocean/core/integrations/mixins/utils.py +250 -29
  32. port_ocean/core/models.py +35 -2
  33. port_ocean/core/utils/utils.py +16 -5
  34. port_ocean/exceptions/execution_manager.py +22 -0
  35. port_ocean/helpers/metric/metric.py +1 -1
  36. port_ocean/helpers/retry.py +4 -40
  37. port_ocean/log/logger_setup.py +2 -2
  38. port_ocean/ocean.py +31 -5
  39. port_ocean/tests/clients/port/mixins/test_entities.py +71 -5
  40. port_ocean/tests/core/event_listener/test_kafka.py +14 -7
  41. port_ocean/tests/core/handlers/actions/test_execution_manager.py +837 -0
  42. port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +932 -1
  43. port_ocean/tests/core/handlers/entity_processor/test_jq_input_evaluator.py +932 -0
  44. port_ocean/tests/core/handlers/webhook/test_processor_manager.py +3 -1
  45. port_ocean/tests/core/utils/test_get_port_diff.py +164 -0
  46. port_ocean/tests/helpers/test_retry.py +241 -1
  47. port_ocean/tests/utils/test_cache.py +240 -0
  48. port_ocean/utils/cache.py +45 -9
  49. {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/METADATA +2 -1
  50. {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/RECORD +53 -43
  51. {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/LICENSE.md +0 -0
  52. {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/WHEEL +0 -0
  53. {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/entry_points.txt +0 -0
@@ -179,7 +179,7 @@ async def test_search_entities_uses_datasource_route_when_query_is_none(
179
179
  ) -> None:
180
180
  """Test that search_entities uses datasource route when query is None"""
181
181
  mock_response = MagicMock()
182
- mock_response.json.return_value = {"entities": []}
182
+ mock_response.json.return_value = {"entities": [], "next": None}
183
183
  mock_response.is_error = False
184
184
  mock_response.status_code = 200
185
185
  mock_response.headers = {}
@@ -208,8 +208,74 @@ async def test_search_entities_uses_datasource_route_when_query_is_none(
208
208
  == "https://api.getport.io/v1/blueprints/entities/datasource-entities"
209
209
  )
210
210
 
211
- expected_json = {
212
- "datasource_prefix": "port-ocean/test-integration/",
213
- "datasource_suffix": "/test-identifier/sync",
211
+ sent_json = call_args[1]["json"]
212
+ assert sent_json["datasource_prefix"] == "port-ocean/test-integration/"
213
+ assert sent_json["datasource_suffix"] == "/test-identifier/sync"
214
+
215
+
216
+ async def test_search_entities_uses_datasource_route_when_query_is_none_two_pages(
217
+ entity_client: EntityClientMixin,
218
+ ) -> None:
219
+ """Test that search_entities uses datasource route when query is None"""
220
+ # First response with pagination token
221
+ mock_response_first = MagicMock()
222
+ mock_response_first.json.return_value = {
223
+ "entities": [
224
+ Entity(identifier="entity_1", blueprint="entity_1"),
225
+ Entity(identifier="entity_2", blueprint="entity_2"),
226
+ ],
227
+ "next": "next_page_token",
228
+ }
229
+ mock_response_first.is_error = False
230
+ mock_response_first.status_code = 200
231
+ mock_response_first.headers = {}
232
+
233
+ # Second response without pagination token (end of pagination)
234
+ mock_response_second = MagicMock()
235
+ mock_response_second.json.return_value = {
236
+ "entities": [Entity(identifier="entity_3", blueprint="entity_3")],
237
+ "next": None,
214
238
  }
215
- assert call_args[1]["json"] == expected_json
239
+ mock_response_second.is_error = False
240
+ mock_response_second.status_code = 200
241
+ mock_response_second.headers = {}
242
+
243
+ # Mock the client to return different responses for each call
244
+ entity_client.client.post = AsyncMock(side_effect=[mock_response_first, mock_response_second]) # type: ignore
245
+ entity_client.auth.headers = AsyncMock(return_value={"Authorization": "Bearer test"}) # type: ignore
246
+
247
+ entity_client.auth.integration_type = "test-integration"
248
+ entity_client.auth.integration_identifier = "test-identifier"
249
+ entity_client.auth.api_url = "https://api.getport.io/v1"
250
+
251
+ mock_user_agent_type = MagicMock()
252
+ mock_user_agent_type.value = "sync"
253
+
254
+ entities = await entity_client.search_entities(
255
+ user_agent_type=mock_user_agent_type,
256
+ query=None,
257
+ )
258
+
259
+ # Should call the datasource-entities endpoint exactly twice for pagination
260
+ assert entity_client.client.post.await_count == 2
261
+ assert len(entities) == 3
262
+
263
+ # Check first call
264
+ first_call_args = entity_client.client.post.call_args_list[0]
265
+ assert (
266
+ first_call_args[0][0]
267
+ == "https://api.getport.io/v1/blueprints/entities/datasource-entities"
268
+ )
269
+ first_sent_json = first_call_args[1]["json"]
270
+ assert first_sent_json["datasource_prefix"] == "port-ocean/test-integration/"
271
+ assert first_sent_json["datasource_suffix"] == "/test-identifier/sync"
272
+
273
+ # Check second call
274
+ second_call_args = entity_client.client.post.call_args_list[1]
275
+ assert (
276
+ second_call_args[0][0]
277
+ == "https://api.getport.io/v1/blueprints/entities/datasource-entities"
278
+ )
279
+ second_sent_json = second_call_args[1]["json"]
280
+ assert second_sent_json["datasource_prefix"] == "port-ocean/test-integration/"
281
+ assert second_sent_json["datasource_suffix"] == "/test-identifier/sync"
@@ -1,11 +1,12 @@
1
1
  from port_ocean.core.event_listener.kafka import KafkaEventListenerSettings
2
+ from port_ocean.core.models import EventListenerType
2
3
  import pytest
3
4
  from pydantic import ValidationError
4
5
 
5
6
 
6
7
  def test_default_kafka_settings() -> None:
7
8
  """Test default values are properly set"""
8
- config = KafkaEventListenerSettings(type="KAFKA")
9
+ config = KafkaEventListenerSettings(type=EventListenerType.KAFKA)
9
10
  assert config.type == "KAFKA"
10
11
  assert config.security_protocol == "SASL_SSL"
11
12
  assert config.authentication_mechanism == "SCRAM-SHA-512"
@@ -17,28 +18,32 @@ def test_default_kafka_settings() -> None:
17
18
  def test_brokers_json_array_parsing() -> None:
18
19
  """Test that JSON array strings get converted to comma-separated"""
19
20
  json_brokers = '["broker1:9092", "broker2:9092", "broker3:9092"]'
20
- config = KafkaEventListenerSettings(type="KAFKA", brokers=json_brokers)
21
+ config = KafkaEventListenerSettings(
22
+ type=EventListenerType.KAFKA, brokers=json_brokers
23
+ )
21
24
  assert config.brokers == "broker1:9092,broker2:9092,broker3:9092"
22
25
 
23
26
 
24
27
  def test_brokers_regular_string_unchanged() -> None:
25
28
  """Test that regular comma-separated strings pass through unchanged"""
26
29
  regular_brokers = "broker1:9092,broker2:9092"
27
- config = KafkaEventListenerSettings(type="KAFKA", brokers=regular_brokers)
30
+ config = KafkaEventListenerSettings(
31
+ type=EventListenerType.KAFKA, brokers=regular_brokers
32
+ )
28
33
  assert config.brokers == regular_brokers
29
34
 
30
35
 
31
36
  def test_brokers_malformed_json_unchanged() -> None:
32
37
  """Test that malformed JSON strings don't break validation"""
33
38
  bad_json = "[broker1:9092, broker2:9092"
34
- config = KafkaEventListenerSettings(type="KAFKA", brokers=bad_json)
39
+ config = KafkaEventListenerSettings(type=EventListenerType.KAFKA, brokers=bad_json)
35
40
  assert config.brokers == bad_json
36
41
 
37
42
 
38
43
  def test_custom_values() -> None:
39
44
  """Test overriding default values"""
40
45
  config = KafkaEventListenerSettings(
41
- type="KAFKA",
46
+ type=EventListenerType.KAFKA,
42
47
  brokers="custom:9092",
43
48
  security_protocol="PLAINTEXT",
44
49
  authentication_mechanism="PLAIN",
@@ -60,11 +65,13 @@ def test_type_literal_validation() -> None:
60
65
 
61
66
  def test_empty_brokers_array() -> None:
62
67
  """Test empty JSON array becomes empty string"""
63
- config = KafkaEventListenerSettings(type="KAFKA", brokers="[]")
68
+ config = KafkaEventListenerSettings(type=EventListenerType.KAFKA, brokers="[]")
64
69
  assert config.brokers == ""
65
70
 
66
71
 
67
72
  def test_single_broker_array() -> None:
68
73
  """Test single broker in JSON array"""
69
- config = KafkaEventListenerSettings(type="KAFKA", brokers='["single:9092"]')
74
+ config = KafkaEventListenerSettings(
75
+ type=EventListenerType.KAFKA, brokers='["single:9092"]'
76
+ )
70
77
  assert config.brokers == "single:9092"