port-ocean 0.5.5__py3-none-any.whl → 0.17.8__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.
- integrations/_infra/Dockerfile.Deb +56 -0
- integrations/_infra/Dockerfile.alpine +108 -0
- integrations/_infra/Dockerfile.base.builder +26 -0
- integrations/_infra/Dockerfile.base.runner +13 -0
- integrations/_infra/Dockerfile.dockerignore +94 -0
- {port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}} → integrations/_infra}/Makefile +21 -8
- integrations/_infra/grpcio.sh +18 -0
- integrations/_infra/init.sh +5 -0
- port_ocean/bootstrap.py +1 -1
- port_ocean/cli/commands/defaults/clean.py +3 -1
- port_ocean/cli/commands/new.py +42 -7
- port_ocean/cli/commands/sail.py +7 -1
- port_ocean/cli/cookiecutter/cookiecutter.json +3 -0
- port_ocean/cli/cookiecutter/hooks/post_gen_project.py +20 -3
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +6 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +41 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +16 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +6 -7
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +1 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +7 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +1 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +16 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +21 -10
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +2 -0
- port_ocean/clients/port/authentication.py +16 -4
- port_ocean/clients/port/client.py +17 -0
- port_ocean/clients/port/mixins/blueprints.py +7 -8
- port_ocean/clients/port/mixins/entities.py +108 -53
- port_ocean/clients/port/mixins/integrations.py +23 -34
- port_ocean/clients/port/retry_transport.py +0 -5
- port_ocean/clients/port/utils.py +9 -3
- port_ocean/config/base.py +16 -16
- port_ocean/config/dynamic.py +2 -0
- port_ocean/config/settings.py +79 -11
- port_ocean/context/event.py +18 -5
- port_ocean/context/ocean.py +14 -3
- port_ocean/core/defaults/clean.py +10 -3
- port_ocean/core/defaults/common.py +25 -9
- port_ocean/core/defaults/initialize.py +111 -100
- port_ocean/core/event_listener/__init__.py +8 -0
- port_ocean/core/event_listener/base.py +49 -10
- port_ocean/core/event_listener/factory.py +9 -1
- port_ocean/core/event_listener/http.py +11 -3
- port_ocean/core/event_listener/kafka.py +24 -5
- port_ocean/core/event_listener/once.py +96 -4
- port_ocean/core/event_listener/polling.py +16 -14
- port_ocean/core/event_listener/webhooks_only.py +41 -0
- port_ocean/core/handlers/__init__.py +1 -2
- port_ocean/core/handlers/entities_state_applier/base.py +4 -1
- port_ocean/core/handlers/entities_state_applier/port/applier.py +29 -87
- port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +5 -2
- port_ocean/core/handlers/entity_processor/base.py +26 -22
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +253 -45
- port_ocean/core/handlers/port_app_config/base.py +55 -15
- port_ocean/core/handlers/port_app_config/models.py +24 -5
- port_ocean/core/handlers/resync_state_updater/__init__.py +5 -0
- port_ocean/core/handlers/resync_state_updater/updater.py +84 -0
- port_ocean/core/integrations/base.py +5 -7
- port_ocean/core/integrations/mixins/events.py +3 -1
- port_ocean/core/integrations/mixins/sync.py +4 -2
- port_ocean/core/integrations/mixins/sync_raw.py +209 -74
- port_ocean/core/integrations/mixins/utils.py +1 -1
- port_ocean/core/models.py +44 -0
- port_ocean/core/ocean_types.py +29 -11
- port_ocean/core/utils/entity_topological_sorter.py +90 -0
- port_ocean/core/utils/utils.py +109 -0
- port_ocean/debug_cli.py +5 -0
- port_ocean/exceptions/core.py +4 -0
- port_ocean/exceptions/port_defaults.py +0 -2
- port_ocean/helpers/retry.py +85 -24
- port_ocean/log/handlers.py +23 -2
- port_ocean/log/logger_setup.py +8 -1
- port_ocean/log/sensetive.py +25 -10
- port_ocean/middlewares.py +10 -2
- port_ocean/ocean.py +57 -24
- port_ocean/run.py +10 -5
- port_ocean/tests/__init__.py +0 -0
- port_ocean/tests/clients/port/mixins/test_entities.py +53 -0
- port_ocean/tests/conftest.py +4 -0
- port_ocean/tests/core/defaults/test_common.py +166 -0
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +350 -0
- port_ocean/tests/core/handlers/mixins/test_sync_raw.py +552 -0
- port_ocean/tests/core/test_utils.py +73 -0
- port_ocean/tests/core/utils/test_entity_topological_sorter.py +99 -0
- port_ocean/tests/helpers/__init__.py +0 -0
- port_ocean/tests/helpers/fake_port_api.py +191 -0
- port_ocean/tests/helpers/fixtures.py +46 -0
- port_ocean/tests/helpers/integration.py +31 -0
- port_ocean/tests/helpers/ocean_app.py +66 -0
- port_ocean/tests/helpers/port_client.py +21 -0
- port_ocean/tests/helpers/smoke_test.py +82 -0
- port_ocean/tests/log/test_handlers.py +71 -0
- port_ocean/tests/test_smoke.py +74 -0
- port_ocean/tests/utils/test_async_iterators.py +45 -0
- port_ocean/tests/utils/test_cache.py +189 -0
- port_ocean/utils/async_iterators.py +109 -0
- port_ocean/utils/cache.py +37 -1
- port_ocean/utils/misc.py +22 -4
- port_ocean/utils/queue_utils.py +88 -0
- port_ocean/utils/signal.py +1 -4
- port_ocean/utils/time.py +54 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/METADATA +27 -19
- port_ocean-0.17.8.dist-info/RECORD +164 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/WHEEL +1 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -94
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -15
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -17
- port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -40
- port_ocean/core/utils.py +0 -65
- port_ocean-0.5.5.dist-info/RECORD +0 -129
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/entry_points.txt +0 -0
|
@@ -10,6 +10,7 @@ from port_ocean.core.event_listener.base import (
|
|
|
10
10
|
EventListenerSettings,
|
|
11
11
|
)
|
|
12
12
|
from port_ocean.utils.repeat import repeat_every
|
|
13
|
+
from port_ocean.utils.signal import signal_handler
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class PollingEventListenerSettings(EventListenerSettings):
|
|
@@ -26,9 +27,6 @@ class PollingEventListenerSettings(EventListenerSettings):
|
|
|
26
27
|
resync_on_start: bool = True
|
|
27
28
|
interval: int = 60
|
|
28
29
|
|
|
29
|
-
def to_request(self) -> dict[str, Any]:
|
|
30
|
-
return {}
|
|
31
|
-
|
|
32
30
|
|
|
33
31
|
class PollingEventListener(BaseEventListener):
|
|
34
32
|
"""
|
|
@@ -48,7 +46,16 @@ class PollingEventListener(BaseEventListener):
|
|
|
48
46
|
):
|
|
49
47
|
super().__init__(events)
|
|
50
48
|
self.event_listener_config = event_listener_config
|
|
51
|
-
|
|
49
|
+
|
|
50
|
+
def should_resync(self, last_updated_at: str) -> bool:
|
|
51
|
+
_last_updated_at = (
|
|
52
|
+
ocean.app.resync_state_updater.last_integration_state_updated_at
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if _last_updated_at is None:
|
|
56
|
+
return self.event_listener_config.resync_on_start
|
|
57
|
+
|
|
58
|
+
return _last_updated_at != last_updated_at
|
|
52
59
|
|
|
53
60
|
async def _start(self) -> None:
|
|
54
61
|
"""
|
|
@@ -68,18 +75,13 @@ class PollingEventListener(BaseEventListener):
|
|
|
68
75
|
integration = await ocean.app.port_client.get_current_integration()
|
|
69
76
|
last_updated_at = integration["updatedAt"]
|
|
70
77
|
|
|
71
|
-
should_resync
|
|
72
|
-
self._last_updated_at is not None
|
|
73
|
-
or self.event_listener_config.resync_on_start
|
|
74
|
-
) and self._last_updated_at != last_updated_at
|
|
75
|
-
|
|
76
|
-
if should_resync:
|
|
78
|
+
if self.should_resync(last_updated_at):
|
|
77
79
|
logger.info("Detected change in integration, resyncing")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self.events["on_resync"]({}) # type: ignore
|
|
80
|
+
ocean.app.resync_state_updater.last_integration_state_updated_at = (
|
|
81
|
+
last_updated_at
|
|
81
82
|
)
|
|
82
|
-
self.
|
|
83
|
+
running_task: Task[Any] = get_event_loop().create_task(self._resync({}))
|
|
84
|
+
signal_handler.register(running_task.cancel)
|
|
83
85
|
|
|
84
86
|
await running_task
|
|
85
87
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from loguru import logger
|
|
4
|
+
|
|
5
|
+
from port_ocean.core.event_listener.base import (
|
|
6
|
+
BaseEventListener,
|
|
7
|
+
EventListenerEvents,
|
|
8
|
+
EventListenerSettings,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WebhooksOnlyEventListenerSettings(EventListenerSettings):
|
|
13
|
+
"""
|
|
14
|
+
This class inherits from `EventListenerSettings`, which provides a foundation for creating event listener settings.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
type: Literal["WEBHOOKS_ONLY"]
|
|
18
|
+
should_resync: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WebhooksOnlyEventListener(BaseEventListener):
|
|
22
|
+
"""
|
|
23
|
+
No resync event listener.
|
|
24
|
+
|
|
25
|
+
It is used to handle events exclusively through webhooks without supporting resync events.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
|
|
29
|
+
event_listener_config (OnceEventListenerSettings): The event listener configuration settings.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
events: EventListenerEvents,
|
|
35
|
+
event_listener_config: WebhooksOnlyEventListenerSettings,
|
|
36
|
+
):
|
|
37
|
+
super().__init__(events)
|
|
38
|
+
self.event_listener_config = event_listener_config
|
|
39
|
+
|
|
40
|
+
async def _start(self) -> None:
|
|
41
|
+
logger.info("Starting Webhooks-only event listener")
|
|
@@ -4,7 +4,7 @@ from .entities_state_applier.base import (
|
|
|
4
4
|
from .entities_state_applier.port.applier import (
|
|
5
5
|
HttpEntitiesStateApplier,
|
|
6
6
|
)
|
|
7
|
-
from .entity_processor.base import BaseEntityProcessor
|
|
7
|
+
from .entity_processor.base import BaseEntityProcessor
|
|
8
8
|
from .entity_processor.jq_entity_processor import (
|
|
9
9
|
JQEntityProcessor,
|
|
10
10
|
)
|
|
@@ -12,7 +12,6 @@ from .port_app_config.api import APIPortAppConfig
|
|
|
12
12
|
from .port_app_config.base import BasePortAppConfig
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
"EntityPortDiff",
|
|
16
15
|
"BaseEntityProcessor",
|
|
17
16
|
"JQEntityProcessor",
|
|
18
17
|
"BasePortAppConfig",
|
|
@@ -47,12 +47,15 @@ class BaseEntitiesStateApplier(BaseHandler):
|
|
|
47
47
|
@abstractmethod
|
|
48
48
|
async def upsert(
|
|
49
49
|
self, entities: list[Entity], user_agent_type: UserAgentType
|
|
50
|
-
) ->
|
|
50
|
+
) -> list[Entity]:
|
|
51
51
|
"""Upsert (insert or update) the given entities into the state.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
entities (list[Entity]): The entities to be upserted.
|
|
55
55
|
user_agent_type (UserAgentType): The user agent responsible for the upsert.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
list[Entity]: The upserted entities.
|
|
56
59
|
"""
|
|
57
60
|
pass
|
|
58
61
|
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from itertools import chain
|
|
3
|
-
|
|
4
1
|
from loguru import logger
|
|
5
2
|
|
|
6
3
|
from port_ocean.clients.port.types import UserAgentType
|
|
@@ -11,17 +8,11 @@ from port_ocean.core.handlers.entities_state_applier.base import (
|
|
|
11
8
|
from port_ocean.core.handlers.entities_state_applier.port.get_related_entities import (
|
|
12
9
|
get_related_entities,
|
|
13
10
|
)
|
|
14
|
-
|
|
15
|
-
order_by_entities_dependencies,
|
|
16
|
-
)
|
|
17
|
-
from port_ocean.core.handlers.entities_state_applier.port.validate_entity_relations import (
|
|
18
|
-
validate_entity_relations,
|
|
19
|
-
)
|
|
20
|
-
from port_ocean.core.handlers.entity_processor.base import EntityPortDiff
|
|
11
|
+
|
|
21
12
|
from port_ocean.core.models import Entity
|
|
22
13
|
from port_ocean.core.ocean_types import EntityDiff
|
|
23
|
-
from port_ocean.core.utils import
|
|
24
|
-
from port_ocean.
|
|
14
|
+
from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
|
|
15
|
+
from port_ocean.core.utils.utils import is_same_entity, get_port_diff
|
|
25
16
|
|
|
26
17
|
|
|
27
18
|
class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
@@ -32,63 +23,17 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
32
23
|
through HTTP requests.
|
|
33
24
|
"""
|
|
34
25
|
|
|
35
|
-
async def
|
|
36
|
-
logger.info("Validated deleted entities")
|
|
37
|
-
if not event.port_app_config.delete_dependent_entities:
|
|
38
|
-
dependent_entities = await asyncio.gather(
|
|
39
|
-
*(
|
|
40
|
-
self.context.port_client.search_dependent_entities(entity)
|
|
41
|
-
for entity in entities
|
|
42
|
-
)
|
|
43
|
-
)
|
|
44
|
-
new_dependent_entities = get_unique(
|
|
45
|
-
[
|
|
46
|
-
entity
|
|
47
|
-
for entity in chain.from_iterable(dependent_entities)
|
|
48
|
-
if not any(is_same_entity(item, entity) for item in entities)
|
|
49
|
-
]
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
if new_dependent_entities:
|
|
53
|
-
raise RelationValidationException(
|
|
54
|
-
f"Must enable delete_dependent_entities flag or delete all dependent entities: "
|
|
55
|
-
f" {[(dep.blueprint, dep.identifier) for dep in new_dependent_entities]}"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
async def _validate_entity_diff(self, diff: EntityPortDiff) -> None:
|
|
59
|
-
config = event.port_app_config
|
|
60
|
-
await self._validate_delete_dependent_entities(diff.deleted)
|
|
61
|
-
modified_or_created_entities = diff.modified + diff.created
|
|
62
|
-
|
|
63
|
-
if modified_or_created_entities and not config.create_missing_related_entities:
|
|
64
|
-
logger.info("Validating modified or created entities")
|
|
65
|
-
|
|
66
|
-
await asyncio.gather(
|
|
67
|
-
*(
|
|
68
|
-
self.context.port_client.validate_entity_payload(
|
|
69
|
-
entity,
|
|
70
|
-
config.enable_merge_entity,
|
|
71
|
-
create_missing_related_entities=config.create_missing_related_entities,
|
|
72
|
-
)
|
|
73
|
-
for entity in modified_or_created_entities
|
|
74
|
-
)
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
if not event.port_app_config.delete_dependent_entities:
|
|
78
|
-
logger.info("Validating no relation blocks the operation")
|
|
79
|
-
await validate_entity_relations(diff, self.context.port_client)
|
|
80
|
-
|
|
81
|
-
async def _delete_diff(
|
|
26
|
+
async def _safe_delete(
|
|
82
27
|
self,
|
|
83
28
|
entities_to_delete: list[Entity],
|
|
84
|
-
|
|
29
|
+
entities_to_protect: list[Entity],
|
|
85
30
|
user_agent_type: UserAgentType,
|
|
86
31
|
) -> None:
|
|
87
32
|
if not entities_to_delete:
|
|
88
33
|
return
|
|
89
34
|
|
|
90
35
|
related_entities = await get_related_entities(
|
|
91
|
-
|
|
36
|
+
entities_to_protect, self.context.port_client
|
|
92
37
|
)
|
|
93
38
|
|
|
94
39
|
allowed_entities_to_delete = []
|
|
@@ -98,7 +43,8 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
98
43
|
is_same_entity(entity, entity_to_delete) for entity in related_entities
|
|
99
44
|
)
|
|
100
45
|
is_part_of_created = any(
|
|
101
|
-
is_same_entity(entity, entity_to_delete)
|
|
46
|
+
is_same_entity(entity, entity_to_delete)
|
|
47
|
+
for entity in entities_to_protect
|
|
102
48
|
)
|
|
103
49
|
if is_part_of_related:
|
|
104
50
|
if event.port_app_config.create_missing_related_entities:
|
|
@@ -119,21 +65,14 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
119
65
|
user_agent_type: UserAgentType,
|
|
120
66
|
) -> None:
|
|
121
67
|
diff = get_port_diff(entities["before"], entities["after"])
|
|
68
|
+
kept_entities = diff.created + diff.modified
|
|
122
69
|
|
|
123
70
|
logger.info(
|
|
124
71
|
f"Updating entity diff (created: {len(diff.created)}, deleted: {len(diff.deleted)}, modified: {len(diff.modified)})"
|
|
125
72
|
)
|
|
126
|
-
await self.
|
|
73
|
+
modified_entities = await self.upsert(kept_entities, user_agent_type)
|
|
127
74
|
|
|
128
|
-
|
|
129
|
-
await self.upsert(diff.created, user_agent_type)
|
|
130
|
-
logger.info("Upserting modified entities")
|
|
131
|
-
await self.upsert(diff.modified, user_agent_type)
|
|
132
|
-
|
|
133
|
-
logger.info("Deleting diff entities")
|
|
134
|
-
await self._delete_diff(
|
|
135
|
-
diff.deleted, diff.created + diff.modified, user_agent_type
|
|
136
|
-
)
|
|
75
|
+
await self._safe_delete(diff.deleted, modified_entities, user_agent_type)
|
|
137
76
|
|
|
138
77
|
async def delete_diff(
|
|
139
78
|
self,
|
|
@@ -145,39 +84,40 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
145
84
|
if not diff.deleted:
|
|
146
85
|
return
|
|
147
86
|
|
|
87
|
+
kept_entities = diff.created + diff.modified
|
|
88
|
+
|
|
148
89
|
logger.info(
|
|
149
|
-
f"
|
|
90
|
+
f"Determining entities to delete ({len(diff.deleted)}/{len(kept_entities)})"
|
|
150
91
|
)
|
|
151
|
-
await self._validate_entity_diff(diff)
|
|
152
92
|
|
|
153
|
-
|
|
154
|
-
await self._delete_diff(
|
|
155
|
-
diff.deleted, diff.created + diff.modified, user_agent_type
|
|
156
|
-
)
|
|
93
|
+
await self._safe_delete(diff.deleted, kept_entities, user_agent_type)
|
|
157
94
|
|
|
158
95
|
async def upsert(
|
|
159
96
|
self, entities: list[Entity], user_agent_type: UserAgentType
|
|
160
|
-
) ->
|
|
97
|
+
) -> list[Entity]:
|
|
161
98
|
logger.info(f"Upserting {len(entities)} entities")
|
|
99
|
+
modified_entities: list[Entity] = []
|
|
162
100
|
if event.port_app_config.create_missing_related_entities:
|
|
163
|
-
await self.context.port_client.batch_upsert_entities(
|
|
101
|
+
modified_entities = await self.context.port_client.batch_upsert_entities(
|
|
164
102
|
entities,
|
|
165
103
|
event.port_app_config.get_port_request_options(),
|
|
166
104
|
user_agent_type,
|
|
167
105
|
should_raise=False,
|
|
168
106
|
)
|
|
169
107
|
else:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
for entity in ordered_created_entities:
|
|
175
|
-
await self.context.port_client.upsert_entity(
|
|
108
|
+
for entity in entities:
|
|
109
|
+
upsertedEntity = await self.context.port_client.upsert_entity(
|
|
176
110
|
entity,
|
|
177
111
|
event.port_app_config.get_port_request_options(),
|
|
178
112
|
user_agent_type,
|
|
179
113
|
should_raise=False,
|
|
180
114
|
)
|
|
115
|
+
if upsertedEntity:
|
|
116
|
+
modified_entities.append(upsertedEntity)
|
|
117
|
+
# condition to false to differentiate from `result_entity.is_using_search_identifier`
|
|
118
|
+
if upsertedEntity is False:
|
|
119
|
+
event.entity_topological_sorter.register_entity(entity)
|
|
120
|
+
return modified_entities
|
|
181
121
|
|
|
182
122
|
async def delete(
|
|
183
123
|
self, entities: list[Entity], user_agent_type: UserAgentType
|
|
@@ -191,7 +131,9 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
191
131
|
should_raise=False,
|
|
192
132
|
)
|
|
193
133
|
else:
|
|
194
|
-
ordered_deleted_entities =
|
|
134
|
+
ordered_deleted_entities = (
|
|
135
|
+
EntityTopologicalSorter.order_by_entities_dependencies(entities)
|
|
136
|
+
)
|
|
195
137
|
|
|
196
138
|
for entity in ordered_deleted_entities:
|
|
197
139
|
await self.context.port_client.delete_entity(
|
|
@@ -14,7 +14,6 @@ def node(entity: Entity) -> Node:
|
|
|
14
14
|
def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
|
|
15
15
|
nodes: dict[Node, Set[Node]] = {}
|
|
16
16
|
entities_map = {}
|
|
17
|
-
|
|
18
17
|
for entity in entities:
|
|
19
18
|
nodes[node(entity)] = set()
|
|
20
19
|
entities_map[node(entity)] = entity
|
|
@@ -33,7 +32,11 @@ def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
|
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
for related_entity in related_entities:
|
|
36
|
-
|
|
35
|
+
if (
|
|
36
|
+
entity.blueprint is not related_entity.blueprint
|
|
37
|
+
or entity.identifier is not related_entity.identifier
|
|
38
|
+
):
|
|
39
|
+
nodes[node(entity)].add(node(related_entity))
|
|
37
40
|
|
|
38
41
|
sort_op = TopologicalSorter(nodes)
|
|
39
42
|
try:
|
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
2
|
|
|
4
3
|
from loguru import logger
|
|
5
4
|
|
|
6
5
|
from port_ocean.core.handlers.base import BaseHandler
|
|
7
6
|
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
|
|
8
|
-
from port_ocean.core.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class EntityPortDiff:
|
|
14
|
-
"""Represents the differences between entities for porting.
|
|
15
|
-
|
|
16
|
-
This class holds the lists of deleted, modified, and created entities as part
|
|
17
|
-
of the porting process.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
deleted: list[Entity] = field(default_factory=list)
|
|
21
|
-
modified: list[Entity] = field(default_factory=list)
|
|
22
|
-
created: list[Entity] = field(default_factory=list)
|
|
7
|
+
from port_ocean.core.ocean_types import (
|
|
8
|
+
RAW_ITEM,
|
|
9
|
+
CalculationResult,
|
|
10
|
+
EntitySelectorDiff,
|
|
11
|
+
)
|
|
23
12
|
|
|
24
13
|
|
|
25
14
|
class BaseEntityProcessor(BaseHandler):
|
|
@@ -33,21 +22,36 @@ class BaseEntityProcessor(BaseHandler):
|
|
|
33
22
|
|
|
34
23
|
@abstractmethod
|
|
35
24
|
async def _parse_items(
|
|
36
|
-
self,
|
|
37
|
-
|
|
25
|
+
self,
|
|
26
|
+
mapping: ResourceConfig,
|
|
27
|
+
raw_data: list[RAW_ITEM],
|
|
28
|
+
parse_all: bool = False,
|
|
29
|
+
send_raw_data_examples_amount: int = 0,
|
|
30
|
+
) -> CalculationResult:
|
|
38
31
|
pass
|
|
39
32
|
|
|
40
33
|
async def parse_items(
|
|
41
|
-
self,
|
|
42
|
-
|
|
34
|
+
self,
|
|
35
|
+
mapping: ResourceConfig,
|
|
36
|
+
raw_data: list[RAW_ITEM],
|
|
37
|
+
parse_all: bool = False,
|
|
38
|
+
send_raw_data_examples_amount: int = 0,
|
|
39
|
+
) -> CalculationResult:
|
|
43
40
|
"""Public method to parse raw entity data and map it to an EntityDiff.
|
|
44
41
|
|
|
45
42
|
Args:
|
|
46
43
|
mapping (ResourceConfig): The configuration for entity mapping.
|
|
47
|
-
raw_data (
|
|
44
|
+
raw_data (list[RawEntity]): The raw data to be parsed.
|
|
45
|
+
parse_all (bool): Whether to parse all data or just data that passed the selector.
|
|
46
|
+
send_raw_data_examples_amount (bool): Whether to send example data to the integration service.
|
|
48
47
|
|
|
49
48
|
Returns:
|
|
50
49
|
EntityDiff: The parsed entity differences.
|
|
51
50
|
"""
|
|
52
51
|
with logger.contextualize(kind=mapping.kind):
|
|
53
|
-
|
|
52
|
+
if not raw_data:
|
|
53
|
+
return CalculationResult(EntitySelectorDiff([], []), [])
|
|
54
|
+
|
|
55
|
+
return await self._parse_items(
|
|
56
|
+
mapping, raw_data, parse_all, send_raw_data_examples_amount
|
|
57
|
+
)
|