port-ocean 0.15.2__tar.gz → 0.16.0__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.15.2 → port_ocean-0.16.0}/PKG-INFO +1 -1
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/new.py +4 -7
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/cookiecutter.json +3 -0
- port_ocean-0.16.0/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +6 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/entities.py +33 -4
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/event.py +11 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/initialize.py +1 -1
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +9 -17
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +5 -2
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +4 -1
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/sync_raw.py +19 -2
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/utils.py +1 -1
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/models.py +4 -0
- port_ocean-0.16.0/port_ocean/core/utils/entity_topological_sorter.py +90 -0
- port_ocean-0.16.0/port_ocean/debug_cli.py +5 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/run.py +1 -1
- port_ocean-0.16.0/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +400 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/test_utils.py +1 -1
- port_ocean-0.16.0/port_ocean/tests/core/utils/test_entity_topological_sorter.py +99 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/pyproject.toml +1 -1
- port_ocean-0.15.2/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -2
- {port_ocean-0.15.2 → port_ocean-0.16.0}/LICENSE.md +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/README.md +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.Deb +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/authentication.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/settings.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.15.2/port_ocean/core → port_ocean-0.16.0/port_ocean/core/utils}/utils.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/retry.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/ocean.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/py.typed +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/port_client.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/version.py +0 -0
|
@@ -77,18 +77,15 @@ def new(path: str, is_private_integration: bool) -> None:
|
|
|
77
77
|
)
|
|
78
78
|
console.print("Here are your next steps:\n", style="bold")
|
|
79
79
|
console.print(
|
|
80
|
-
"⚓️ Install necessary packages: Run [bold][blue]make install[/blue][/bold] to install all required packages for your project
|
|
81
|
-
f"▶️ [bold][blue]cd {path}/{name} && make install && . .venv/bin/activate[/blue][/bold]\n"
|
|
80
|
+
f"⚓️ Install necessary packages: Run [bold][blue]cd {path}/{name} && make install && . .venv/bin/activate[/blue][/bold] to install all required packages for your project."
|
|
82
81
|
)
|
|
83
82
|
console.print(
|
|
84
|
-
|
|
83
|
+
"⚓️ Copy example env file: Run [bold][blue]cp .env.example .env [/blue][/bold] and update your integration's configuration in the .env file."
|
|
85
84
|
)
|
|
86
85
|
console.print(
|
|
87
|
-
"⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue]
|
|
88
|
-
f"▶️ [bold][blue]ocean sail {path}/{name}[/blue][/bold] \n"
|
|
86
|
+
"⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue][/bold] to run the project using Ocean."
|
|
89
87
|
)
|
|
90
88
|
if not final_private_integration:
|
|
91
89
|
console.print(
|
|
92
|
-
"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run[/blue][/bold] to launch your project using Make.
|
|
93
|
-
f"▶️ [bold][blue]make run {path}/{name}[/blue][/bold]"
|
|
90
|
+
f"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run {path}/{name}[/blue][/bold] to launch your project using Make."
|
|
94
91
|
)
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
"email": "Your address email <you@example.com>",
|
|
7
7
|
"release_date": "{% now 'local' %}",
|
|
8
8
|
"is_private_integration": true,
|
|
9
|
+
"port_client_id": "you can find it using: https://docs.getport.io/build-your-software-catalog/custom-integration/api/#find-your-port-credentials",
|
|
10
|
+
"port_client_secret": "you can find it using: https://docs.getport.io/build-your-software-catalog/custom-integration/api/#find-your-port-credentials",
|
|
11
|
+
"is_us_region": false,
|
|
9
12
|
"_extensions": [
|
|
10
13
|
"jinja2_time.TimeExtension",
|
|
11
14
|
"extensions.VersionExtension"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
OCEAN__PORT__CLIENT_ID={{ cookiecutter.port_client_id }}
|
|
2
|
+
OCEAN__PORT__CLIENT_SECRET={{ cookiecutter.port_client_secret }}
|
|
3
|
+
OCEAN__INTEGRATION__IDENTIFIER={{ cookiecutter.integration_slug }}
|
|
4
|
+
OCEAN__PORT__BASE_URL={% if cookiecutter.is_us_region %}https://api.us.getport.io{% else %}https://api.getport.io{% endif %}
|
|
5
|
+
OCEAN__EVENT_LISTENER__TYPE=POLLING
|
|
6
|
+
OCEAN__INITIALIZE_PORT_RESOURCES=true
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any, Literal
|
|
3
3
|
from urllib.parse import quote_plus
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
@@ -11,7 +11,8 @@ from port_ocean.clients.port.utils import (
|
|
|
11
11
|
handle_status_code,
|
|
12
12
|
PORT_HTTP_MAX_CONNECTIONS_LIMIT,
|
|
13
13
|
)
|
|
14
|
-
from port_ocean.core.models import Entity
|
|
14
|
+
from port_ocean.core.models import Entity, PortAPIErrorMessage
|
|
15
|
+
from starlette import status
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class EntityClientMixin:
|
|
@@ -29,7 +30,27 @@ class EntityClientMixin:
|
|
|
29
30
|
request_options: RequestOptions,
|
|
30
31
|
user_agent_type: UserAgentType | None = None,
|
|
31
32
|
should_raise: bool = True,
|
|
32
|
-
) -> Entity | None:
|
|
33
|
+
) -> Entity | None | Literal[False]:
|
|
34
|
+
"""
|
|
35
|
+
This function upserts an entity into Port.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
```python
|
|
39
|
+
upsertedEntity = await self.context.port_client.upsert_entity(
|
|
40
|
+
entity,
|
|
41
|
+
event.port_app_config.get_port_request_options(),
|
|
42
|
+
user_agent_type,
|
|
43
|
+
should_raise=False,
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
:param entity: An Entity to be upserted
|
|
47
|
+
:param request_options: A dictionary specifying how to upsert the entity
|
|
48
|
+
:param user_agent_type: a UserAgentType specifying who is preforming the action
|
|
49
|
+
:param should_raise: A boolean specifying whether the error should be raised or handled silently
|
|
50
|
+
:return: [Entity] if the upsert occured successfully
|
|
51
|
+
:return: [None] will be returned if entity is using search identifier
|
|
52
|
+
:return: [False] will be returned if upsert failed because of unmet dependency
|
|
53
|
+
"""
|
|
33
54
|
validation_only = request_options["validation_only"]
|
|
34
55
|
async with self.semaphore:
|
|
35
56
|
logger.debug(
|
|
@@ -50,13 +71,21 @@ class EntityClientMixin:
|
|
|
50
71
|
},
|
|
51
72
|
extensions={"retryable": True},
|
|
52
73
|
)
|
|
53
|
-
|
|
54
74
|
if response.is_error:
|
|
55
75
|
logger.error(
|
|
56
76
|
f"Error {'Validating' if validation_only else 'Upserting'} "
|
|
57
77
|
f"entity: {entity.identifier} of "
|
|
58
78
|
f"blueprint: {entity.blueprint}"
|
|
59
79
|
)
|
|
80
|
+
result = response.json()
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
response.status_code == status.HTTP_404_NOT_FOUND
|
|
84
|
+
and not result.get("ok")
|
|
85
|
+
and result.get("error") == PortAPIErrorMessage.NOT_FOUND.value
|
|
86
|
+
):
|
|
87
|
+
# Return false to differentiate from `result_entity.is_using_search_identifier`
|
|
88
|
+
return False
|
|
60
89
|
handle_status_code(response, should_raise)
|
|
61
90
|
result = response.json()
|
|
62
91
|
|
|
@@ -14,6 +14,7 @@ from typing import (
|
|
|
14
14
|
from uuid import uuid4
|
|
15
15
|
|
|
16
16
|
from loguru import logger
|
|
17
|
+
from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
|
|
17
18
|
from pydispatch import dispatcher # type: ignore
|
|
18
19
|
from werkzeug.local import LocalStack, LocalProxy
|
|
19
20
|
|
|
@@ -24,6 +25,7 @@ from port_ocean.exceptions.context import (
|
|
|
24
25
|
)
|
|
25
26
|
from port_ocean.utils.misc import get_time
|
|
26
27
|
|
|
28
|
+
|
|
27
29
|
if TYPE_CHECKING:
|
|
28
30
|
from port_ocean.core.handlers.port_app_config.models import (
|
|
29
31
|
ResourceConfig,
|
|
@@ -50,6 +52,9 @@ class EventContext:
|
|
|
50
52
|
_parent_event: Optional["EventContext"] = None
|
|
51
53
|
_event_id: str = field(default_factory=lambda: str(uuid4()))
|
|
52
54
|
_on_abort_callbacks: list[AbortCallbackFunction] = field(default_factory=list)
|
|
55
|
+
entity_topological_sorter: EntityTopologicalSorter = field(
|
|
56
|
+
default_factory=EntityTopologicalSorter
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
def on_abort(self, func: AbortCallbackFunction) -> None:
|
|
55
60
|
self._on_abort_callbacks.append(func)
|
|
@@ -129,6 +134,11 @@ async def event_context(
|
|
|
129
134
|
) -> AsyncIterator[EventContext]:
|
|
130
135
|
parent = parent_override or _event_context_stack.top
|
|
131
136
|
parent_attributes = parent.attributes if parent else {}
|
|
137
|
+
entity_topological_sorter = (
|
|
138
|
+
parent.entity_topological_sorter
|
|
139
|
+
if parent and parent.entity_topological_sorter
|
|
140
|
+
else EntityTopologicalSorter()
|
|
141
|
+
)
|
|
132
142
|
|
|
133
143
|
attributes = {**parent_attributes, **(attributes or {})}
|
|
134
144
|
new_event = EventContext(
|
|
@@ -138,6 +148,7 @@ async def event_context(
|
|
|
138
148
|
_parent_event=parent,
|
|
139
149
|
# inherit port app config from parent event, so it can be used in nested events
|
|
140
150
|
_port_app_config=parent.port_app_config if parent else None,
|
|
151
|
+
entity_topological_sorter=entity_topological_sorter,
|
|
141
152
|
)
|
|
142
153
|
_event_context_stack.push(new_event)
|
|
143
154
|
|
|
@@ -14,7 +14,7 @@ from port_ocean.core.defaults.common import (
|
|
|
14
14
|
)
|
|
15
15
|
from port_ocean.core.handlers.port_app_config.models import PortAppConfig
|
|
16
16
|
from port_ocean.core.models import Blueprint
|
|
17
|
-
from port_ocean.core.utils import gather_and_split_errors_from_results
|
|
17
|
+
from port_ocean.core.utils.utils import gather_and_split_errors_from_results
|
|
18
18
|
from port_ocean.exceptions.port_defaults import (
|
|
19
19
|
AbortDefaultCreationError,
|
|
20
20
|
)
|
|
@@ -8,12 +8,11 @@ from port_ocean.core.handlers.entities_state_applier.base import (
|
|
|
8
8
|
from port_ocean.core.handlers.entities_state_applier.port.get_related_entities import (
|
|
9
9
|
get_related_entities,
|
|
10
10
|
)
|
|
11
|
-
|
|
12
|
-
order_by_entities_dependencies,
|
|
13
|
-
)
|
|
11
|
+
|
|
14
12
|
from port_ocean.core.models import Entity
|
|
15
13
|
from port_ocean.core.ocean_types import EntityDiff
|
|
16
|
-
from port_ocean.core.utils import
|
|
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
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
@@ -106,19 +105,7 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
106
105
|
should_raise=False,
|
|
107
106
|
)
|
|
108
107
|
else:
|
|
109
|
-
entities_with_search_identifier: list[Entity] = []
|
|
110
|
-
entities_without_search_identifier: list[Entity] = []
|
|
111
108
|
for entity in entities:
|
|
112
|
-
if entity.is_using_search_identifier:
|
|
113
|
-
entities_with_search_identifier.append(entity)
|
|
114
|
-
else:
|
|
115
|
-
entities_without_search_identifier.append(entity)
|
|
116
|
-
|
|
117
|
-
ordered_created_entities = reversed(
|
|
118
|
-
entities_with_search_identifier
|
|
119
|
-
+ order_by_entities_dependencies(entities_without_search_identifier)
|
|
120
|
-
)
|
|
121
|
-
for entity in ordered_created_entities:
|
|
122
109
|
upsertedEntity = await self.context.port_client.upsert_entity(
|
|
123
110
|
entity,
|
|
124
111
|
event.port_app_config.get_port_request_options(),
|
|
@@ -127,6 +114,9 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
127
114
|
)
|
|
128
115
|
if upsertedEntity:
|
|
129
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)
|
|
130
120
|
return modified_entities
|
|
131
121
|
|
|
132
122
|
async def delete(
|
|
@@ -141,7 +131,9 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
|
|
|
141
131
|
should_raise=False,
|
|
142
132
|
)
|
|
143
133
|
else:
|
|
144
|
-
ordered_deleted_entities =
|
|
134
|
+
ordered_deleted_entities = (
|
|
135
|
+
EntityTopologicalSorter.order_by_entities_dependencies(entities)
|
|
136
|
+
)
|
|
145
137
|
|
|
146
138
|
for entity in ordered_deleted_entities:
|
|
147
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:
|
|
@@ -16,7 +16,10 @@ from port_ocean.core.ocean_types import (
|
|
|
16
16
|
EntitySelectorDiff,
|
|
17
17
|
CalculationResult,
|
|
18
18
|
)
|
|
19
|
-
from port_ocean.core.utils import
|
|
19
|
+
from port_ocean.core.utils.utils import (
|
|
20
|
+
gather_and_split_errors_from_results,
|
|
21
|
+
zip_and_sum,
|
|
22
|
+
)
|
|
20
23
|
from port_ocean.exceptions.core import EntityProcessorException
|
|
21
24
|
from port_ocean.utils.queue_utils import process_in_queue
|
|
22
25
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from graphlib import CycleError
|
|
2
3
|
import inspect
|
|
3
4
|
import typing
|
|
4
5
|
from typing import Callable, Awaitable, Any
|
|
@@ -27,7 +28,7 @@ from port_ocean.core.ocean_types import (
|
|
|
27
28
|
RAW_ITEM,
|
|
28
29
|
CalculationResult,
|
|
29
30
|
)
|
|
30
|
-
from port_ocean.core.utils import zip_and_sum, gather_and_split_errors_from_results
|
|
31
|
+
from port_ocean.core.utils.utils import zip_and_sum, gather_and_split_errors_from_results
|
|
31
32
|
from port_ocean.exceptions.core import OceanAbortException
|
|
32
33
|
|
|
33
34
|
SEND_RAW_DATA_EXAMPLES_AMOUNT = 5
|
|
@@ -396,7 +397,20 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
396
397
|
{"before": entities_before_flatten, "after": entities_after_flatten},
|
|
397
398
|
user_agent_type,
|
|
398
399
|
)
|
|
399
|
-
|
|
400
|
+
async def sort_and_upsert_failed_entities(self,user_agent_type: UserAgentType)->None:
|
|
401
|
+
try:
|
|
402
|
+
if not event.entity_topological_sorter.should_execute():
|
|
403
|
+
return None
|
|
404
|
+
logger.info(f"Executings topological sort of {event.entity_topological_sorter.get_entities_count()} entities failed to upsert.",failed_toupsert_entities_count=event.entity_topological_sorter.get_entities_count())
|
|
405
|
+
|
|
406
|
+
for entity in event.entity_topological_sorter.get_entities():
|
|
407
|
+
await self.entities_state_applier.context.port_client.upsert_entity(entity,event.port_app_config.get_port_request_options(),user_agent_type,should_raise=False)
|
|
408
|
+
|
|
409
|
+
except OceanAbortException as ocean_abort:
|
|
410
|
+
logger.info(f"Failed topological sort of failed to upsert entites - trying to upsert unordered {event.entity_topological_sorter.get_entities_count()} entities.",failed_topological_sort_entities_count=event.entity_topological_sorter.get_entities_count() )
|
|
411
|
+
if isinstance(ocean_abort.__cause__,CycleError):
|
|
412
|
+
for entity in event.entity_topological_sorter.get_entities(False):
|
|
413
|
+
await self.entities_state_applier.context.port_client.upsert_entity(entity,event.port_app_config.get_port_request_options(),user_agent_type,should_raise=False)
|
|
400
414
|
async def sync_raw_all(
|
|
401
415
|
self,
|
|
402
416
|
_: dict[Any, Any] | None = None,
|
|
@@ -426,6 +440,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
426
440
|
use_cache=False
|
|
427
441
|
)
|
|
428
442
|
logger.info(f"Resync will use the following mappings: {app_config.dict()}")
|
|
443
|
+
|
|
429
444
|
try:
|
|
430
445
|
did_fetched_current_state = True
|
|
431
446
|
entities_at_port = await ocean.port_client.search_entities(
|
|
@@ -455,6 +470,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
455
470
|
event.on_abort(lambda: task.cancel())
|
|
456
471
|
|
|
457
472
|
creation_results.append(await task)
|
|
473
|
+
|
|
474
|
+
await self.sort_and_upsert_failed_entities(user_agent_type)
|
|
458
475
|
except asyncio.CancelledError as e:
|
|
459
476
|
logger.warning("Resync aborted successfully, skipping delete phase. This leads to an incomplete state")
|
|
460
477
|
raise
|
|
@@ -9,7 +9,7 @@ from port_ocean.core.ocean_types import (
|
|
|
9
9
|
RESYNC_EVENT_LISTENER,
|
|
10
10
|
RESYNC_RESULT,
|
|
11
11
|
)
|
|
12
|
-
from port_ocean.core.utils import validate_result
|
|
12
|
+
from port_ocean.core.utils.utils import validate_result
|
|
13
13
|
from port_ocean.exceptions.core import (
|
|
14
14
|
RawObjectValidationException,
|
|
15
15
|
OceanAbortException,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Any, Generator
|
|
2
|
+
from port_ocean.context import event
|
|
3
|
+
from port_ocean.core.models import Entity
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from graphlib import TopologicalSorter, CycleError
|
|
8
|
+
from typing import Set
|
|
9
|
+
|
|
10
|
+
from port_ocean.exceptions.core import OceanAbortException
|
|
11
|
+
|
|
12
|
+
Node = tuple[str, str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EntityTopologicalSorter:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self.entities: list[Entity] = []
|
|
18
|
+
|
|
19
|
+
def register_entity(
|
|
20
|
+
self,
|
|
21
|
+
entity: Entity,
|
|
22
|
+
) -> None:
|
|
23
|
+
logger.debug(
|
|
24
|
+
f"Will retry upserting entity - {entity.identifier} at the end of resync"
|
|
25
|
+
)
|
|
26
|
+
self.entities.append(entity)
|
|
27
|
+
|
|
28
|
+
def should_execute(self) -> int:
|
|
29
|
+
return not event.event.port_app_config.create_missing_related_entities
|
|
30
|
+
|
|
31
|
+
def get_entities_count(self) -> int:
|
|
32
|
+
return len(self.entities)
|
|
33
|
+
|
|
34
|
+
def get_entities(self, sorted: bool = True) -> Generator[Entity, Any, None]:
|
|
35
|
+
if not sorted:
|
|
36
|
+
for entity in self.entities:
|
|
37
|
+
yield entity
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
sorted_and_mapped = EntityTopologicalSorter.order_by_entities_dependencies(
|
|
41
|
+
self.entities
|
|
42
|
+
)
|
|
43
|
+
for entity in sorted_and_mapped:
|
|
44
|
+
yield entity
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def node(entity: Entity) -> Node:
|
|
48
|
+
return entity.identifier, entity.blueprint
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
|
|
52
|
+
nodes: dict[Node, Set[Node]] = {}
|
|
53
|
+
entities_map = {}
|
|
54
|
+
for entity in entities:
|
|
55
|
+
nodes[EntityTopologicalSorter.node(entity)] = set()
|
|
56
|
+
entities_map[EntityTopologicalSorter.node(entity)] = entity
|
|
57
|
+
|
|
58
|
+
for entity in entities:
|
|
59
|
+
relation_target_ids: list[str] = sum(
|
|
60
|
+
[
|
|
61
|
+
identifiers if isinstance(identifiers, list) else [identifiers]
|
|
62
|
+
for identifiers in entity.relations.values()
|
|
63
|
+
if identifiers is not None
|
|
64
|
+
],
|
|
65
|
+
[],
|
|
66
|
+
)
|
|
67
|
+
related_entities = [
|
|
68
|
+
related
|
|
69
|
+
for related in entities
|
|
70
|
+
if related.identifier in relation_target_ids
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for related_entity in related_entities:
|
|
74
|
+
if (
|
|
75
|
+
entity.blueprint is not related_entity.blueprint
|
|
76
|
+
or entity.identifier is not related_entity.identifier
|
|
77
|
+
):
|
|
78
|
+
nodes[EntityTopologicalSorter.node(entity)].add(
|
|
79
|
+
EntityTopologicalSorter.node(related_entity)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
sort_op = TopologicalSorter(nodes)
|
|
83
|
+
try:
|
|
84
|
+
return [entities_map[item] for item in sort_op.static_order()]
|
|
85
|
+
except CycleError as ex:
|
|
86
|
+
raise OceanAbortException(
|
|
87
|
+
"Cannot order entities due to cyclic dependencies. \n"
|
|
88
|
+
"If you do want to have cyclic dependencies, please make sure to set the keys"
|
|
89
|
+
" 'createMissingRelatedEntities' and 'deleteDependentEntities' in the integration config in Port."
|
|
90
|
+
) from ex
|
|
@@ -9,7 +9,7 @@ from port_ocean.bootstrap import create_default_app
|
|
|
9
9
|
from port_ocean.config.dynamic import default_config_factory
|
|
10
10
|
from port_ocean.config.settings import ApplicationSettings, LogLevelType
|
|
11
11
|
from port_ocean.core.defaults.initialize import initialize_defaults
|
|
12
|
-
from port_ocean.core.utils import validate_integration_runtime
|
|
12
|
+
from port_ocean.core.utils.utils import validate_integration_runtime
|
|
13
13
|
from port_ocean.log.logger_setup import setup_logger
|
|
14
14
|
from port_ocean.ocean import Ocean
|
|
15
15
|
from port_ocean.utils.misc import get_spec_file, load_module
|