port-ocean 0.28.8__tar.gz → 0.28.11__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.28.8 → port_ocean-0.28.11}/PKG-INFO +1 -1
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.Deb +0 -1
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.local +0 -1
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/integrations.py +1 -1
- port_ocean-0.28.11/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +357 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/models.py +4 -2
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/sync_raw.py +1 -1
- port_ocean-0.28.11/port_ocean/core/integrations/mixins/utils.py +136 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/utils/utils.py +16 -5
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +1 -1
- port_ocean-0.28.11/port_ocean/tests/core/utils/test_get_port_diff.py +164 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/pyproject.toml +1 -1
- port_ocean-0.28.8/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -679
- port_ocean-0.28.8/port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +0 -69
- port_ocean-0.28.8/port_ocean/core/integrations/mixins/utils.py +0 -348
- {port_ocean-0.28.8 → port_ocean-0.28.11}/LICENSE.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/README.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/README.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/entry_local.sh +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/disk.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/errors.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/memory.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/auth_client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/oauth_client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/authentication.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/entities.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/organization.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/settings.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/event.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/metric_resource.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/webhooks_only.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/group_queue.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/local_queue.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/live_events.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/models.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/debug_cli.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/webhook_processor.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/metric/metric.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/metric/utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/retry.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/stream.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/ocean.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/py.typed +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/run.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/test_disk_cache.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/test_memory_cache.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/oauth/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/config/test_config.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/conftest.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/test_utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/port_client.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/test_retry.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_metric.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_ocean.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/ipc.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/version.py +0 -0
|
@@ -301,7 +301,7 @@ class IntegrationClientMixin:
|
|
|
301
301
|
headers=headers,
|
|
302
302
|
json={
|
|
303
303
|
"items": raw_data,
|
|
304
|
-
"extractionTimestamp": datetime.now().
|
|
304
|
+
"extractionTimestamp": int(datetime.now().timestamp()),
|
|
305
305
|
},
|
|
306
306
|
)
|
|
307
307
|
handle_port_status_code(response, should_log=False)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Task
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
import jq # type: ignore
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from port_ocean.context.ocean import ocean
|
|
11
|
+
from port_ocean.core.handlers.entity_processor.base import BaseEntityProcessor
|
|
12
|
+
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
|
|
13
|
+
from port_ocean.core.models import Entity
|
|
14
|
+
from port_ocean.core.ocean_types import (
|
|
15
|
+
RAW_ITEM,
|
|
16
|
+
EntitySelectorDiff,
|
|
17
|
+
CalculationResult,
|
|
18
|
+
)
|
|
19
|
+
from port_ocean.core.utils.utils import (
|
|
20
|
+
gather_and_split_errors_from_results,
|
|
21
|
+
zip_and_sum,
|
|
22
|
+
)
|
|
23
|
+
from port_ocean.exceptions.core import EntityProcessorException
|
|
24
|
+
from port_ocean.utils.queue_utils import process_in_queue
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExampleStates:
|
|
28
|
+
__succeed: list[dict[str, Any]]
|
|
29
|
+
__errors: list[dict[str, Any]]
|
|
30
|
+
__max_size: int
|
|
31
|
+
|
|
32
|
+
def __init__(self, max_size: int = 0) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Store two sequences:
|
|
35
|
+
- succeed: items that succeeded
|
|
36
|
+
- errors: items that failed
|
|
37
|
+
"""
|
|
38
|
+
self.__succeed = []
|
|
39
|
+
self.__errors = []
|
|
40
|
+
self.__max_size = max_size
|
|
41
|
+
|
|
42
|
+
def add_example(self, succeed: bool, item: dict[str, Any]) -> None:
|
|
43
|
+
if succeed:
|
|
44
|
+
self.__succeed.append(item)
|
|
45
|
+
else:
|
|
46
|
+
self.__errors.append(item)
|
|
47
|
+
|
|
48
|
+
def __len__(self) -> int:
|
|
49
|
+
"""
|
|
50
|
+
Total number of items (successes + errors).
|
|
51
|
+
"""
|
|
52
|
+
return len(self.__succeed) + len(self.__errors)
|
|
53
|
+
|
|
54
|
+
def get_examples(self, number: int = 0) -> list[dict[str, Any]]:
|
|
55
|
+
"""
|
|
56
|
+
Return a list of up to number items, taking successes first,
|
|
57
|
+
"""
|
|
58
|
+
if number <= 0:
|
|
59
|
+
number = self.__max_size
|
|
60
|
+
# how many from succeed?
|
|
61
|
+
s_count = min(number, len(self.__succeed))
|
|
62
|
+
result = list(self.__succeed[:s_count])
|
|
63
|
+
# how many more from errors?
|
|
64
|
+
e_count = number - s_count
|
|
65
|
+
if e_count > 0:
|
|
66
|
+
result.extend(self.__errors[:e_count])
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class MappedEntity:
|
|
72
|
+
"""Represents the entity after applying the mapping
|
|
73
|
+
|
|
74
|
+
This class holds the mapping entity along with the selector boolean value and optionally the raw data.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
entity: dict[str, Any] = field(default_factory=dict)
|
|
78
|
+
did_entity_pass_selector: bool = False
|
|
79
|
+
raw_data: Optional[dict[str, Any]] = None
|
|
80
|
+
misconfigurations: dict[str, str] = field(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class JQEntityProcessor(BaseEntityProcessor):
|
|
84
|
+
"""Processes and parses entities using JQ expressions.
|
|
85
|
+
|
|
86
|
+
This class extends the BaseEntityProcessor and provides methods for processing and
|
|
87
|
+
parsing entities based on PyJQ queries. It supports compiling and executing PyJQ patterns,
|
|
88
|
+
searching for data in dictionaries, and transforming data based on object mappings.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@lru_cache
|
|
92
|
+
def _compile(self, pattern: str) -> Any:
|
|
93
|
+
if not ocean.config.allow_environment_variables_jq_access:
|
|
94
|
+
pattern = "def env: {}; {} as $ENV | " + pattern
|
|
95
|
+
return jq.compile(pattern)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _stop_iterator_handler(func: Any) -> Any:
|
|
99
|
+
"""
|
|
100
|
+
Wrap the function to handle StopIteration exceptions.
|
|
101
|
+
Prevents StopIteration from stopping the thread and skipping further queue processing.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def inner() -> Any:
|
|
105
|
+
try:
|
|
106
|
+
return func()
|
|
107
|
+
except StopIteration:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
return inner
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _notify_mapping_issues(
|
|
114
|
+
entity_misconfigurations: dict[str, str],
|
|
115
|
+
missing_required_fields: bool,
|
|
116
|
+
entity_mapping_fault_counter: int,
|
|
117
|
+
) -> None:
|
|
118
|
+
|
|
119
|
+
if len(entity_misconfigurations) > 0:
|
|
120
|
+
logger.info(
|
|
121
|
+
f"Unable to find valid data for: {entity_misconfigurations} (null, missing, or misconfigured)"
|
|
122
|
+
)
|
|
123
|
+
if missing_required_fields:
|
|
124
|
+
logger.info(
|
|
125
|
+
f"{entity_mapping_fault_counter} transformations of batch failed due to empty, null or missing values"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
async def _search(self, data: dict[str, Any], pattern: str) -> Any:
|
|
129
|
+
try:
|
|
130
|
+
loop = asyncio.get_event_loop()
|
|
131
|
+
compiled_pattern = self._compile(pattern)
|
|
132
|
+
func = compiled_pattern.input_value(data)
|
|
133
|
+
return await loop.run_in_executor(
|
|
134
|
+
None, self._stop_iterator_handler(func.first)
|
|
135
|
+
)
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
logger.debug(
|
|
138
|
+
f"Search failed for pattern '{pattern}' in data: {data}, Error: {exc}"
|
|
139
|
+
)
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
async def _search_as_bool(self, data: dict[str, Any], pattern: str) -> bool:
|
|
143
|
+
loop = asyncio.get_event_loop()
|
|
144
|
+
|
|
145
|
+
compiled_pattern = self._compile(pattern)
|
|
146
|
+
func = compiled_pattern.input_value(data)
|
|
147
|
+
|
|
148
|
+
value = await loop.run_in_executor(
|
|
149
|
+
None, self._stop_iterator_handler(func.first)
|
|
150
|
+
)
|
|
151
|
+
if isinstance(value, bool):
|
|
152
|
+
return value
|
|
153
|
+
raise EntityProcessorException(
|
|
154
|
+
f"Expected boolean value, got value:{value} of type: {type(value)} instead"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
async def _search_as_object(
|
|
158
|
+
self,
|
|
159
|
+
data: dict[str, Any],
|
|
160
|
+
obj: dict[str, Any],
|
|
161
|
+
misconfigurations: dict[str, str] | None = None,
|
|
162
|
+
) -> dict[str, Any | None]:
|
|
163
|
+
"""
|
|
164
|
+
Identify and extract the relevant value for the chosen key and populate it into the entity
|
|
165
|
+
:param data: the property itself that holds the key and the value, it is being passed to the task and we get back a task item,
|
|
166
|
+
if the data is a dict, we will recursively call this function again.
|
|
167
|
+
:param obj: the key that we want its value to be mapped into our entity.
|
|
168
|
+
:param misconfigurations: due to the recursive nature of this function,
|
|
169
|
+
we aim to have a dict that represents all of the misconfigured properties and when used recursively,
|
|
170
|
+
we pass this reference to misfoncigured object to add the relevant misconfigured keys.
|
|
171
|
+
:return: Mapped object with found value.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
search_tasks: dict[
|
|
175
|
+
str, Task[dict[str, Any | None]] | list[Task[dict[str, Any | None]]]
|
|
176
|
+
] = {}
|
|
177
|
+
for key, value in obj.items():
|
|
178
|
+
if isinstance(value, list):
|
|
179
|
+
search_tasks[key] = [
|
|
180
|
+
asyncio.create_task(
|
|
181
|
+
self._search_as_object(data, obj, misconfigurations)
|
|
182
|
+
)
|
|
183
|
+
for obj in value
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
elif isinstance(value, dict):
|
|
187
|
+
search_tasks[key] = asyncio.create_task(
|
|
188
|
+
self._search_as_object(data, value, misconfigurations)
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
search_tasks[key] = asyncio.create_task(self._search(data, value))
|
|
192
|
+
|
|
193
|
+
result: dict[str, Any | None] = {}
|
|
194
|
+
for key, task in search_tasks.items():
|
|
195
|
+
try:
|
|
196
|
+
if isinstance(task, list):
|
|
197
|
+
result_list = []
|
|
198
|
+
for task in task:
|
|
199
|
+
task_result = await task
|
|
200
|
+
if task_result is None and misconfigurations is not None:
|
|
201
|
+
misconfigurations[key] = obj[key]
|
|
202
|
+
result_list.append(task_result)
|
|
203
|
+
result[key] = result_list
|
|
204
|
+
else:
|
|
205
|
+
task_result = await task
|
|
206
|
+
if task_result is None and misconfigurations is not None:
|
|
207
|
+
misconfigurations[key] = obj[key]
|
|
208
|
+
result[key] = task_result
|
|
209
|
+
except Exception:
|
|
210
|
+
result[key] = None
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
async def _get_mapped_entity(
|
|
214
|
+
self,
|
|
215
|
+
data: dict[str, Any],
|
|
216
|
+
raw_entity_mappings: dict[str, Any],
|
|
217
|
+
selector_query: str,
|
|
218
|
+
parse_all: bool = False,
|
|
219
|
+
) -> MappedEntity:
|
|
220
|
+
should_run = await self._search_as_bool(data, selector_query)
|
|
221
|
+
if parse_all or should_run:
|
|
222
|
+
misconfigurations: dict[str, str] = {}
|
|
223
|
+
mapped_entity = await self._search_as_object(
|
|
224
|
+
data, raw_entity_mappings, misconfigurations
|
|
225
|
+
)
|
|
226
|
+
return MappedEntity(
|
|
227
|
+
mapped_entity,
|
|
228
|
+
did_entity_pass_selector=should_run,
|
|
229
|
+
raw_data=data,
|
|
230
|
+
misconfigurations=misconfigurations,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return MappedEntity(
|
|
234
|
+
{},
|
|
235
|
+
did_entity_pass_selector=False,
|
|
236
|
+
raw_data=data,
|
|
237
|
+
misconfigurations={},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
async def _calculate_entity(
|
|
241
|
+
self,
|
|
242
|
+
data: dict[str, Any],
|
|
243
|
+
raw_entity_mappings: dict[str, Any],
|
|
244
|
+
items_to_parse: str | None,
|
|
245
|
+
items_to_parse_name: str,
|
|
246
|
+
selector_query: str,
|
|
247
|
+
parse_all: bool = False,
|
|
248
|
+
) -> tuple[list[MappedEntity], list[Exception]]:
|
|
249
|
+
raw_data = [data.copy()]
|
|
250
|
+
if not ocean.config.yield_items_to_parse:
|
|
251
|
+
if items_to_parse:
|
|
252
|
+
items = await self._search(data, items_to_parse)
|
|
253
|
+
if not isinstance(items, list):
|
|
254
|
+
logger.warning(
|
|
255
|
+
f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
|
|
256
|
+
f" Skipping..."
|
|
257
|
+
)
|
|
258
|
+
return [], []
|
|
259
|
+
raw_data = [{items_to_parse_name: item, **data} for item in items]
|
|
260
|
+
|
|
261
|
+
entities, errors = await gather_and_split_errors_from_results(
|
|
262
|
+
[
|
|
263
|
+
self._get_mapped_entity(
|
|
264
|
+
raw,
|
|
265
|
+
raw_entity_mappings,
|
|
266
|
+
selector_query,
|
|
267
|
+
parse_all,
|
|
268
|
+
)
|
|
269
|
+
for raw in raw_data
|
|
270
|
+
]
|
|
271
|
+
)
|
|
272
|
+
if errors:
|
|
273
|
+
logger.error(
|
|
274
|
+
f"Failed to calculate entities with {len(errors)} errors. errors: {errors}"
|
|
275
|
+
)
|
|
276
|
+
return entities, errors
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
async def _send_examples(data: list[dict[str, Any]], kind: str) -> None:
|
|
280
|
+
try:
|
|
281
|
+
if data:
|
|
282
|
+
await ocean.port_client.ingest_integration_kind_examples(
|
|
283
|
+
kind, data, should_log=False
|
|
284
|
+
)
|
|
285
|
+
except Exception as ex:
|
|
286
|
+
logger.warning(
|
|
287
|
+
f"Failed to send raw data example {ex}",
|
|
288
|
+
exc_info=True,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
async def _parse_items(
|
|
292
|
+
self,
|
|
293
|
+
mapping: ResourceConfig,
|
|
294
|
+
raw_results: list[RAW_ITEM],
|
|
295
|
+
parse_all: bool = False,
|
|
296
|
+
send_raw_data_examples_amount: int = 0,
|
|
297
|
+
) -> CalculationResult:
|
|
298
|
+
raw_entity_mappings: dict[str, Any] = mapping.port.entity.mappings.dict(
|
|
299
|
+
exclude_unset=True
|
|
300
|
+
)
|
|
301
|
+
logger.info(f"Parsing {len(raw_results)} raw results into entities")
|
|
302
|
+
calculated_entities_results, errors = zip_and_sum(
|
|
303
|
+
await process_in_queue(
|
|
304
|
+
raw_results,
|
|
305
|
+
self._calculate_entity,
|
|
306
|
+
raw_entity_mappings,
|
|
307
|
+
mapping.port.items_to_parse,
|
|
308
|
+
mapping.port.items_to_parse_name,
|
|
309
|
+
mapping.selector.query,
|
|
310
|
+
parse_all,
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
logger.debug(
|
|
314
|
+
f"Finished parsing raw results into entities with {len(errors)} errors. errors: {errors}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
passed_entities = []
|
|
318
|
+
failed_entities = []
|
|
319
|
+
examples_to_send = ExampleStates(send_raw_data_examples_amount)
|
|
320
|
+
entity_misconfigurations: dict[str, str] = {}
|
|
321
|
+
missing_required_fields: bool = False
|
|
322
|
+
entity_mapping_fault_counter: int = 0
|
|
323
|
+
for result in calculated_entities_results:
|
|
324
|
+
if len(result.misconfigurations) > 0:
|
|
325
|
+
entity_misconfigurations |= result.misconfigurations
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
len(examples_to_send) < send_raw_data_examples_amount
|
|
329
|
+
and result.raw_data is not None
|
|
330
|
+
):
|
|
331
|
+
examples_to_send.add_example(
|
|
332
|
+
result.did_entity_pass_selector, result.raw_data
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if result.entity.get("identifier") and result.entity.get("blueprint"):
|
|
336
|
+
parsed_entity = Entity.parse_obj(result.entity)
|
|
337
|
+
if result.did_entity_pass_selector:
|
|
338
|
+
passed_entities.append(parsed_entity)
|
|
339
|
+
else:
|
|
340
|
+
failed_entities.append(parsed_entity)
|
|
341
|
+
else:
|
|
342
|
+
missing_required_fields = True
|
|
343
|
+
entity_mapping_fault_counter += 1
|
|
344
|
+
|
|
345
|
+
self._notify_mapping_issues(
|
|
346
|
+
entity_misconfigurations,
|
|
347
|
+
missing_required_fields,
|
|
348
|
+
entity_mapping_fault_counter,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
await self._send_examples(examples_to_send.get_examples(), mapping.kind)
|
|
352
|
+
|
|
353
|
+
return CalculationResult(
|
|
354
|
+
EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
|
|
355
|
+
errors,
|
|
356
|
+
misconfigured_entity_keys=entity_misconfigurations,
|
|
357
|
+
)
|
|
@@ -29,7 +29,9 @@ class EntityMapping(BaseModel):
|
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
31
|
def is_using_search_identifier(self) -> bool:
|
|
32
|
-
return isinstance(self.identifier, dict)
|
|
32
|
+
return isinstance(self.identifier, dict) or isinstance(
|
|
33
|
+
self.identifier, IngestSearchQuery
|
|
34
|
+
)
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class MappingsConfig(BaseModel):
|
|
@@ -39,7 +41,7 @@ class MappingsConfig(BaseModel):
|
|
|
39
41
|
class PortResourceConfig(BaseModel):
|
|
40
42
|
entity: MappingsConfig
|
|
41
43
|
items_to_parse: str | None = Field(alias="itemsToParse")
|
|
42
|
-
items_to_parse_name: str = Field(alias="itemsToParseName", default="item")
|
|
44
|
+
items_to_parse_name: str | None = Field(alias="itemsToParseName", default="item")
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
class Selector(BaseModel):
|
|
@@ -117,7 +117,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
117
117
|
logger.info(
|
|
118
118
|
f"Found async generator function for {resource_config.kind} name: {task.__qualname__}"
|
|
119
119
|
)
|
|
120
|
-
results.append(resync_generator_wrapper(task, resource_config.kind,
|
|
120
|
+
results.append(resync_generator_wrapper(task, resource_config.kind,resource_config.port.items_to_parse))
|
|
121
121
|
else:
|
|
122
122
|
logger.info(
|
|
123
123
|
f"Found sync function for {resource_config.kind} name: {task.__qualname__}"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Awaitable, Generator, Callable, cast
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import multiprocessing
|
|
8
|
+
|
|
9
|
+
from port_ocean.core.handlers.entity_processor.jq_entity_processor import JQEntityProcessor
|
|
10
|
+
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
|
|
11
|
+
from port_ocean.core.ocean_types import (
|
|
12
|
+
ASYNC_GENERATOR_RESYNC_TYPE,
|
|
13
|
+
RAW_RESULT,
|
|
14
|
+
RESYNC_EVENT_LISTENER,
|
|
15
|
+
RESYNC_RESULT,
|
|
16
|
+
)
|
|
17
|
+
from port_ocean.core.utils.utils import validate_result
|
|
18
|
+
from port_ocean.exceptions.core import (
|
|
19
|
+
RawObjectValidationException,
|
|
20
|
+
OceanAbortException,
|
|
21
|
+
KindNotImplementedException,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from port_ocean.utils.async_http import _http_client
|
|
25
|
+
from port_ocean.clients.port.utils import _http_client as _port_http_client
|
|
26
|
+
from port_ocean.helpers.metric.metric import MetricType, MetricPhase
|
|
27
|
+
from port_ocean.context.ocean import ocean
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def resync_error_handling() -> Generator[None, None, None]:
|
|
31
|
+
try:
|
|
32
|
+
yield
|
|
33
|
+
except RawObjectValidationException as error:
|
|
34
|
+
err_msg = f"Failed to validate raw data for returned data from resync function, error: {error}"
|
|
35
|
+
logger.exception(err_msg)
|
|
36
|
+
raise OceanAbortException(err_msg) from error
|
|
37
|
+
except StopAsyncIteration:
|
|
38
|
+
raise
|
|
39
|
+
except Exception as error:
|
|
40
|
+
err_msg = f"Failed to execute resync function, error: {error}"
|
|
41
|
+
logger.exception(err_msg)
|
|
42
|
+
raise OceanAbortException(err_msg) from error
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def resync_function_wrapper(
|
|
46
|
+
fn: Callable[[str], Awaitable[RAW_RESULT]], kind: str
|
|
47
|
+
) -> RAW_RESULT:
|
|
48
|
+
with resync_error_handling():
|
|
49
|
+
results = await fn(kind)
|
|
50
|
+
return validate_result(results)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def resync_generator_wrapper(
|
|
54
|
+
fn: Callable[[str], ASYNC_GENERATOR_RESYNC_TYPE], kind: str, items_to_parse: str | None = None
|
|
55
|
+
) -> ASYNC_GENERATOR_RESYNC_TYPE:
|
|
56
|
+
generator = fn(kind)
|
|
57
|
+
errors = []
|
|
58
|
+
try:
|
|
59
|
+
while True:
|
|
60
|
+
try:
|
|
61
|
+
with resync_error_handling():
|
|
62
|
+
result = await anext(generator)
|
|
63
|
+
if not ocean.config.yield_items_to_parse:
|
|
64
|
+
yield validate_result(result)
|
|
65
|
+
else:
|
|
66
|
+
batch_size = ocean.config.yield_items_to_parse_batch_size
|
|
67
|
+
if items_to_parse:
|
|
68
|
+
for data in result:
|
|
69
|
+
items = await cast(JQEntityProcessor, ocean.app.integration.entity_processor)._search(data, items_to_parse)
|
|
70
|
+
if not isinstance(items, list):
|
|
71
|
+
logger.warning(
|
|
72
|
+
f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
|
|
73
|
+
f" Skipping..."
|
|
74
|
+
)
|
|
75
|
+
yield []
|
|
76
|
+
raw_data = [{"item": item, **data} for item in items]
|
|
77
|
+
while True:
|
|
78
|
+
raw_data_batch = raw_data[:batch_size]
|
|
79
|
+
yield raw_data_batch
|
|
80
|
+
raw_data = raw_data[batch_size:]
|
|
81
|
+
if len(raw_data) == 0:
|
|
82
|
+
break
|
|
83
|
+
else:
|
|
84
|
+
yield validate_result(result)
|
|
85
|
+
except OceanAbortException as error:
|
|
86
|
+
errors.append(error)
|
|
87
|
+
ocean.metrics.inc_metric(
|
|
88
|
+
name=MetricType.OBJECT_COUNT_NAME,
|
|
89
|
+
labels=[ocean.metrics.current_resource_kind(), MetricPhase.EXTRACT , MetricPhase.ExtractResult.FAILED],
|
|
90
|
+
value=1
|
|
91
|
+
)
|
|
92
|
+
except StopAsyncIteration:
|
|
93
|
+
if errors:
|
|
94
|
+
raise ExceptionGroup(
|
|
95
|
+
"At least one of the resync generator iterations failed", errors
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def is_resource_supported(
|
|
100
|
+
kind: str, resync_event_mapping: dict[str | None, list[RESYNC_EVENT_LISTENER]]
|
|
101
|
+
) -> bool:
|
|
102
|
+
return bool(resync_event_mapping[kind] or resync_event_mapping[None])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def unsupported_kind_response(
|
|
106
|
+
kind: str, available_resync_kinds: list[str]
|
|
107
|
+
) -> tuple[RESYNC_RESULT, list[Exception]]:
|
|
108
|
+
logger.error(f"Kind {kind} is not supported in this integration")
|
|
109
|
+
return [], [KindNotImplementedException(kind, available_resync_kinds)]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ProcessWrapper(multiprocessing.Process):
|
|
113
|
+
def __init__(self, *args, **kwargs):
|
|
114
|
+
super().__init__(*args, **kwargs)
|
|
115
|
+
|
|
116
|
+
async def join_async(self) -> None:
|
|
117
|
+
while self.exitcode is None:
|
|
118
|
+
await asyncio.sleep(2)
|
|
119
|
+
if self.exitcode != 0:
|
|
120
|
+
logger.error(f"Process {self.pid} failed with exit code {self.exitcode}")
|
|
121
|
+
else:
|
|
122
|
+
logger.info(f"Process {self.pid} finished with exit code {self.exitcode}")
|
|
123
|
+
return super().join()
|
|
124
|
+
|
|
125
|
+
def clear_http_client_context() -> None:
|
|
126
|
+
try:
|
|
127
|
+
while _http_client.top is not None:
|
|
128
|
+
_http_client.pop()
|
|
129
|
+
except (RuntimeError, AttributeError):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
while _port_http_client.top is not None:
|
|
134
|
+
_port_http_client.pop()
|
|
135
|
+
except (RuntimeError, AttributeError):
|
|
136
|
+
pass
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from typing import Iterable, Any, TypeVar, Callable, Awaitable
|
|
5
5
|
|
|
6
6
|
from loguru import logger
|
|
7
|
-
from pydantic import parse_obj_as, ValidationError
|
|
7
|
+
from pydantic import BaseModel, parse_obj_as, ValidationError
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
from port_ocean.clients.port.client import PortClient
|
|
@@ -79,6 +79,19 @@ async def gather_and_split_errors_from_results(
|
|
|
79
79
|
return valid_items, errors
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _get_entity_key(entity: Entity) -> tuple[str, str]:
|
|
83
|
+
identifier = entity.identifier
|
|
84
|
+
if isinstance(identifier, BaseModel):
|
|
85
|
+
identifier = identifier.dict()
|
|
86
|
+
|
|
87
|
+
key_part = (
|
|
88
|
+
json.dumps(identifier, sort_keys=True)
|
|
89
|
+
if isinstance(identifier, dict)
|
|
90
|
+
else str(identifier)
|
|
91
|
+
)
|
|
92
|
+
return key_part, entity.blueprint
|
|
93
|
+
|
|
94
|
+
|
|
82
95
|
def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPortDiff:
|
|
83
96
|
before_dict = {}
|
|
84
97
|
after_dict = {}
|
|
@@ -88,12 +101,10 @@ def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPo
|
|
|
88
101
|
|
|
89
102
|
# Create dictionaries for before and after lists
|
|
90
103
|
for entity in before:
|
|
91
|
-
|
|
92
|
-
before_dict[key] = entity
|
|
104
|
+
before_dict[_get_entity_key(entity)] = entity
|
|
93
105
|
|
|
94
106
|
for entity in after:
|
|
95
|
-
|
|
96
|
-
after_dict[key] = entity
|
|
107
|
+
after_dict[_get_entity_key(entity)] = entity
|
|
97
108
|
|
|
98
109
|
# Find created, modified, and deleted objects
|
|
99
110
|
for key, obj in after_dict.items():
|
|
@@ -50,7 +50,7 @@ class TestJQEntityProcessor:
|
|
|
50
50
|
raw_entity_mappings = {"foo": ".foo"}
|
|
51
51
|
selector_query = '.foo == "bar"'
|
|
52
52
|
result = await mocked_processor._get_mapped_entity(
|
|
53
|
-
data, raw_entity_mappings,
|
|
53
|
+
data, raw_entity_mappings, selector_query
|
|
54
54
|
)
|
|
55
55
|
assert result.entity == {"foo": "bar"}
|
|
56
56
|
assert result.did_entity_pass_selector is True
|