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.
- integrations/_infra/Dockerfile.Deb +6 -1
- integrations/_infra/Dockerfile.local +1 -0
- port_ocean/clients/port/authentication.py +19 -0
- port_ocean/clients/port/client.py +3 -0
- port_ocean/clients/port/mixins/actions.py +93 -0
- port_ocean/clients/port/mixins/blueprints.py +0 -12
- port_ocean/clients/port/mixins/entities.py +79 -44
- port_ocean/clients/port/mixins/integrations.py +7 -2
- port_ocean/config/settings.py +35 -3
- port_ocean/context/ocean.py +7 -5
- port_ocean/core/defaults/initialize.py +12 -5
- port_ocean/core/event_listener/__init__.py +7 -0
- port_ocean/core/event_listener/actions_only.py +42 -0
- port_ocean/core/event_listener/base.py +4 -1
- port_ocean/core/event_listener/factory.py +18 -9
- port_ocean/core/event_listener/http.py +4 -3
- port_ocean/core/event_listener/kafka.py +3 -2
- port_ocean/core/event_listener/once.py +5 -2
- port_ocean/core/event_listener/polling.py +4 -3
- port_ocean/core/event_listener/webhooks_only.py +3 -2
- port_ocean/core/handlers/actions/__init__.py +7 -0
- port_ocean/core/handlers/actions/abstract_executor.py +150 -0
- port_ocean/core/handlers/actions/execution_manager.py +434 -0
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +479 -17
- port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +137 -0
- port_ocean/core/handlers/port_app_config/models.py +4 -2
- port_ocean/core/handlers/resync_state_updater/updater.py +4 -2
- port_ocean/core/handlers/webhook/abstract_webhook_processor.py +16 -0
- port_ocean/core/handlers/webhook/processor_manager.py +30 -12
- port_ocean/core/integrations/mixins/sync_raw.py +10 -5
- port_ocean/core/integrations/mixins/utils.py +250 -29
- port_ocean/core/models.py +35 -2
- port_ocean/core/utils/utils.py +16 -5
- port_ocean/exceptions/execution_manager.py +22 -0
- port_ocean/helpers/metric/metric.py +1 -1
- port_ocean/helpers/retry.py +4 -40
- port_ocean/log/logger_setup.py +2 -2
- port_ocean/ocean.py +31 -5
- port_ocean/tests/clients/port/mixins/test_entities.py +71 -5
- port_ocean/tests/core/event_listener/test_kafka.py +14 -7
- port_ocean/tests/core/handlers/actions/test_execution_manager.py +837 -0
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +932 -1
- port_ocean/tests/core/handlers/entity_processor/test_jq_input_evaluator.py +932 -0
- port_ocean/tests/core/handlers/webhook/test_processor_manager.py +3 -1
- port_ocean/tests/core/utils/test_get_port_diff.py +164 -0
- port_ocean/tests/helpers/test_retry.py +241 -1
- port_ocean/tests/utils/test_cache.py +240 -0
- port_ocean/utils/cache.py +45 -9
- {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/METADATA +2 -1
- {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/RECORD +53 -43
- {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.28.2.dist-info → port_ocean-0.29.0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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=
|
|
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(
|
|
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(
|
|
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=
|
|
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=
|
|
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=
|
|
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(
|
|
74
|
+
config = KafkaEventListenerSettings(
|
|
75
|
+
type=EventListenerType.KAFKA, brokers='["single:9092"]'
|
|
76
|
+
)
|
|
70
77
|
assert config.brokers == "single:9092"
|