port-ocean 0.30.3__tar.gz → 0.30.5__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.3 → port_ocean-0.30.5}/PKG-INFO +1 -1
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/entities.py +35 -26
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/utils.py +114 -12
- port_ocean-0.30.5/port_ocean/tests/core/integrations/mixins/test_integration_utils.py +494 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/pyproject.toml +1 -1
- {port_ocean-0.30.3 → port_ocean-0.30.5}/LICENSE.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/README.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.Deb +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Dockerfile.local +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/README.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/entry_local.sh +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cache/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cache/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cache/disk.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cache/errors.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cache/memory.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/auth/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/auth/auth_client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/auth/oauth_client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/authentication.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/actions.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/integrations.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/mixins/organization.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/config/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/config/settings.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/context/event.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/context/metric_resource.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/actions_only.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/event_listener/webhooks_only.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/actions/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/actions/abstract_executor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/actions/execution_manager.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/queue/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/queue/group_queue.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/queue/local_queue.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/webhook/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/live_events.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/models.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/core/utils/utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/debug_cli.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/execution_manager.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/exceptions/webhook_processor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/metric/metric.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/metric/utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/retry.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/helpers/stream.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/ocean.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/py.typed +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/run.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/cache/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/cache/test_disk_cache.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/cache/test_memory_cache.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/oauth/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/clients/test_streaming_wrapper.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/config/test_config.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/conftest.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/defaults/test_initialize.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/actions/test_execution_manager.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/entity_processor/test_jq_input_evaluator.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/test_utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/utils/test_get_port_diff.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/port_client.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/helpers/test_retry.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/test_metric.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/test_metrics_endpoints.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/test_ocean.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/ipc.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.30.3 → port_ocean-0.30.5}/port_ocean/version.py +0 -0
|
@@ -262,34 +262,36 @@ class EntityClientMixin:
|
|
|
262
262
|
}
|
|
263
263
|
error_entities = {error["index"]: error for error in result.get("errors", [])}
|
|
264
264
|
|
|
265
|
+
ocean.metrics.inc_metric(
|
|
266
|
+
name=MetricType.OBJECT_COUNT_NAME,
|
|
267
|
+
labels=[
|
|
268
|
+
ocean.metrics.current_resource_kind(),
|
|
269
|
+
MetricPhase.LOAD,
|
|
270
|
+
MetricPhase.LoadResult.LOADED,
|
|
271
|
+
],
|
|
272
|
+
value=len(successful_entities),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
ocean.metrics.inc_metric(
|
|
276
|
+
name=MetricType.OBJECT_COUNT_NAME,
|
|
277
|
+
labels=[
|
|
278
|
+
ocean.metrics.current_resource_kind(),
|
|
279
|
+
MetricPhase.LOAD,
|
|
280
|
+
MetricPhase.LoadResult.FAILED,
|
|
281
|
+
],
|
|
282
|
+
value=len(error_entities),
|
|
283
|
+
)
|
|
284
|
+
|
|
265
285
|
batch_results: list[tuple[bool | None, Entity]] = []
|
|
266
286
|
for entity_index, original_entity in index_to_entity.items():
|
|
267
287
|
reduced_entity = self._reduce_entity(original_entity)
|
|
268
288
|
if entity_index in successful_entities:
|
|
269
|
-
ocean.metrics.inc_metric(
|
|
270
|
-
name=MetricType.OBJECT_COUNT_NAME,
|
|
271
|
-
labels=[
|
|
272
|
-
ocean.metrics.current_resource_kind(),
|
|
273
|
-
MetricPhase.LOAD,
|
|
274
|
-
MetricPhase.LoadResult.LOADED,
|
|
275
|
-
],
|
|
276
|
-
value=1,
|
|
277
|
-
)
|
|
278
289
|
success_entity = successful_entities[entity_index]
|
|
279
290
|
# Create a copy of the original entity with the new identifier
|
|
280
291
|
updated_entity = reduced_entity.copy()
|
|
281
292
|
updated_entity.identifier = success_entity["identifier"]
|
|
282
293
|
batch_results.append((True, updated_entity))
|
|
283
294
|
elif entity_index in error_entities:
|
|
284
|
-
ocean.metrics.inc_metric(
|
|
285
|
-
name=MetricType.OBJECT_COUNT_NAME,
|
|
286
|
-
labels=[
|
|
287
|
-
ocean.metrics.current_resource_kind(),
|
|
288
|
-
MetricPhase.LOAD,
|
|
289
|
-
MetricPhase.LoadResult.FAILED,
|
|
290
|
-
],
|
|
291
|
-
value=1,
|
|
292
|
-
)
|
|
293
295
|
error = error_entities[entity_index]
|
|
294
296
|
if (
|
|
295
297
|
error.get("identifier") == "unknown"
|
|
@@ -357,16 +359,23 @@ class EntityClientMixin:
|
|
|
357
359
|
blueprint = entities[0].blueprint
|
|
358
360
|
|
|
359
361
|
identifier_counts = Counter((e.blueprint, e.identifier) for e in entities)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
362
|
+
duplicates: dict[tuple[str, str], int] = {
|
|
363
|
+
key: cnt for key, cnt in identifier_counts.items() if cnt > 1
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if duplicates:
|
|
367
|
+
# Total number of *extra* occurrences beyond the first appearance of each entity
|
|
368
|
+
duplicate_count = sum(cnt - 1 for cnt in duplicates.values())
|
|
369
|
+
|
|
370
|
+
# Show up to 5 example (blueprint, identifier) pairs to avoid noisy logs
|
|
371
|
+
duplicate_examples = list(duplicates)[:5]
|
|
363
372
|
|
|
364
|
-
if duplicate_count:
|
|
365
|
-
duplicate_examples = [
|
|
366
|
-
key for key, cnt in identifier_counts.items() if cnt > 1
|
|
367
|
-
][:5]
|
|
368
373
|
logger.warning(
|
|
369
|
-
|
|
374
|
+
"Detected duplicate entities (by blueprint and identifier) that may not be ingested because an identical identifier existed",
|
|
375
|
+
extra={
|
|
376
|
+
"duplicate_examples": duplicate_examples,
|
|
377
|
+
"duplicate_count": duplicate_count,
|
|
378
|
+
},
|
|
370
379
|
)
|
|
371
380
|
|
|
372
381
|
bulk_size = self.calculate_entities_batch_size(entities)
|
|
@@ -9,6 +9,7 @@ import subprocess
|
|
|
9
9
|
import tempfile
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from typing import Any, AsyncGenerator, Awaitable, Callable, Generator, cast
|
|
12
|
+
import copy
|
|
12
13
|
|
|
13
14
|
import ijson
|
|
14
15
|
from loguru import logger
|
|
@@ -124,8 +125,8 @@ async def resync_generator_wrapper(
|
|
|
124
125
|
if items_to_parse:
|
|
125
126
|
for data in result:
|
|
126
127
|
data_path: str | None = None
|
|
127
|
-
if isinstance(data, dict) and data.get("
|
|
128
|
-
content = data
|
|
128
|
+
if isinstance(data, dict) and data.get("__type") == "path":
|
|
129
|
+
content = data.get("file", {}).get("content") if isinstance(data["file"].get("content"), dict) else {}
|
|
129
130
|
data_path = content.get("path", None)
|
|
130
131
|
bulks = get_items_to_parse_bulks(data, data_path, items_to_parse, items_to_parse_name, data.get("__base_jq", ".file.content"))
|
|
131
132
|
async for bulk in bulks:
|
|
@@ -147,6 +148,62 @@ async def resync_generator_wrapper(
|
|
|
147
148
|
"At least one of the resync generator iterations failed", errors
|
|
148
149
|
)
|
|
149
150
|
|
|
151
|
+
def extract_jq_deletion_path_revised(jq_expression: str) -> str | None:
|
|
152
|
+
"""
|
|
153
|
+
Revised function to extract a simple path suitable for del() by analyzing pipe segments.
|
|
154
|
+
"""
|
|
155
|
+
expr = jq_expression.strip()
|
|
156
|
+
|
|
157
|
+
# 1. Handle surrounding parentheses and extract the main chain
|
|
158
|
+
if expr.startswith('('):
|
|
159
|
+
match_paren = re.match(r'\((.*?)\)', expr, re.DOTALL)
|
|
160
|
+
if match_paren:
|
|
161
|
+
chain = match_paren.group(1).strip()
|
|
162
|
+
else:
|
|
163
|
+
return None
|
|
164
|
+
else:
|
|
165
|
+
chain = expr
|
|
166
|
+
|
|
167
|
+
# 2. Split the chain by the main pipe operator (excluding pipes inside quotes or brackets,
|
|
168
|
+
# but for simplicity here, we split naively and check segments)
|
|
169
|
+
segments = chain.split('|')
|
|
170
|
+
|
|
171
|
+
# 3. Analyze each segment for a simple path
|
|
172
|
+
for segment in segments:
|
|
173
|
+
segment = segment.strip()
|
|
174
|
+
|
|
175
|
+
# Ignore variable assignment segments like '. as $root'
|
|
176
|
+
if re.match(r'^\.\s+as\s+\$\w+', segment):
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Ignore identity and variable access like '.' or '$items'
|
|
180
|
+
if segment == '.' or segment.startswith('$'):
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
# Look for the first genuine path accessor (e.g., .key, .[index], .key.nested, .key[0])
|
|
184
|
+
# This regex looks for a starting dot and follows it with path components
|
|
185
|
+
# (alphanumeric keys or bracketed accessors, where brackets can follow words directly)
|
|
186
|
+
# Pattern: Start with .word or .[index], then optionally more:
|
|
187
|
+
# - .word (dot followed by word)
|
|
188
|
+
# - [index] (bracket directly after word, no dot)
|
|
189
|
+
# - .[index] (dot followed by bracket)
|
|
190
|
+
path_match = re.match(r'(\.[\w]+|\.\[[^\]]+\])(\.[\w]+|\[[^\]]+\]|\.\[[^\]]+\])*', segment)
|
|
191
|
+
|
|
192
|
+
if path_match:
|
|
193
|
+
path = path_match.group(0).strip()
|
|
194
|
+
|
|
195
|
+
# If the path is immediately followed by a simple fallback (// value),
|
|
196
|
+
# we consider the path complete.
|
|
197
|
+
if re.search(r'\s*//\s*(\[\]|null|\.|\{.*?\})', segment):
|
|
198
|
+
return path
|
|
199
|
+
|
|
200
|
+
# If the path is just a path segment followed by nothing or the end of a complex
|
|
201
|
+
# expression (like .file.content.raw) we return it.
|
|
202
|
+
return path
|
|
203
|
+
|
|
204
|
+
# Default case: No suitable path found after checking all segments
|
|
205
|
+
return None
|
|
206
|
+
|
|
150
207
|
|
|
151
208
|
def is_resource_supported(
|
|
152
209
|
kind: str, resync_event_mapping: dict[str | None, list[RESYNC_EVENT_LISTENER]]
|
|
@@ -197,6 +254,7 @@ async def get_items_to_parse_bulks(raw_data: dict[Any, Any], data_path: str, ite
|
|
|
197
254
|
temp_output_path = None
|
|
198
255
|
|
|
199
256
|
try:
|
|
257
|
+
is_path_type = True
|
|
200
258
|
# Create secure temporary files
|
|
201
259
|
if not data_path:
|
|
202
260
|
raw_data_serialized = json.dumps(raw_data)
|
|
@@ -204,17 +262,17 @@ async def get_items_to_parse_bulks(raw_data: dict[Any, Any], data_path: str, ite
|
|
|
204
262
|
with open(temp_data_path, "w") as f:
|
|
205
263
|
f.write(raw_data_serialized)
|
|
206
264
|
data_path = temp_data_path
|
|
265
|
+
is_path_type = False
|
|
207
266
|
|
|
208
267
|
temp_output_path = _create_secure_temp_file("_parsed.json")
|
|
209
268
|
|
|
210
|
-
delete_target =
|
|
211
|
-
base_jq_object_string = await _build_base_jq_object_string(raw_data, base_jq, delete_target)
|
|
269
|
+
delete_target = extract_jq_deletion_path_revised(items_to_parse) or '.'
|
|
212
270
|
|
|
213
271
|
# Build jq expression safely
|
|
214
272
|
jq_expression = f""". as $all
|
|
215
273
|
| ($all | {items_to_parse}) as $items
|
|
216
274
|
| $items
|
|
217
|
-
|
|
|
275
|
+
| {_build_mapping_jq_expression(items_to_parse_name, base_jq, delete_target, is_path_type)}"""
|
|
218
276
|
|
|
219
277
|
# Use subprocess with list arguments instead of shell=True
|
|
220
278
|
|
|
@@ -237,7 +295,7 @@ async def get_items_to_parse_bulks(raw_data: dict[Any, Any], data_path: str, ite
|
|
|
237
295
|
with open(temp_output_path, "r") as f:
|
|
238
296
|
events_stream = get_events_as_a_stream(f, 'item', ocean.config.yield_items_to_parse_batch_size)
|
|
239
297
|
for items_bulk in events_stream:
|
|
240
|
-
yield items_bulk
|
|
298
|
+
yield items_bulk if not is_path_type else [merge_raw_data_to_item(item, raw_data) for item in items_bulk]
|
|
241
299
|
|
|
242
300
|
except ValueError as e:
|
|
243
301
|
logger.error(f"Invalid jq expression: {e}")
|
|
@@ -260,13 +318,57 @@ def unsupported_kind_response(
|
|
|
260
318
|
logger.error(f"Kind {kind} is not supported in this integration")
|
|
261
319
|
return [], [KindNotImplementedException(kind, available_resync_kinds)]
|
|
262
320
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
321
|
+
def _build_mapping_jq_expression(items_to_parse_name: str, base_jq: str, delete_target: str, is_path_type: bool = False) -> str:
|
|
322
|
+
if is_path_type:
|
|
323
|
+
return f"map({{{items_to_parse_name}: . }} | {base_jq} = (($all | del({delete_target})) // {{}}))"
|
|
324
|
+
return f"map(($all | del({delete_target})) + {{{items_to_parse_name}: . }})"
|
|
325
|
+
|
|
326
|
+
def merge_raw_data_to_item(item: dict[str, Any], raw_data: dict[str, Any]) -> dict[str, Any]:
|
|
327
|
+
return recursive_dict_merge(raw_data, item)
|
|
328
|
+
|
|
329
|
+
def recursive_dict_merge(d1: dict[Any, Any], d2: dict[Any, Any]) -> dict[Any, Any]:
|
|
330
|
+
"""
|
|
331
|
+
Recursively merges dict d2 into dict d1.
|
|
332
|
+
|
|
333
|
+
If a key exists in both dictionaries:
|
|
334
|
+
1. If the value in d2 is an empty dictionary (e.g., {}), it overwrites the value in d1.
|
|
335
|
+
2. If both values are non-empty dictionaries, they are merged recursively.
|
|
336
|
+
3. Otherwise, the value from d2 overwrites the value from d1.
|
|
337
|
+
|
|
338
|
+
The original dictionaries are not modified (d1 is copied).
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
d1: The base dictionary (will be copied and modified).
|
|
342
|
+
d2: The dictionary to merge into d1.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The merged dictionary.
|
|
346
|
+
"""
|
|
347
|
+
# Start with a copy of d1 to ensure d1 is not mutated
|
|
348
|
+
merged_dict = copy.deepcopy(d1)
|
|
349
|
+
|
|
350
|
+
for key, value in d2.items():
|
|
351
|
+
# Condition to trigger recursive deep merge:
|
|
352
|
+
# 1. Key exists in merged_dict
|
|
353
|
+
# 2. Both values are dictionaries
|
|
354
|
+
# 3. The value from d2 is NOT an empty dictionary ({}).
|
|
355
|
+
# If d2's value is {}, we treat it as an explicit instruction to overwrite/clear.
|
|
356
|
+
is_deep_merge = (
|
|
357
|
+
key in merged_dict and
|
|
358
|
+
isinstance(merged_dict[key], dict) and
|
|
359
|
+
isinstance(value, dict) and
|
|
360
|
+
value != {}
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if is_deep_merge:
|
|
364
|
+
# If both are dictionaries and d2 is not empty, recurse
|
|
365
|
+
merged_dict[key] = recursive_dict_merge(merged_dict[key], value)
|
|
366
|
+
else:
|
|
367
|
+
# Otherwise (new key, non-dict value, or explicit {} overwrite),
|
|
368
|
+
# overwrite the value from d1 with the value from d2
|
|
369
|
+
merged_dict[key] = value
|
|
269
370
|
|
|
371
|
+
return merged_dict
|
|
270
372
|
|
|
271
373
|
def get_events_as_a_stream(
|
|
272
374
|
stream: Any,
|