port-ocean 0.30.7__tar.gz → 0.31.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.
- {port_ocean-0.30.7 → port_ocean-0.31.0}/PKG-INFO +1 -1
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.Deb +1 -1
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.local +2 -2
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/entry_local.sh +2 -1
- port_ocean-0.31.0/integrations/_infra/hosts +4 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/config/settings.py +1 -2
- port_ocean-0.31.0/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +375 -0
- port_ocean-0.31.0/port_ocean/core/integrations/mixins/utils.py +198 -0
- port_ocean-0.31.0/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +382 -0
- port_ocean-0.31.0/port_ocean/tests/core/integrations/mixins/test_integration_utils.py +182 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/pyproject.toml +1 -1
- port_ocean-0.30.7/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -841
- port_ocean-0.30.7/port_ocean/core/integrations/mixins/utils.py +0 -428
- port_ocean-0.30.7/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -1313
- port_ocean-0.30.7/port_ocean/tests/core/integrations/mixins/test_integration_utils.py +0 -494
- {port_ocean-0.30.7 → port_ocean-0.31.0}/LICENSE.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/README.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/README.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cache/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cache/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cache/disk.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cache/errors.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cache/memory.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/auth/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/auth/auth_client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/auth/oauth_client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/authentication.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/actions.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/entities.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/mixins/organization.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/config/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/context/event.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/context/metric_resource.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/actions_only.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/event_listener/webhooks_only.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/actions/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/actions/abstract_executor.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/actions/execution_manager.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/queue/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/queue/group_queue.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/queue/local_queue.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/webhook/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/live_events.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/models.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/core/utils/utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/debug_cli.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/execution_manager.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/exceptions/webhook_processor.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/metric/metric.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/metric/utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/retry.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/helpers/stream.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/ocean.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/py.typed +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/run.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/cache/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/cache/test_disk_cache.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/cache/test_memory_cache.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/oauth/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/clients/test_streaming_wrapper.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/config/test_config.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/conftest.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/defaults/test_initialize.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/actions/test_execution_manager.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_input_evaluator.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/test_utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/utils/test_get_port_diff.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/port_client.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/helpers/test_retry.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/test_metric.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/test_metrics_endpoints.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/test_ocean.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/ipc.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.30.7 → port_ocean-0.31.0}/port_ocean/version.py +0 -0
|
@@ -26,10 +26,10 @@ RUN apt-get update \
|
|
|
26
26
|
python3-pip \
|
|
27
27
|
python3-poetry \
|
|
28
28
|
build-essential\
|
|
29
|
-
jq \
|
|
30
29
|
git \
|
|
31
30
|
python3-venv \
|
|
32
31
|
acl \
|
|
32
|
+
libyaml-dev \
|
|
33
33
|
&& apt-get clean
|
|
34
34
|
|
|
35
35
|
ARG BUILD_CONTEXT
|
|
@@ -51,7 +51,6 @@ RUN rm -rf .venv-docker ${BUILD_CONTEXT}/.venv-docker
|
|
|
51
51
|
RUN python3 -m venv .venv-docker
|
|
52
52
|
RUN python3 -m venv ${BUILD_CONTEXT}/.venv-docker
|
|
53
53
|
|
|
54
|
-
|
|
55
54
|
WORKDIR /app/${BUILD_CONTEXT}
|
|
56
55
|
|
|
57
56
|
WORKDIR /app
|
|
@@ -60,6 +59,7 @@ RUN chown -R ocean:appgroup /app/${BUILD_CONTEXT} && chmod -R 755 /app/${BUILD_C
|
|
|
60
59
|
RUN chown -R ocean:appgroup /tmp/ocean && chmod -R 755 /tmp/ocean
|
|
61
60
|
# Add ocean user to ssl certs group
|
|
62
61
|
RUN setfacl -m u:ocean:rwX /etc/ssl/certs
|
|
62
|
+
|
|
63
63
|
USER ocean
|
|
64
64
|
|
|
65
65
|
ENTRYPOINT ["./integrations/_infra/entry_local.sh"]
|
|
@@ -132,8 +132,7 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
|
|
|
132
132
|
upsert_entities_batch_max_length: int = 20
|
|
133
133
|
upsert_entities_batch_max_size_in_bytes: int = 1024 * 1024
|
|
134
134
|
lakehouse_enabled: bool = False
|
|
135
|
-
|
|
136
|
-
yield_items_to_parse_batch_size: int = 10
|
|
135
|
+
yield_items_to_parse_batch_size: int = 500
|
|
137
136
|
|
|
138
137
|
streaming: StreamingSettings = Field(default_factory=lambda: StreamingSettings())
|
|
139
138
|
actions_processor: ActionsProcessorSettings = Field(
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from asyncio import Task
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
import jq # type: ignore
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from port_ocean.context.ocean import ocean
|
|
13
|
+
from port_ocean.core.handlers.entity_processor.base import BaseEntityProcessor
|
|
14
|
+
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
|
|
15
|
+
from port_ocean.core.models import Entity
|
|
16
|
+
from port_ocean.core.ocean_types import (
|
|
17
|
+
RAW_ITEM,
|
|
18
|
+
CalculationResult,
|
|
19
|
+
EntitySelectorDiff,
|
|
20
|
+
)
|
|
21
|
+
from port_ocean.core.utils.utils import (
|
|
22
|
+
gather_and_split_errors_from_results,
|
|
23
|
+
zip_and_sum,
|
|
24
|
+
)
|
|
25
|
+
from port_ocean.exceptions.core import EntityProcessorException
|
|
26
|
+
from port_ocean.utils.queue_utils import process_in_queue
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ExampleStates:
|
|
30
|
+
__succeed: list[dict[str, Any]]
|
|
31
|
+
__errors: list[dict[str, Any]]
|
|
32
|
+
__max_size: int
|
|
33
|
+
|
|
34
|
+
def __init__(self, max_size: int = 0) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Store two sequences:
|
|
37
|
+
- succeed: items that succeeded
|
|
38
|
+
- errors: items that failed
|
|
39
|
+
"""
|
|
40
|
+
self.__succeed = []
|
|
41
|
+
self.__errors = []
|
|
42
|
+
self.__max_size = max_size
|
|
43
|
+
|
|
44
|
+
def add_example(self, succeed: bool, item: dict[str, Any]) -> None:
|
|
45
|
+
if succeed:
|
|
46
|
+
self.__succeed.append(item)
|
|
47
|
+
else:
|
|
48
|
+
self.__errors.append(item)
|
|
49
|
+
|
|
50
|
+
def __len__(self) -> int:
|
|
51
|
+
"""
|
|
52
|
+
Total number of items (successes + errors).
|
|
53
|
+
"""
|
|
54
|
+
return len(self.__succeed) + len(self.__errors)
|
|
55
|
+
|
|
56
|
+
def get_examples(self, number: int = 0) -> list[dict[str, Any]]:
|
|
57
|
+
"""
|
|
58
|
+
Return a list of up to number items, taking successes first,
|
|
59
|
+
"""
|
|
60
|
+
if number <= 0:
|
|
61
|
+
number = self.__max_size
|
|
62
|
+
# how many from succeed?
|
|
63
|
+
s_count = min(number, len(self.__succeed))
|
|
64
|
+
result = list(self.__succeed[:s_count])
|
|
65
|
+
# how many more from errors?
|
|
66
|
+
e_count = number - s_count
|
|
67
|
+
if e_count > 0:
|
|
68
|
+
result.extend(self.__errors[:e_count])
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class MappedEntity:
|
|
74
|
+
"""Represents the entity after applying the mapping
|
|
75
|
+
|
|
76
|
+
This class holds the mapping entity along with the selector boolean value and optionally the raw data.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
entity: dict[str, Any] = field(default_factory=dict)
|
|
80
|
+
did_entity_pass_selector: bool = False
|
|
81
|
+
raw_data: Optional[dict[str, Any] | tuple[dict[str, Any], str]] = None
|
|
82
|
+
misconfigurations: dict[str, str] = field(default_factory=dict)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class JQEntityProcessor(BaseEntityProcessor):
|
|
86
|
+
"""Processes and parses entities using JQ expressions.
|
|
87
|
+
|
|
88
|
+
This class extends the BaseEntityProcessor and provides methods for processing and
|
|
89
|
+
parsing entities based on PyJQ queries. It supports compiling and executing PyJQ patterns,
|
|
90
|
+
searching for data in dictionaries, and transforming data based on object mappings.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _format_filter(filter: str) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Convert single quotes to double quotes in JQ expressions.
|
|
97
|
+
Only replaces single quotes that are opening or closing string delimiters,
|
|
98
|
+
not single quotes that are part of string content.
|
|
99
|
+
"""
|
|
100
|
+
# Escape single quotes only if they are opening or closing a string
|
|
101
|
+
# Pattern matches:
|
|
102
|
+
# - Single quote at start of string or after whitespace (opening quote)
|
|
103
|
+
# - Single quote before whitespace or end of string (closing quote)
|
|
104
|
+
# Uses negative lookahead/lookbehind to avoid replacing quotes inside strings
|
|
105
|
+
# \1 and \2 will be empty for the alternative that didn't match, so \1"\2 works for both cases
|
|
106
|
+
# This matches the TypeScript pattern: /(^|\s)'(?!\s|")|(?<!\s|")'(\s|$)/g
|
|
107
|
+
formatted_filter = re.sub(
|
|
108
|
+
r'(^|\s)\'(?!\s|")|(?<!\s|")\'(\s|$)', r'\1"\2', filter
|
|
109
|
+
)
|
|
110
|
+
return formatted_filter
|
|
111
|
+
|
|
112
|
+
@lru_cache
|
|
113
|
+
def _compile(self, pattern: str) -> Any:
|
|
114
|
+
# Convert single quotes to double quotes for JQ compatibility
|
|
115
|
+
pattern = self._format_filter(pattern)
|
|
116
|
+
if not ocean.config.allow_environment_variables_jq_access:
|
|
117
|
+
pattern = "def env: {}; {} as $ENV | " + pattern
|
|
118
|
+
return jq.compile(pattern)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _stop_iterator_handler(func: Any) -> Any:
|
|
122
|
+
"""
|
|
123
|
+
Wrap the function to handle StopIteration exceptions.
|
|
124
|
+
Prevents StopIteration from stopping the thread and skipping further queue processing.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def inner() -> Any:
|
|
128
|
+
try:
|
|
129
|
+
return func()
|
|
130
|
+
except StopIteration:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
return inner
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _notify_mapping_issues(
|
|
137
|
+
entity_misconfigurations: dict[str, str],
|
|
138
|
+
missing_required_fields: bool,
|
|
139
|
+
entity_mapping_fault_counter: int,
|
|
140
|
+
) -> None:
|
|
141
|
+
if len(entity_misconfigurations) > 0:
|
|
142
|
+
logger.error(
|
|
143
|
+
f"Unable to find valid data for: {entity_misconfigurations} (null, missing, or misconfigured)"
|
|
144
|
+
)
|
|
145
|
+
if missing_required_fields:
|
|
146
|
+
logger.error(
|
|
147
|
+
f"{entity_mapping_fault_counter} transformations of batch failed due to empty, null or missing values"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
async def _search(self, data: dict[str, Any], pattern: str) -> Any:
|
|
151
|
+
try:
|
|
152
|
+
compiled_pattern = self._compile(pattern)
|
|
153
|
+
func = compiled_pattern.input_value(data)
|
|
154
|
+
return func.first()
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.error(
|
|
157
|
+
f"Search failed for pattern '{pattern}' in data: {data}, Error: {exc}"
|
|
158
|
+
)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
async def _search_as_bool(self, data: dict[str, Any] | str, pattern: str) -> bool:
|
|
162
|
+
|
|
163
|
+
compiled_pattern = self._compile(pattern)
|
|
164
|
+
|
|
165
|
+
func = compiled_pattern.input_value(data)
|
|
166
|
+
|
|
167
|
+
value = func.first()
|
|
168
|
+
if isinstance(value, bool):
|
|
169
|
+
return value
|
|
170
|
+
raise EntityProcessorException(
|
|
171
|
+
f"Expected boolean value, got value:{value} of type: {type(value)} instead"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def _search_as_object(
|
|
175
|
+
self,
|
|
176
|
+
data: dict[str, Any],
|
|
177
|
+
obj: dict[str, Any],
|
|
178
|
+
misconfigurations: dict[str, str] | None = None,
|
|
179
|
+
) -> dict[str, Any | None]:
|
|
180
|
+
"""
|
|
181
|
+
Identify and extract the relevant value for the chosen key and populate it into the entity
|
|
182
|
+
: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,
|
|
183
|
+
if the data is a dict, we will recursively call this function again.
|
|
184
|
+
:param obj: the key that we want its value to be mapped into our entity.
|
|
185
|
+
:param misconfigurations: due to the recursive nature of this function,
|
|
186
|
+
we aim to have a dict that represents all of the misconfigured properties and when used recursively,
|
|
187
|
+
we pass this reference to misfoncigured object to add the relevant misconfigured keys.
|
|
188
|
+
:return: Mapped object with found value.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
search_tasks: dict[
|
|
192
|
+
str, Task[dict[str, Any | None]] | list[Task[dict[str, Any | None]]]
|
|
193
|
+
] = {}
|
|
194
|
+
for key, value in obj.items():
|
|
195
|
+
if isinstance(value, list):
|
|
196
|
+
search_tasks[key] = [
|
|
197
|
+
asyncio.create_task(
|
|
198
|
+
self._search_as_object(data, obj, misconfigurations)
|
|
199
|
+
)
|
|
200
|
+
for obj in value
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
elif isinstance(value, dict):
|
|
204
|
+
search_tasks[key] = asyncio.create_task(
|
|
205
|
+
self._search_as_object(data, value, misconfigurations)
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
search_tasks[key] = asyncio.create_task(self._search(data, value))
|
|
209
|
+
|
|
210
|
+
result: dict[str, Any | None] = {}
|
|
211
|
+
for key, task in search_tasks.items():
|
|
212
|
+
try:
|
|
213
|
+
if isinstance(task, list):
|
|
214
|
+
result_list = []
|
|
215
|
+
for task in task:
|
|
216
|
+
task_result = await task
|
|
217
|
+
if task_result is None and misconfigurations is not None:
|
|
218
|
+
misconfigurations[key] = obj[key]
|
|
219
|
+
result_list.append(task_result)
|
|
220
|
+
result[key] = result_list
|
|
221
|
+
else:
|
|
222
|
+
task_result = await task
|
|
223
|
+
if task_result is None and misconfigurations is not None:
|
|
224
|
+
misconfigurations[key] = obj[key]
|
|
225
|
+
result[key] = task_result
|
|
226
|
+
except Exception:
|
|
227
|
+
result[key] = None
|
|
228
|
+
return result
|
|
229
|
+
|
|
230
|
+
async def _get_mapped_entity(
|
|
231
|
+
self,
|
|
232
|
+
data: dict[str, Any],
|
|
233
|
+
raw_entity_mappings: dict[str, Any],
|
|
234
|
+
selector_query: str,
|
|
235
|
+
parse_all: bool = False,
|
|
236
|
+
) -> MappedEntity:
|
|
237
|
+
should_run = await self._search_as_bool(data, selector_query)
|
|
238
|
+
if parse_all or should_run:
|
|
239
|
+
misconfigurations: dict[str, str] = {}
|
|
240
|
+
mapped_entity = await self._search_as_object(
|
|
241
|
+
data, raw_entity_mappings, misconfigurations
|
|
242
|
+
)
|
|
243
|
+
return MappedEntity(
|
|
244
|
+
mapped_entity,
|
|
245
|
+
did_entity_pass_selector=should_run,
|
|
246
|
+
raw_data=data if should_run else None,
|
|
247
|
+
misconfigurations=misconfigurations,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return MappedEntity()
|
|
251
|
+
|
|
252
|
+
async def _calculate_entity(
|
|
253
|
+
self,
|
|
254
|
+
data: dict[str, Any],
|
|
255
|
+
raw_entity_mappings: dict[str, Any],
|
|
256
|
+
selector_query: str,
|
|
257
|
+
parse_all: bool = False,
|
|
258
|
+
) -> tuple[list[MappedEntity], list[Exception]]:
|
|
259
|
+
raw_data = [data.copy()]
|
|
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.selector.query,
|
|
308
|
+
parse_all,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
logger.debug(
|
|
312
|
+
f"Finished parsing raw results into entities with {len(errors)} errors. errors: {errors}"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
passed_entities = []
|
|
316
|
+
failed_entities = []
|
|
317
|
+
examples_to_send = ExampleStates(send_raw_data_examples_amount)
|
|
318
|
+
entity_misconfigurations: dict[str, str] = {}
|
|
319
|
+
missing_required_fields: bool = False
|
|
320
|
+
entity_mapping_fault_counter: int = 0
|
|
321
|
+
for result in calculated_entities_results:
|
|
322
|
+
if len(result.misconfigurations) > 0:
|
|
323
|
+
entity_misconfigurations |= result.misconfigurations
|
|
324
|
+
|
|
325
|
+
if (
|
|
326
|
+
len(examples_to_send) < send_raw_data_examples_amount
|
|
327
|
+
and result.raw_data is not None
|
|
328
|
+
):
|
|
329
|
+
examples_to_send.add_example(
|
|
330
|
+
result.did_entity_pass_selector,
|
|
331
|
+
self._get_raw_data_for_example(
|
|
332
|
+
result.raw_data, mapping.port.items_to_parse_name
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if result.entity.get("identifier") and result.entity.get("blueprint"):
|
|
337
|
+
parsed_entity = Entity.parse_obj(result.entity)
|
|
338
|
+
if result.did_entity_pass_selector:
|
|
339
|
+
passed_entities.append(parsed_entity)
|
|
340
|
+
else:
|
|
341
|
+
failed_entities.append(parsed_entity)
|
|
342
|
+
else:
|
|
343
|
+
missing_required_fields = True
|
|
344
|
+
entity_mapping_fault_counter += 1
|
|
345
|
+
|
|
346
|
+
self._notify_mapping_issues(
|
|
347
|
+
entity_misconfigurations,
|
|
348
|
+
missing_required_fields,
|
|
349
|
+
entity_mapping_fault_counter,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
await self._send_examples(examples_to_send.get_examples(), mapping.kind)
|
|
353
|
+
|
|
354
|
+
return CalculationResult(
|
|
355
|
+
EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
|
|
356
|
+
errors,
|
|
357
|
+
misconfigured_entity_keys=entity_misconfigurations,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def _get_raw_data_for_example(
|
|
361
|
+
self,
|
|
362
|
+
data: dict[str, Any] | tuple[dict[str, Any], str],
|
|
363
|
+
items_to_parse_name: str,
|
|
364
|
+
) -> dict[str, Any]:
|
|
365
|
+
if isinstance(data, tuple):
|
|
366
|
+
raw_data = json.loads(data[1])
|
|
367
|
+
return {
|
|
368
|
+
**(
|
|
369
|
+
data[0]
|
|
370
|
+
if items_to_parse_name in data[0]
|
|
371
|
+
else {items_to_parse_name: data[0]}
|
|
372
|
+
),
|
|
373
|
+
**raw_data,
|
|
374
|
+
}
|
|
375
|
+
return data
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import multiprocessing
|
|
3
|
+
import re
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, AsyncGenerator, Awaitable, Callable, Generator
|
|
6
|
+
|
|
7
|
+
import ijson
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from port_ocean.clients.port.utils import _http_client as _port_http_client
|
|
11
|
+
from port_ocean.context.ocean import ocean
|
|
12
|
+
from port_ocean.core.ocean_types import (
|
|
13
|
+
ASYNC_GENERATOR_RESYNC_TYPE,
|
|
14
|
+
RAW_RESULT,
|
|
15
|
+
RESYNC_EVENT_LISTENER,
|
|
16
|
+
RESYNC_RESULT,
|
|
17
|
+
)
|
|
18
|
+
from port_ocean.core.utils.utils import validate_result
|
|
19
|
+
from port_ocean.exceptions.core import (
|
|
20
|
+
RawObjectValidationException,
|
|
21
|
+
OceanAbortException,
|
|
22
|
+
KindNotImplementedException,
|
|
23
|
+
)
|
|
24
|
+
from port_ocean.helpers.metric.metric import MetricType, MetricPhase
|
|
25
|
+
from port_ocean.utils.async_http import _http_client
|
|
26
|
+
|
|
27
|
+
def extract_jq_deletion_path_revised(jq_expression: str) -> str | None:
|
|
28
|
+
"""
|
|
29
|
+
Revised function to extract a simple path suitable for del() by analyzing pipe segments.
|
|
30
|
+
"""
|
|
31
|
+
expr = jq_expression.strip()
|
|
32
|
+
|
|
33
|
+
# 1. Handle surrounding parentheses and extract the main chain
|
|
34
|
+
if expr.startswith('('):
|
|
35
|
+
match_paren = re.match(r'\((.*?)\)', expr, re.DOTALL)
|
|
36
|
+
if match_paren:
|
|
37
|
+
chain = match_paren.group(1).strip()
|
|
38
|
+
else:
|
|
39
|
+
return None
|
|
40
|
+
else:
|
|
41
|
+
chain = expr
|
|
42
|
+
|
|
43
|
+
# 2. Split the chain by the main pipe operator (excluding pipes inside quotes or brackets,
|
|
44
|
+
# but for simplicity here, we split naively and check segments)
|
|
45
|
+
segments = chain.split('|')
|
|
46
|
+
|
|
47
|
+
# 3. Analyze each segment for a simple path
|
|
48
|
+
for segment in segments:
|
|
49
|
+
segment = segment.strip()
|
|
50
|
+
|
|
51
|
+
# Ignore variable assignment segments like '. as $root'
|
|
52
|
+
if re.match(r'^\.\s+as\s+\$\w+', segment):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Ignore identity and variable access like '.' or '$items'
|
|
56
|
+
if segment == '.' or segment.startswith('$'):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Look for the first genuine path accessor (e.g., .key, .[index], .key.nested, .key[0])
|
|
60
|
+
# This regex looks for a starting dot and follows it with path components
|
|
61
|
+
# (alphanumeric keys or bracketed accessors, where brackets can follow words directly)
|
|
62
|
+
# Pattern: Start with .word or .[index], then optionally more:
|
|
63
|
+
# - .word (dot followed by word)
|
|
64
|
+
# - [index] (bracket directly after word, no dot)
|
|
65
|
+
# - .[index] (dot followed by bracket)
|
|
66
|
+
path_match = re.match(r'(\.[\w]+|\.\[[^\]]+\])(\.[\w]+|\[[^\]]+\]|\.\[[^\]]+\])*', segment)
|
|
67
|
+
|
|
68
|
+
if path_match:
|
|
69
|
+
path = path_match.group(0).strip()
|
|
70
|
+
|
|
71
|
+
# If the path is immediately followed by a simple fallback (// value),
|
|
72
|
+
# we consider the path complete.
|
|
73
|
+
if re.search(r'\s*//\s*(\[\]|null|\.|\{.*?\})', segment):
|
|
74
|
+
return path
|
|
75
|
+
|
|
76
|
+
# If the path is just a path segment followed by nothing or the end of a complex
|
|
77
|
+
# expression (like .file.content.raw) we return it.
|
|
78
|
+
return path
|
|
79
|
+
|
|
80
|
+
# Default case: No suitable path found after checking all segments
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@contextmanager
|
|
84
|
+
def resync_error_handling() -> Generator[None, None, None]:
|
|
85
|
+
try:
|
|
86
|
+
yield
|
|
87
|
+
except RawObjectValidationException as error:
|
|
88
|
+
err_msg = f"Failed to validate raw data for returned data from resync function, error: {error}"
|
|
89
|
+
logger.exception(err_msg)
|
|
90
|
+
raise OceanAbortException(err_msg) from error
|
|
91
|
+
except StopAsyncIteration:
|
|
92
|
+
raise
|
|
93
|
+
except Exception as error:
|
|
94
|
+
err_msg = f"Failed to execute resync function, error: {error}"
|
|
95
|
+
logger.exception(err_msg)
|
|
96
|
+
raise OceanAbortException(err_msg) from error
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def resync_function_wrapper(
|
|
100
|
+
fn: Callable[[str], Awaitable[RAW_RESULT]], kind: str, items_to_parse: str | None = None
|
|
101
|
+
) -> RAW_RESULT:
|
|
102
|
+
with resync_error_handling():
|
|
103
|
+
results = await fn(kind)
|
|
104
|
+
return validate_result(results)
|
|
105
|
+
|
|
106
|
+
async def handle_items_to_parse(result: RAW_RESULT, items_to_parse_name: str, items_to_parse: str | None = None) -> AsyncGenerator[list[dict[str, Any]], None]:
|
|
107
|
+
delete_target = extract_jq_deletion_path_revised(items_to_parse) or '.'
|
|
108
|
+
jq_expression = f". | del({delete_target})"
|
|
109
|
+
batch_size = ocean.config.yield_items_to_parse_batch_size
|
|
110
|
+
|
|
111
|
+
for item in result:
|
|
112
|
+
lean_item = await ocean.app.integration.entity_processor._search(item, jq_expression)
|
|
113
|
+
items_to_parse_data = await ocean.app.integration.entity_processor._search(item, items_to_parse)
|
|
114
|
+
if not isinstance(items_to_parse_data, list):
|
|
115
|
+
logger.warning(
|
|
116
|
+
f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items_to_parse_data)}."
|
|
117
|
+
f" Skipping..."
|
|
118
|
+
)
|
|
119
|
+
continue
|
|
120
|
+
batch = []
|
|
121
|
+
while len(items_to_parse_data) > 0:
|
|
122
|
+
if (len(batch) >= batch_size):
|
|
123
|
+
yield batch
|
|
124
|
+
batch = []
|
|
125
|
+
merged_item = {**lean_item}
|
|
126
|
+
merged_item[items_to_parse_name] = items_to_parse_data.pop(0)
|
|
127
|
+
batch.append(merged_item)
|
|
128
|
+
if len(batch) > 0:
|
|
129
|
+
yield batch
|
|
130
|
+
|
|
131
|
+
async def resync_generator_wrapper(
|
|
132
|
+
fn: Callable[[str], ASYNC_GENERATOR_RESYNC_TYPE], kind: str, items_to_parse_name: str, items_to_parse: str | None = None
|
|
133
|
+
) -> ASYNC_GENERATOR_RESYNC_TYPE:
|
|
134
|
+
generator = fn(kind)
|
|
135
|
+
errors = []
|
|
136
|
+
try:
|
|
137
|
+
while True:
|
|
138
|
+
try:
|
|
139
|
+
with resync_error_handling():
|
|
140
|
+
result = validate_result(await anext(generator))
|
|
141
|
+
|
|
142
|
+
if items_to_parse:
|
|
143
|
+
async for batch in handle_items_to_parse(result, items_to_parse_name, items_to_parse):
|
|
144
|
+
yield batch
|
|
145
|
+
else:
|
|
146
|
+
yield result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
except OceanAbortException as error:
|
|
150
|
+
errors.append(error)
|
|
151
|
+
ocean.metrics.inc_metric(
|
|
152
|
+
name=MetricType.OBJECT_COUNT_NAME,
|
|
153
|
+
labels=[ocean.metrics.current_resource_kind(), MetricPhase.EXTRACT , MetricPhase.ExtractResult.FAILED],
|
|
154
|
+
value=1
|
|
155
|
+
)
|
|
156
|
+
except StopAsyncIteration:
|
|
157
|
+
if errors:
|
|
158
|
+
raise ExceptionGroup(
|
|
159
|
+
"At least one of the resync generator iterations failed", errors
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def is_resource_supported(
|
|
164
|
+
kind: str, resync_event_mapping: dict[str | None, list[RESYNC_EVENT_LISTENER]]
|
|
165
|
+
) -> bool:
|
|
166
|
+
return bool(resync_event_mapping[kind] or resync_event_mapping[None])
|
|
167
|
+
|
|
168
|
+
def unsupported_kind_response(
|
|
169
|
+
kind: str, available_resync_kinds: list[str]
|
|
170
|
+
) -> tuple[RESYNC_RESULT, list[Exception]]:
|
|
171
|
+
logger.error(f"Kind {kind} is not supported in this integration")
|
|
172
|
+
return [], [KindNotImplementedException(kind, available_resync_kinds)]
|
|
173
|
+
|
|
174
|
+
class ProcessWrapper(multiprocessing.Process):
|
|
175
|
+
def __init__(self, *args, **kwargs):
|
|
176
|
+
super().__init__(*args, **kwargs)
|
|
177
|
+
|
|
178
|
+
async def join_async(self) -> None:
|
|
179
|
+
while self.exitcode is None:
|
|
180
|
+
await asyncio.sleep(2)
|
|
181
|
+
if self.exitcode != 0:
|
|
182
|
+
logger.error(f"Process {self.pid} failed with exit code {self.exitcode}")
|
|
183
|
+
else:
|
|
184
|
+
logger.info(f"Process {self.pid} finished with exit code {self.exitcode}")
|
|
185
|
+
return super().join()
|
|
186
|
+
|
|
187
|
+
def clear_http_client_context() -> None:
|
|
188
|
+
try:
|
|
189
|
+
while _http_client.top is not None:
|
|
190
|
+
_http_client.pop()
|
|
191
|
+
except (RuntimeError, AttributeError):
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
while _port_http_client.top is not None:
|
|
196
|
+
_port_http_client.pop()
|
|
197
|
+
except (RuntimeError, AttributeError):
|
|
198
|
+
pass
|