port-ocean 0.17.8__tar.gz → 0.18.1__tar.gz
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.
- {port_ocean-0.17.8 → port_ocean-0.18.1}/PKG-INFO +1 -1
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/mixins/entities.py +21 -6
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/sync_raw.py +136 -24
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/models.py +4 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/utils/utils.py +80 -4
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +309 -2
- port_ocean-0.18.1/port_ocean/tests/core/utils/test_resolve_entities_diff.py +559 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/pyproject.toml +1 -1
- {port_ocean-0.17.8 → port_ocean-0.18.1}/LICENSE.md +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/README.md +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Dockerfile.Deb +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/authentication.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/mixins/integrations.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/config/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/config/settings.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/context/event.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/event_listener/webhooks_only.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/integrations/mixins/utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/debug_cli.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/helpers/retry.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/ocean.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/py.typed +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/run.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/core/test_utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/port_client.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.17.8 → port_ocean-0.18.1}/port_ocean/version.py +0 -0
|
@@ -98,10 +98,22 @@ class EntityClientMixin:
|
|
|
98
98
|
if result_entity.is_using_search_identifier:
|
|
99
99
|
return None
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
return self._reduce_entity(result_entity)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def _reduce_entity(entity: Entity) -> Entity:
|
|
105
|
+
"""
|
|
106
|
+
Reduces an entity to only keep identifier, blueprint and processed relations.
|
|
107
|
+
This helps save memory by removing unnecessary data.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
entity: The entity to reduce
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Entity: A new entity with only the essential data
|
|
114
|
+
"""
|
|
103
115
|
reduced_entity = Entity(
|
|
104
|
-
identifier=
|
|
116
|
+
identifier=entity.identifier, blueprint=entity.blueprint
|
|
105
117
|
)
|
|
106
118
|
|
|
107
119
|
# Turning dict typed relations (raw search relations) is required
|
|
@@ -109,7 +121,7 @@ class EntityClientMixin:
|
|
|
109
121
|
# and ignore the ones that don't as they weren't upserted
|
|
110
122
|
reduced_entity.relations = {
|
|
111
123
|
key: None if isinstance(relation, dict) else relation
|
|
112
|
-
for key, relation in
|
|
124
|
+
for key, relation in entity.relations.items()
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
return reduced_entity
|
|
@@ -202,7 +214,10 @@ class EntityClientMixin:
|
|
|
202
214
|
)
|
|
203
215
|
|
|
204
216
|
async def search_entities(
|
|
205
|
-
self,
|
|
217
|
+
self,
|
|
218
|
+
user_agent_type: UserAgentType,
|
|
219
|
+
query: dict[Any, Any] | None = None,
|
|
220
|
+
parameters_to_include: list[str] | None = None,
|
|
206
221
|
) -> list[Entity]:
|
|
207
222
|
default_query = {
|
|
208
223
|
"combinator": "and",
|
|
@@ -232,7 +247,7 @@ class EntityClientMixin:
|
|
|
232
247
|
headers=await self.auth.headers(user_agent_type),
|
|
233
248
|
params={
|
|
234
249
|
"exclude_calculated_properties": "true",
|
|
235
|
-
"include": ["blueprint", "identifier"],
|
|
250
|
+
"include": parameters_to_include or ["blueprint", "identifier"],
|
|
236
251
|
},
|
|
237
252
|
extensions={"retryable": True},
|
|
238
253
|
)
|
|
@@ -28,7 +28,7 @@ from port_ocean.core.ocean_types import (
|
|
|
28
28
|
RAW_ITEM,
|
|
29
29
|
CalculationResult,
|
|
30
30
|
)
|
|
31
|
-
from port_ocean.core.utils.utils import zip_and_sum, gather_and_split_errors_from_results
|
|
31
|
+
from port_ocean.core.utils.utils import resolve_entities_diff, zip_and_sum, gather_and_split_errors_from_results
|
|
32
32
|
from port_ocean.exceptions.core import OceanAbortException
|
|
33
33
|
|
|
34
34
|
SEND_RAW_DATA_EXAMPLES_AMOUNT = 5
|
|
@@ -130,24 +130,132 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
130
130
|
)
|
|
131
131
|
)
|
|
132
132
|
|
|
133
|
+
def _construct_search_query_for_entities(self, entities: list[Entity]) -> dict:
|
|
134
|
+
"""Create a query to search for entities by their identifiers.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
entities (list[Entity]): List of entities to search for.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict: Query structure for searching entities by identifier and blueprint.
|
|
141
|
+
"""
|
|
142
|
+
return {
|
|
143
|
+
"combinator": "and",
|
|
144
|
+
"rules": [
|
|
145
|
+
{
|
|
146
|
+
"combinator": "or",
|
|
147
|
+
"rules": [
|
|
148
|
+
{
|
|
149
|
+
"property": "$identifier",
|
|
150
|
+
"operator": "in",
|
|
151
|
+
"value": [entity.identifier for entity in entities],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"property": "$blueprint",
|
|
155
|
+
"operator": "=",
|
|
156
|
+
"value": entities[0].blueprint,
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async def _map_entities_compared_with_port(
|
|
164
|
+
self,
|
|
165
|
+
entities: list[Entity],
|
|
166
|
+
resource: ResourceConfig,
|
|
167
|
+
user_agent_type: UserAgentType,
|
|
168
|
+
) -> list[Entity]:
|
|
169
|
+
if not entities:
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
if entities[0].is_using_search_identifier or entities[0].is_using_search_relation:
|
|
173
|
+
return entities
|
|
174
|
+
|
|
175
|
+
BATCH_SIZE = 50
|
|
176
|
+
entities_at_port_with_properties = []
|
|
177
|
+
|
|
178
|
+
# Process entities in batches
|
|
179
|
+
for start_index in range(0, len(entities), BATCH_SIZE):
|
|
180
|
+
entities_batch = entities[start_index:start_index + BATCH_SIZE]
|
|
181
|
+
batch_results = await self._fetch_entities_batch_from_port(
|
|
182
|
+
entities_batch,
|
|
183
|
+
resource,
|
|
184
|
+
user_agent_type
|
|
185
|
+
)
|
|
186
|
+
entities_at_port_with_properties.extend(batch_results)
|
|
187
|
+
|
|
188
|
+
logger.info("Got entities from port with properties and relations", port_entities=len(entities_at_port_with_properties))
|
|
189
|
+
|
|
190
|
+
if len(entities_at_port_with_properties) > 0:
|
|
191
|
+
return resolve_entities_diff(entities, entities_at_port_with_properties)
|
|
192
|
+
return entities
|
|
193
|
+
|
|
194
|
+
async def _fetch_entities_batch_from_port(
|
|
195
|
+
self,
|
|
196
|
+
entities_batch: list[Entity],
|
|
197
|
+
resource: ResourceConfig,
|
|
198
|
+
user_agent_type: UserAgentType,
|
|
199
|
+
) -> list[Entity]:
|
|
200
|
+
query = self._construct_search_query_for_entities(entities_batch)
|
|
201
|
+
return await ocean.port_client.search_entities(
|
|
202
|
+
user_agent_type,
|
|
203
|
+
parameters_to_include=["blueprint", "identifier"] + (
|
|
204
|
+
["title"] if resource.port.entity.mappings.title != None else []
|
|
205
|
+
) + (
|
|
206
|
+
["team"] if resource.port.entity.mappings.team != None else []
|
|
207
|
+
) + [
|
|
208
|
+
f"properties.{prop}" for prop in resource.port.entity.mappings.properties
|
|
209
|
+
] + [
|
|
210
|
+
f"relations.{relation}" for relation in resource.port.entity.mappings.relations
|
|
211
|
+
],
|
|
212
|
+
query=query
|
|
213
|
+
)
|
|
214
|
+
|
|
133
215
|
async def _register_resource_raw(
|
|
134
216
|
self,
|
|
135
217
|
resource: ResourceConfig,
|
|
136
218
|
results: list[dict[Any, Any]],
|
|
137
219
|
user_agent_type: UserAgentType,
|
|
138
220
|
parse_all: bool = False,
|
|
139
|
-
send_raw_data_examples_amount: int = 0
|
|
221
|
+
send_raw_data_examples_amount: int = 0
|
|
140
222
|
) -> CalculationResult:
|
|
141
223
|
objects_diff = await self._calculate_raw(
|
|
142
224
|
[(resource, results)], parse_all, send_raw_data_examples_amount
|
|
143
225
|
)
|
|
144
|
-
modified_objects =
|
|
145
|
-
|
|
226
|
+
modified_objects = []
|
|
227
|
+
|
|
228
|
+
if ocean.app.is_saas():
|
|
229
|
+
try:
|
|
230
|
+
changed_entities = await self._map_entities_compared_with_port(
|
|
231
|
+
objects_diff[0].entity_selector_diff.passed,
|
|
232
|
+
resource,
|
|
233
|
+
user_agent_type
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if changed_entities:
|
|
237
|
+
logger.info("Upserting changed entities", changed_entities=len(changed_entities),
|
|
238
|
+
total_entities=len(objects_diff[0].entity_selector_diff.passed))
|
|
239
|
+
await self.entities_state_applier.upsert(
|
|
240
|
+
changed_entities, user_agent_type
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
logger.info("Entities in batch didn't changed since last sync, skipping", total_entities=len(objects_diff[0].entity_selector_diff.passed))
|
|
244
|
+
|
|
245
|
+
modified_objects = [ocean.port_client._reduce_entity(entity) for entity in objects_diff[0].entity_selector_diff.passed]
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.warning(f"Failed to resolve batch entities with Port, falling back to upserting all entities: {str(e)}")
|
|
248
|
+
modified_objects = await self.entities_state_applier.upsert(
|
|
249
|
+
objects_diff[0].entity_selector_diff.passed, user_agent_type
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
modified_objects = await self.entities_state_applier.upsert(
|
|
253
|
+
objects_diff[0].entity_selector_diff.passed, user_agent_type
|
|
146
254
|
)
|
|
147
255
|
return CalculationResult(
|
|
148
256
|
objects_diff[0].entity_selector_diff._replace(passed=modified_objects),
|
|
149
257
|
errors=objects_diff[0].errors,
|
|
150
|
-
misonfigured_entity_keys=objects_diff[0].misonfigured_entity_keys
|
|
258
|
+
misonfigured_entity_keys=objects_diff[0].misonfigured_entity_keys
|
|
151
259
|
)
|
|
152
260
|
|
|
153
261
|
async def _unregister_resource_raw(
|
|
@@ -186,14 +294,17 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
186
294
|
send_raw_data_examples_amount = (
|
|
187
295
|
SEND_RAW_DATA_EXAMPLES_AMOUNT if ocean.config.send_raw_data_examples else 0
|
|
188
296
|
)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
297
|
+
|
|
298
|
+
passed_entities = []
|
|
299
|
+
if raw_results:
|
|
300
|
+
calculation_result = await self._register_resource_raw(
|
|
301
|
+
resource_config,
|
|
302
|
+
raw_results,
|
|
303
|
+
user_agent_type,
|
|
304
|
+
send_raw_data_examples_amount=send_raw_data_examples_amount
|
|
305
|
+
)
|
|
306
|
+
errors.extend(calculation_result.errors)
|
|
307
|
+
passed_entities = list(calculation_result.entity_selector_diff.passed)
|
|
197
308
|
|
|
198
309
|
for generator in async_generators:
|
|
199
310
|
try:
|
|
@@ -203,14 +314,14 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
203
314
|
0, send_raw_data_examples_amount - len(passed_entities)
|
|
204
315
|
)
|
|
205
316
|
|
|
206
|
-
|
|
317
|
+
calculation_result = await self._register_resource_raw(
|
|
207
318
|
resource_config,
|
|
208
319
|
items,
|
|
209
320
|
user_agent_type,
|
|
210
|
-
send_raw_data_examples_amount=send_raw_data_examples_amount
|
|
321
|
+
send_raw_data_examples_amount=send_raw_data_examples_amount
|
|
211
322
|
)
|
|
212
|
-
errors.extend(
|
|
213
|
-
passed_entities.extend(
|
|
323
|
+
errors.extend(calculation_result.errors)
|
|
324
|
+
passed_entities.extend(calculation_result.entity_selector_diff.passed)
|
|
214
325
|
except* OceanAbortException as error:
|
|
215
326
|
errors.append(error)
|
|
216
327
|
|
|
@@ -446,9 +557,6 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
446
557
|
|
|
447
558
|
try:
|
|
448
559
|
did_fetched_current_state = True
|
|
449
|
-
entities_at_port = await ocean.port_client.search_entities(
|
|
450
|
-
user_agent_type
|
|
451
|
-
)
|
|
452
560
|
except httpx.HTTPError as e:
|
|
453
561
|
logger.warning(
|
|
454
562
|
"Failed to fetch the current state of entities at Port. "
|
|
@@ -461,6 +569,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
461
569
|
|
|
462
570
|
creation_results: list[tuple[list[Entity], list[Exception]]] = []
|
|
463
571
|
|
|
572
|
+
|
|
464
573
|
try:
|
|
465
574
|
for resource in app_config.resources:
|
|
466
575
|
# create resource context per resource kind, so resync method could have access to the resource
|
|
@@ -471,7 +580,6 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
471
580
|
)
|
|
472
581
|
|
|
473
582
|
event.on_abort(lambda: task.cancel())
|
|
474
|
-
|
|
475
583
|
creation_results.append(await task)
|
|
476
584
|
|
|
477
585
|
await self.sort_and_upsert_failed_entities(user_agent_type)
|
|
@@ -487,7 +595,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
487
595
|
return
|
|
488
596
|
|
|
489
597
|
logger.info("Starting resync diff calculation")
|
|
490
|
-
|
|
598
|
+
generated_entities, errors = zip_and_sum(creation_results) or [
|
|
491
599
|
[],
|
|
492
600
|
[],
|
|
493
601
|
]
|
|
@@ -504,10 +612,14 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
504
612
|
logger.error(message, exc_info=error_group)
|
|
505
613
|
else:
|
|
506
614
|
logger.info(
|
|
507
|
-
f"Running resync diff calculation, number of entities
|
|
615
|
+
f"Running resync diff calculation, number of entities created during sync: {len(generated_entities)}"
|
|
616
|
+
)
|
|
617
|
+
entities_at_port = await ocean.port_client.search_entities(
|
|
618
|
+
user_agent_type
|
|
508
619
|
)
|
|
509
620
|
await self.entities_state_applier.delete_diff(
|
|
510
|
-
{"before": entities_at_port, "after":
|
|
621
|
+
{"before": entities_at_port, "after": generated_entities},
|
|
511
622
|
user_agent_type,
|
|
512
623
|
)
|
|
624
|
+
|
|
513
625
|
logger.info("Resync finished successfully")
|
|
@@ -43,6 +43,10 @@ class Entity(BaseModel):
|
|
|
43
43
|
def is_using_search_identifier(self) -> bool:
|
|
44
44
|
return isinstance(self.identifier, dict)
|
|
45
45
|
|
|
46
|
+
@property
|
|
47
|
+
def is_using_search_relation(self) -> bool:
|
|
48
|
+
return any(isinstance(relation, dict) for relation in self.relations.values())
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
class BlueprintRelation(BaseModel):
|
|
48
52
|
many: bool
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
2
4
|
from typing import Iterable, Any, TypeVar, Callable, Awaitable
|
|
3
5
|
|
|
4
6
|
from loguru import logger
|
|
5
7
|
from pydantic import parse_obj_as, ValidationError
|
|
6
8
|
|
|
9
|
+
|
|
7
10
|
from port_ocean.clients.port.client import PortClient
|
|
8
11
|
from port_ocean.core.models import Entity, Runtime
|
|
9
12
|
from port_ocean.core.models import EntityPortDiff
|
|
@@ -76,10 +79,7 @@ async def gather_and_split_errors_from_results(
|
|
|
76
79
|
return valid_items, errors
|
|
77
80
|
|
|
78
81
|
|
|
79
|
-
def get_port_diff(
|
|
80
|
-
before: Iterable[Entity],
|
|
81
|
-
after: Iterable[Entity],
|
|
82
|
-
) -> EntityPortDiff:
|
|
82
|
+
def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPortDiff:
|
|
83
83
|
before_dict = {}
|
|
84
84
|
after_dict = {}
|
|
85
85
|
created = []
|
|
@@ -107,3 +107,79 @@ def get_port_diff(
|
|
|
107
107
|
deleted.append(obj)
|
|
108
108
|
|
|
109
109
|
return EntityPortDiff(created=created, modified=modified, deleted=deleted)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def are_teams_different(
|
|
113
|
+
first_team: str | None | list[Any], second_team: str | None | list[Any]
|
|
114
|
+
) -> bool:
|
|
115
|
+
if isinstance(first_team, list) and isinstance(second_team, list):
|
|
116
|
+
return sorted(first_team) != sorted(second_team)
|
|
117
|
+
return first_team != second_team
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def are_entities_fields_equal(
|
|
121
|
+
first_entity_field: dict[str, Any], second_entity_field: dict[str, Any]
|
|
122
|
+
) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Compare two entity fields by serializing them to JSON and comparing their SHA-256 hashes.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
first_entity_field: First entity field dictionary to compare
|
|
128
|
+
second_entity_field: Second entity field dictionary to compare
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
bool: True if the entity fields have identical content
|
|
132
|
+
"""
|
|
133
|
+
first_props = json.dumps(first_entity_field, sort_keys=True)
|
|
134
|
+
second_props = json.dumps(second_entity_field, sort_keys=True)
|
|
135
|
+
first_hash = hashlib.sha256(first_props.encode()).hexdigest()
|
|
136
|
+
second_hash = hashlib.sha256(second_props.encode()).hexdigest()
|
|
137
|
+
return first_hash == second_hash
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def are_entities_different(first_entity: Entity, second_entity: Entity) -> bool:
|
|
141
|
+
if first_entity.title != second_entity.title:
|
|
142
|
+
return True
|
|
143
|
+
if are_teams_different(first_entity.team, second_entity.team):
|
|
144
|
+
return True
|
|
145
|
+
if not are_entities_fields_equal(first_entity.properties, second_entity.properties):
|
|
146
|
+
return True
|
|
147
|
+
if not are_entities_fields_equal(first_entity.relations, second_entity.relations):
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def resolve_entities_diff(
|
|
154
|
+
source_entities: list[Entity], target_entities: list[Entity]
|
|
155
|
+
) -> list[Entity]:
|
|
156
|
+
"""
|
|
157
|
+
Maps the entities into filtered list of source entities, excluding matches found in target that needs to be upserted
|
|
158
|
+
Args:
|
|
159
|
+
source_entities: List of entities from third party source
|
|
160
|
+
target_entities: List of existing Port entities
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
list[Entity]: Filtered list of source entities, excluding matches found in target
|
|
164
|
+
"""
|
|
165
|
+
target_entities_dict = {}
|
|
166
|
+
source_entities_dict = {}
|
|
167
|
+
changed_entities = []
|
|
168
|
+
|
|
169
|
+
for entity in target_entities:
|
|
170
|
+
key = (entity.identifier, entity.blueprint)
|
|
171
|
+
target_entities_dict[key] = entity
|
|
172
|
+
|
|
173
|
+
for entity in source_entities:
|
|
174
|
+
if entity.is_using_search_identifier or entity.is_using_search_relation:
|
|
175
|
+
return source_entities
|
|
176
|
+
key = (entity.identifier, entity.blueprint)
|
|
177
|
+
source_entities_dict[key] = entity
|
|
178
|
+
|
|
179
|
+
entity_at_target = target_entities_dict.get(key, None)
|
|
180
|
+
if entity_at_target is None:
|
|
181
|
+
changed_entities.append(entity)
|
|
182
|
+
elif are_entities_different(entity, target_entities_dict[key]):
|
|
183
|
+
changed_entities.append(entity)
|
|
184
|
+
|
|
185
|
+
return changed_entities
|