port-ocean 0.27.9__tar.gz → 0.28.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.27.9 → port_ocean-0.28.0}/PKG-INFO +1 -1
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/authentication.py +2 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/client.py +5 -2
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/integrations.py +1 -1
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/config/settings.py +1 -1
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/async_client.py +7 -8
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/retry.py +162 -72
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/ocean.py +11 -12
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/conftest.py +7 -1
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/mixins/test_live_events.py +23 -13
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +32 -27
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/port_client.py +1 -0
- port_ocean-0.28.0/port_ocean/tests/helpers/test_retry.py +309 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/pyproject.toml +1 -1
- {port_ocean-0.27.9 → port_ocean-0.28.0}/LICENSE.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/README.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.Deb +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.alpine +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.base.builder +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.base.runner +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Dockerfile.local +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/Makefile +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/README.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/entry_local.sh +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/grpcio.sh +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/integrations/_infra/init.sh +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cache/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cache/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cache/disk.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cache/errors.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cache/memory.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/auth/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/auth/auth_client.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/auth/oauth_client.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/entities.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/mixins/organization.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/retry_transport.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/config/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/context/event.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/context/metric_resource.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/event_listener/webhooks_only.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/queue/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/queue/group_queue.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/queue/local_queue.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/webhook/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/live_events.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/integrations/mixins/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/models.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/ocean_types.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/core/utils/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/debug_cli.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/exceptions/webhook_processor.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/metric/metric.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/metric/utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/helpers/stream.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/py.typed +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/run.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/cache/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/cache/test_disk_cache.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/cache/test_memory_cache.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/oauth/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/config/test_config.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/conftest.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/test_utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/fixtures.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/integration.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/log/test_handlers.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/test_metric.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/test_ocean.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/test_smoke.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/utils/test_cache.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/async_iterators.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/ipc.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/queue_utils.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/utils/time.py +0 -0
- {port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/version.py +0 -0
@@ -35,6 +35,7 @@ class PortAuthentication:
|
|
35
35
|
integration_identifier: str,
|
36
36
|
integration_type: str,
|
37
37
|
integration_version: str,
|
38
|
+
ingest_url: str,
|
38
39
|
):
|
39
40
|
self.client = client
|
40
41
|
self.api_url = api_url
|
@@ -43,6 +44,7 @@ class PortAuthentication:
|
|
43
44
|
self.integration_identifier = integration_identifier
|
44
45
|
self.integration_type = integration_type
|
45
46
|
self.integration_version = integration_version
|
47
|
+
self.ingest_url = ingest_url
|
46
48
|
self.last_token_object: TokenResponse | None = None
|
47
49
|
|
48
50
|
async def _get_token(self, client_id: str, client_secret: str) -> TokenResponse:
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
1
3
|
from loguru import logger
|
2
4
|
|
3
5
|
from port_ocean.clients.port.authentication import PortAuthentication
|
@@ -10,11 +12,10 @@ from port_ocean.clients.port.types import (
|
|
10
12
|
KafkaCreds,
|
11
13
|
)
|
12
14
|
from port_ocean.clients.port.utils import (
|
13
|
-
handle_port_status_code,
|
14
15
|
get_internal_http_client,
|
16
|
+
handle_port_status_code,
|
15
17
|
)
|
16
18
|
from port_ocean.exceptions.clients import KafkaCredentialsNotFound
|
17
|
-
from typing import Any
|
18
19
|
|
19
20
|
|
20
21
|
class PortClient(
|
@@ -32,6 +33,7 @@ class PortClient(
|
|
32
33
|
integration_identifier: str,
|
33
34
|
integration_type: str,
|
34
35
|
integration_version: str,
|
36
|
+
ingest_url: str,
|
35
37
|
):
|
36
38
|
self.api_url = f"{base_url}/v1"
|
37
39
|
self.client = get_internal_http_client(self)
|
@@ -43,6 +45,7 @@ class PortClient(
|
|
43
45
|
integration_identifier,
|
44
46
|
integration_type,
|
45
47
|
integration_version,
|
48
|
+
ingest_url,
|
46
49
|
)
|
47
50
|
EntityClientMixin.__init__(self, self.auth, self.client)
|
48
51
|
IntegrationClientMixin.__init__(
|
@@ -296,7 +296,7 @@ class IntegrationClientMixin:
|
|
296
296
|
logger.debug("starting POST raw data request", raw_data=raw_data)
|
297
297
|
headers = await self.auth.headers()
|
298
298
|
response = await self.client.post(
|
299
|
-
f"{self.auth.
|
299
|
+
f"{self.auth.ingest_url}/lakehouse/integration-type/{self.auth.integration_type}/integration/{self.integration_identifier}/sync/{sync_id}/kind/{kind}/items",
|
300
300
|
headers=headers,
|
301
301
|
json={
|
302
302
|
"items": raw_data,
|
@@ -9,7 +9,6 @@ from pydantic.main import BaseModel
|
|
9
9
|
|
10
10
|
from port_ocean.config.base import BaseOceanModel, BaseOceanSettings
|
11
11
|
from port_ocean.core.event_listener import EventListenerSettingsType
|
12
|
-
|
13
12
|
from port_ocean.core.models import (
|
14
13
|
CachingStorageMode,
|
15
14
|
CreatePortResourcesOrigin,
|
@@ -47,6 +46,7 @@ class PortSettings(BaseOceanModel, extra=Extra.allow):
|
|
47
46
|
client_secret: str = Field(..., sensitive=True)
|
48
47
|
base_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://api.getport.io")
|
49
48
|
port_app_config_cache_ttl: int = 60
|
49
|
+
ingest_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://ingest.getport.io")
|
50
50
|
|
51
51
|
|
52
52
|
class IntegrationSettings(BaseOceanModel, extra=Extra.allow):
|
@@ -3,7 +3,7 @@ from typing import Any, Type
|
|
3
3
|
import httpx
|
4
4
|
from loguru import logger
|
5
5
|
|
6
|
-
from port_ocean.helpers.retry import RetryTransport
|
6
|
+
from port_ocean.helpers.retry import RetryTransport, RetryConfig
|
7
7
|
from port_ocean.helpers.stream import Stream
|
8
8
|
|
9
9
|
|
@@ -18,10 +18,12 @@ class OceanAsyncClient(httpx.AsyncClient):
|
|
18
18
|
self,
|
19
19
|
transport_class: Type[RetryTransport] = RetryTransport,
|
20
20
|
transport_kwargs: dict[str, Any] | None = None,
|
21
|
+
retry_config: RetryConfig | None = None,
|
21
22
|
**kwargs: Any,
|
22
23
|
):
|
23
24
|
self._transport_kwargs = transport_kwargs
|
24
25
|
self._transport_class = transport_class
|
26
|
+
self._retry_config = retry_config
|
25
27
|
super().__init__(**kwargs)
|
26
28
|
|
27
29
|
def _init_transport( # type: ignore[override]
|
@@ -33,9 +35,8 @@ class OceanAsyncClient(httpx.AsyncClient):
|
|
33
35
|
return super()._init_transport(transport=transport, **kwargs)
|
34
36
|
|
35
37
|
return self._transport_class(
|
36
|
-
wrapped_transport=httpx.AsyncHTTPTransport(
|
37
|
-
|
38
|
-
),
|
38
|
+
wrapped_transport=httpx.AsyncHTTPTransport(**kwargs),
|
39
|
+
retry_config=self._retry_config,
|
39
40
|
logger=logger,
|
40
41
|
**(self._transport_kwargs or {}),
|
41
42
|
)
|
@@ -44,10 +45,8 @@ class OceanAsyncClient(httpx.AsyncClient):
|
|
44
45
|
self, proxy: httpx.Proxy, **kwargs: Any
|
45
46
|
) -> httpx.AsyncBaseTransport:
|
46
47
|
return self._transport_class(
|
47
|
-
wrapped_transport=httpx.AsyncHTTPTransport(
|
48
|
-
|
49
|
-
**kwargs,
|
50
|
-
),
|
48
|
+
wrapped_transport=httpx.AsyncHTTPTransport(proxy=proxy, **kwargs),
|
49
|
+
retry_config=self._retry_config,
|
51
50
|
logger=logger,
|
52
51
|
**(self._transport_kwargs or {}),
|
53
52
|
)
|
@@ -4,12 +4,24 @@ import time
|
|
4
4
|
from datetime import datetime
|
5
5
|
from functools import partial
|
6
6
|
from http import HTTPStatus
|
7
|
-
from typing import
|
7
|
+
from typing import (
|
8
|
+
Any,
|
9
|
+
Callable,
|
10
|
+
Coroutine,
|
11
|
+
Iterable,
|
12
|
+
Mapping,
|
13
|
+
Union,
|
14
|
+
cast,
|
15
|
+
Optional,
|
16
|
+
List,
|
17
|
+
)
|
8
18
|
import httpx
|
9
19
|
from dateutil.parser import isoparse
|
10
20
|
import logging
|
11
21
|
|
22
|
+
MAX_BACKOFF_WAIT_IN_SECONDS = 60
|
12
23
|
_ON_RETRY_CALLBACK: Callable[[httpx.Request], httpx.Request] | None = None
|
24
|
+
_RETRY_CONFIG_CALLBACK: Callable[[], "RetryConfig"] | None = None
|
13
25
|
|
14
26
|
|
15
27
|
def register_on_retry_callback(
|
@@ -19,6 +31,92 @@ def register_on_retry_callback(
|
|
19
31
|
_ON_RETRY_CALLBACK = _on_retry_callback
|
20
32
|
|
21
33
|
|
34
|
+
def register_retry_config_callback(
|
35
|
+
retry_config_callback: Callable[[], "RetryConfig"]
|
36
|
+
) -> None:
|
37
|
+
"""Register a callback function that returns a RetryConfig instance.
|
38
|
+
|
39
|
+
The callback will be called when a RetryTransport needs to be created.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
retry_config_callback: A function that returns a RetryConfig instance
|
43
|
+
"""
|
44
|
+
global _RETRY_CONFIG_CALLBACK
|
45
|
+
_RETRY_CONFIG_CALLBACK = retry_config_callback
|
46
|
+
|
47
|
+
|
48
|
+
class RetryConfig:
|
49
|
+
"""Configuration class for retry behavior that can be customized per integration."""
|
50
|
+
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
max_attempts: int = 10,
|
54
|
+
max_backoff_wait: float = MAX_BACKOFF_WAIT_IN_SECONDS,
|
55
|
+
base_delay: float = 0.1,
|
56
|
+
jitter_ratio: float = 0.1,
|
57
|
+
respect_retry_after_header: bool = True,
|
58
|
+
retryable_methods: Optional[Iterable[str]] = None,
|
59
|
+
retry_status_codes: Optional[Iterable[int]] = None,
|
60
|
+
retry_after_headers: Optional[List[str]] = None,
|
61
|
+
additional_retry_status_codes: Optional[Iterable[int]] = None,
|
62
|
+
):
|
63
|
+
"""
|
64
|
+
Initialize retry configuration.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
max_attempts: Maximum number of retry attempts
|
68
|
+
max_backoff_wait: Maximum backoff wait time in seconds
|
69
|
+
base_delay: Base delay for exponential backoff
|
70
|
+
jitter_ratio: Jitter ratio for backoff (0-0.5)
|
71
|
+
respect_retry_after_header: Whether to respect Retry-After header
|
72
|
+
retryable_methods: HTTP methods that can be retried (overrides defaults if provided)
|
73
|
+
retry_status_codes: DEPRECATED - use additional_retry_status_codes instead
|
74
|
+
retry_after_headers: Custom headers to check for retry timing (e.g., ['X-RateLimit-Reset', 'Retry-After'])
|
75
|
+
additional_retry_status_codes: Additional status codes to retry (extends system defaults)
|
76
|
+
"""
|
77
|
+
self.max_attempts = max_attempts
|
78
|
+
self.max_backoff_wait = max_backoff_wait
|
79
|
+
self.base_delay = base_delay
|
80
|
+
self.jitter_ratio = jitter_ratio
|
81
|
+
self.respect_retry_after_header = respect_retry_after_header
|
82
|
+
|
83
|
+
# Default retryable methods - always include these unless explicitly overridden
|
84
|
+
default_methods = frozenset(
|
85
|
+
["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]
|
86
|
+
)
|
87
|
+
self.retryable_methods = (
|
88
|
+
frozenset(retryable_methods) if retryable_methods else default_methods
|
89
|
+
)
|
90
|
+
|
91
|
+
# Default retry status codes - always include these for system reliability
|
92
|
+
default_status_codes = frozenset(
|
93
|
+
[
|
94
|
+
HTTPStatus.TOO_MANY_REQUESTS,
|
95
|
+
HTTPStatus.BAD_GATEWAY,
|
96
|
+
HTTPStatus.SERVICE_UNAVAILABLE,
|
97
|
+
HTTPStatus.GATEWAY_TIMEOUT,
|
98
|
+
HTTPStatus.UNAUTHORIZED,
|
99
|
+
HTTPStatus.BAD_REQUEST,
|
100
|
+
]
|
101
|
+
)
|
102
|
+
|
103
|
+
# Additional status codes to retry (extends defaults)
|
104
|
+
additional_codes = (
|
105
|
+
frozenset(additional_retry_status_codes)
|
106
|
+
if additional_retry_status_codes
|
107
|
+
else frozenset()
|
108
|
+
)
|
109
|
+
|
110
|
+
# Combine defaults with additional codes for extensibility
|
111
|
+
self.retry_status_codes = default_status_codes | additional_codes
|
112
|
+
self.retry_after_headers = retry_after_headers or ["Retry-After"]
|
113
|
+
|
114
|
+
if jitter_ratio < 0 or jitter_ratio > 0.5:
|
115
|
+
raise ValueError(
|
116
|
+
f"Jitter ratio should be between 0 and 0.5, actual {jitter_ratio}"
|
117
|
+
)
|
118
|
+
|
119
|
+
|
22
120
|
# Adapted from https://github.com/encode/httpx/issues/108#issuecomment-1434439481
|
23
121
|
class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
24
122
|
"""
|
@@ -41,32 +139,16 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
41
139
|
["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"].
|
42
140
|
retry_status_codes (Iterable[int], optional): The HTTP status codes that can be retried. Defaults to
|
43
141
|
[429, 502, 503, 504].
|
142
|
+
retry_config (RetryConfig, optional): Configuration for retry behavior. If not provided, uses defaults.
|
143
|
+
logger (Any, optional): The logger to use for logging retries.
|
44
144
|
|
45
145
|
Attributes:
|
46
146
|
_wrapped_transport (Union[httpx.BaseTransport, httpx.AsyncBaseTransport]): The underlying HTTP transport
|
47
147
|
being wrapped.
|
48
|
-
|
49
|
-
|
50
|
-
_respect_retry_after_header (bool): Whether to respect the Retry-After header in HTTP responses.
|
51
|
-
_retryable_methods (frozenset): The HTTP methods that can be retried.
|
52
|
-
_retry_status_codes (frozenset): The HTTP status codes that can be retried.
|
53
|
-
_jitter_ratio (float): The amount of jitter to add to the backoff time.
|
54
|
-
_max_backoff_wait (float): The maximum time to wait between retries in seconds.
|
148
|
+
_retry_config (RetryConfig): The retry configuration object.
|
149
|
+
_logger (Any): The logger to use for logging retries.
|
55
150
|
"""
|
56
151
|
|
57
|
-
RETRYABLE_METHODS = frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
|
58
|
-
RETRYABLE_STATUS_CODES = frozenset(
|
59
|
-
[
|
60
|
-
HTTPStatus.TOO_MANY_REQUESTS,
|
61
|
-
HTTPStatus.BAD_GATEWAY,
|
62
|
-
HTTPStatus.SERVICE_UNAVAILABLE,
|
63
|
-
HTTPStatus.GATEWAY_TIMEOUT,
|
64
|
-
HTTPStatus.UNAUTHORIZED,
|
65
|
-
HTTPStatus.BAD_REQUEST,
|
66
|
-
]
|
67
|
-
)
|
68
|
-
MAX_BACKOFF_WAIT_IN_SECONDS = 60
|
69
|
-
|
70
152
|
def __init__(
|
71
153
|
self,
|
72
154
|
wrapped_transport: Union[httpx.BaseTransport, httpx.AsyncBaseTransport],
|
@@ -77,6 +159,7 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
77
159
|
respect_retry_after_header: bool = True,
|
78
160
|
retryable_methods: Iterable[str] | None = None,
|
79
161
|
retry_status_codes: Iterable[int] | None = None,
|
162
|
+
retry_config: Optional[RetryConfig] = None,
|
80
163
|
logger: Any | None = None,
|
81
164
|
) -> None:
|
82
165
|
"""
|
@@ -106,29 +189,27 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
106
189
|
retry_status_codes (Iterable[int], optional):
|
107
190
|
The HTTP status codes that can be retried.
|
108
191
|
Defaults to [429, 502, 503, 504].
|
192
|
+
retry_config (RetryConfig, optional):
|
193
|
+
Configuration for retry behavior. If not provided, uses default configuration.
|
109
194
|
logger (Any): The logger to use for logging retries.
|
110
195
|
"""
|
111
196
|
self._wrapped_transport = wrapped_transport
|
112
|
-
|
113
|
-
|
114
|
-
|
197
|
+
|
198
|
+
if retry_config is not None:
|
199
|
+
self._retry_config = retry_config
|
200
|
+
elif _RETRY_CONFIG_CALLBACK is not None:
|
201
|
+
self._retry_config = _RETRY_CONFIG_CALLBACK()
|
202
|
+
else:
|
203
|
+
self._retry_config = RetryConfig(
|
204
|
+
max_attempts=max_attempts,
|
205
|
+
max_backoff_wait=max_backoff_wait,
|
206
|
+
base_delay=base_delay,
|
207
|
+
jitter_ratio=jitter_ratio,
|
208
|
+
respect_retry_after_header=respect_retry_after_header,
|
209
|
+
retryable_methods=retryable_methods,
|
210
|
+
retry_status_codes=retry_status_codes,
|
115
211
|
)
|
116
212
|
|
117
|
-
self._max_attempts = max_attempts
|
118
|
-
self._base_delay = base_delay
|
119
|
-
self._respect_retry_after_header = respect_retry_after_header
|
120
|
-
self._retryable_methods = (
|
121
|
-
frozenset(retryable_methods)
|
122
|
-
if retryable_methods
|
123
|
-
else self.RETRYABLE_METHODS
|
124
|
-
)
|
125
|
-
self._retry_status_codes = (
|
126
|
-
frozenset(retry_status_codes)
|
127
|
-
if retry_status_codes
|
128
|
-
else self.RETRYABLE_STATUS_CODES
|
129
|
-
)
|
130
|
-
self._jitter_ratio = jitter_ratio
|
131
|
-
self._max_backoff_wait = max_backoff_wait
|
132
213
|
self._logger = logger
|
133
214
|
|
134
215
|
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
@@ -206,12 +287,13 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
206
287
|
transport.close()
|
207
288
|
|
208
289
|
def _is_retryable_method(self, request: httpx.Request) -> bool:
|
209
|
-
return
|
210
|
-
|
290
|
+
return (
|
291
|
+
request.method in self._retry_config.retryable_methods
|
292
|
+
or request.extensions.get("retryable", False)
|
211
293
|
)
|
212
294
|
|
213
295
|
def _should_retry(self, response: httpx.Response) -> bool:
|
214
|
-
return response.status_code in self.
|
296
|
+
return response.status_code in self._retry_config.retry_status_codes
|
215
297
|
|
216
298
|
def _log_error(
|
217
299
|
self,
|
@@ -314,46 +396,54 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
314
396
|
)
|
315
397
|
|
316
398
|
async def _should_retry_async(self, response: httpx.Response) -> bool:
|
317
|
-
return response.status_code in self.
|
399
|
+
return response.status_code in self._retry_config.retry_status_codes
|
318
400
|
|
319
401
|
def _calculate_sleep(
|
320
402
|
self, attempts_made: int, headers: Union[httpx.Headers, Mapping[str, str]]
|
321
403
|
) -> float:
|
322
|
-
# Retry-After
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
#
|
331
|
-
|
332
|
-
|
333
|
-
if retry_after_header.isdigit():
|
334
|
-
return float(retry_after_header)
|
335
|
-
|
336
|
-
try:
|
337
|
-
parsed_date = isoparse(
|
338
|
-
retry_after_header
|
339
|
-
).astimezone() # converts to local time
|
340
|
-
diff = (parsed_date - datetime.now().astimezone()).total_seconds()
|
341
|
-
if diff > 0:
|
342
|
-
return min(diff, self._max_backoff_wait)
|
343
|
-
except ValueError:
|
344
|
-
pass
|
345
|
-
|
346
|
-
backoff = self._base_delay * (2 ** (attempts_made - 1))
|
347
|
-
jitter = (backoff * self._jitter_ratio) * random.choice([1, -1])
|
404
|
+
# Check custom retry headers first, then fall back to Retry-After
|
405
|
+
if self._retry_config.respect_retry_after_header:
|
406
|
+
for header_name in self._retry_config.retry_after_headers:
|
407
|
+
if header_value := (headers.get(header_name) or "").strip():
|
408
|
+
sleep_time = self._parse_retry_header(header_value)
|
409
|
+
if sleep_time is not None:
|
410
|
+
return min(sleep_time, self._retry_config.max_backoff_wait)
|
411
|
+
|
412
|
+
# Fall back to exponential backoff
|
413
|
+
backoff = self._retry_config.base_delay * (2 ** (attempts_made - 1))
|
414
|
+
jitter = (backoff * self._retry_config.jitter_ratio) * random.choice([1, -1])
|
348
415
|
total_backoff = backoff + jitter
|
349
|
-
return min(total_backoff, self.
|
416
|
+
return min(total_backoff, self._retry_config.max_backoff_wait)
|
417
|
+
|
418
|
+
def _parse_retry_header(self, header_value: str) -> Optional[float]:
|
419
|
+
"""Parse retry header value and return sleep time in seconds.
|
420
|
+
|
421
|
+
Args:
|
422
|
+
header_value: The header value to parse (e.g., "30", "2023-12-01T12:00:00Z")
|
423
|
+
|
424
|
+
Returns:
|
425
|
+
Sleep time in seconds if parsing succeeds, None if the header value cannot be parsed
|
426
|
+
"""
|
427
|
+
if header_value.isdigit():
|
428
|
+
return float(header_value)
|
429
|
+
|
430
|
+
try:
|
431
|
+
# Try to parse as ISO date (common for rate limit headers like X-RateLimit-Reset)
|
432
|
+
parsed_date = isoparse(header_value).astimezone()
|
433
|
+
diff = (parsed_date - datetime.now().astimezone()).total_seconds()
|
434
|
+
if diff > 0:
|
435
|
+
return diff
|
436
|
+
except ValueError:
|
437
|
+
pass
|
438
|
+
|
439
|
+
return None
|
350
440
|
|
351
441
|
async def _retry_operation_async(
|
352
442
|
self,
|
353
443
|
request: httpx.Request,
|
354
444
|
send_method: Callable[..., Coroutine[Any, Any, httpx.Response]],
|
355
445
|
) -> httpx.Response:
|
356
|
-
remaining_attempts = self.
|
446
|
+
remaining_attempts = self._retry_config.max_attempts
|
357
447
|
attempts_made = 0
|
358
448
|
response: httpx.Response | None = None
|
359
449
|
error: Exception | None = None
|
@@ -403,7 +493,7 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
|
|
403
493
|
request: httpx.Request,
|
404
494
|
send_method: Callable[..., httpx.Response],
|
405
495
|
) -> httpx.Response:
|
406
|
-
remaining_attempts = self.
|
496
|
+
remaining_attempts = self._retry_config.max_attempts
|
407
497
|
attempts_made = 0
|
408
498
|
response: httpx.Response | None = None
|
409
499
|
error: Exception | None = None
|
@@ -1,21 +1,18 @@
|
|
1
1
|
import asyncio
|
2
2
|
import sys
|
3
|
-
from contextlib import asynccontextmanager
|
4
3
|
import threading
|
4
|
+
from contextlib import asynccontextmanager
|
5
5
|
from typing import Any, AsyncIterator, Callable, Dict, Type
|
6
6
|
|
7
|
-
from
|
8
|
-
from port_ocean.cache.disk import DiskCacheProvider
|
9
|
-
from port_ocean.cache.memory import InMemoryCacheProvider
|
10
|
-
from port_ocean.core.models import ProcessExecutionMode
|
11
|
-
import port_ocean.helpers.metric.metric
|
12
|
-
|
13
|
-
from fastapi import FastAPI, APIRouter
|
14
|
-
|
7
|
+
from fastapi import APIRouter, FastAPI
|
15
8
|
from loguru import logger
|
16
9
|
from pydantic import BaseModel
|
17
10
|
from starlette.types import Receive, Scope, Send
|
18
11
|
|
12
|
+
import port_ocean.helpers.metric.metric
|
13
|
+
from port_ocean.cache.base import CacheProvider
|
14
|
+
from port_ocean.cache.disk import DiskCacheProvider
|
15
|
+
from port_ocean.cache.memory import InMemoryCacheProvider
|
19
16
|
from port_ocean.clients.port.client import PortClient
|
20
17
|
from port_ocean.config.settings import (
|
21
18
|
IntegrationConfiguration,
|
@@ -26,16 +23,17 @@ from port_ocean.context.ocean import (
|
|
26
23
|
ocean,
|
27
24
|
)
|
28
25
|
from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
|
26
|
+
from port_ocean.core.handlers.webhook.processor_manager import (
|
27
|
+
LiveEventsProcessorManager,
|
28
|
+
)
|
29
29
|
from port_ocean.core.integrations.base import BaseIntegration
|
30
|
+
from port_ocean.core.models import ProcessExecutionMode
|
30
31
|
from port_ocean.log.sensetive import sensitive_log_filter
|
31
32
|
from port_ocean.middlewares import request_handler
|
32
33
|
from port_ocean.utils.misc import IntegrationStateStatus
|
33
34
|
from port_ocean.utils.repeat import repeat_every
|
34
35
|
from port_ocean.utils.signal import signal_handler
|
35
36
|
from port_ocean.version import __integration_version__
|
36
|
-
from port_ocean.core.handlers.webhook.processor_manager import (
|
37
|
-
LiveEventsProcessorManager,
|
38
|
-
)
|
39
37
|
|
40
38
|
|
41
39
|
class Ocean:
|
@@ -69,6 +67,7 @@ class Ocean:
|
|
69
67
|
integration_identifier=self.config.integration.identifier,
|
70
68
|
integration_type=self.config.integration.type,
|
71
69
|
integration_version=__integration_version__,
|
70
|
+
ingest_url=self.config.port.ingest_url,
|
72
71
|
)
|
73
72
|
self.cache_provider: CacheProvider = self._get_caching_provider()
|
74
73
|
self.process_execution_mode: ProcessExecutionMode = (
|
@@ -96,7 +96,13 @@ def mock_http_client() -> MagicMock:
|
|
96
96
|
@pytest.fixture
|
97
97
|
def mock_port_client(mock_http_client: MagicMock) -> PortClient:
|
98
98
|
mock_port_client = PortClient(
|
99
|
-
MagicMock(),
|
99
|
+
MagicMock(),
|
100
|
+
MagicMock(),
|
101
|
+
MagicMock(),
|
102
|
+
MagicMock(),
|
103
|
+
MagicMock(),
|
104
|
+
MagicMock(),
|
105
|
+
MagicMock(),
|
100
106
|
)
|
101
107
|
mock_port_client.auth = AsyncMock()
|
102
108
|
mock_port_client.auth.headers = AsyncMock(
|
{port_ocean-0.27.9 → port_ocean-0.28.0}/port_ocean/tests/core/handlers/mixins/test_live_events.py
RENAMED
@@ -1,7 +1,9 @@
|
|
1
1
|
from typing import Any
|
2
|
-
from httpx import Response
|
3
|
-
import pytest
|
4
2
|
from unittest.mock import AsyncMock, MagicMock, patch
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
from httpx import Response
|
6
|
+
|
5
7
|
from port_ocean.clients.port.client import PortClient
|
6
8
|
from port_ocean.clients.port.types import UserAgentType
|
7
9
|
from port_ocean.context.ocean import PortOceanContext
|
@@ -11,8 +13,6 @@ from port_ocean.core.handlers.entities_state_applier.port.applier import (
|
|
11
13
|
from port_ocean.core.handlers.entity_processor.jq_entity_processor import (
|
12
14
|
JQEntityProcessor,
|
13
15
|
)
|
14
|
-
from port_ocean.core.handlers.webhook.webhook_event import WebhookEventRawResults
|
15
|
-
from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
|
16
16
|
from port_ocean.core.handlers.port_app_config.models import (
|
17
17
|
EntityMapping,
|
18
18
|
MappingsConfig,
|
@@ -21,6 +21,8 @@ from port_ocean.core.handlers.port_app_config.models import (
|
|
21
21
|
ResourceConfig,
|
22
22
|
Selector,
|
23
23
|
)
|
24
|
+
from port_ocean.core.handlers.webhook.webhook_event import WebhookEventRawResults
|
25
|
+
from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
|
24
26
|
from port_ocean.core.models import Entity
|
25
27
|
from port_ocean.core.ocean_types import CalculationResult, EntitySelectorDiff
|
26
28
|
from port_ocean.ocean import Ocean
|
@@ -253,7 +255,13 @@ def mock_port_app_config_handler(
|
|
253
255
|
@pytest.fixture
|
254
256
|
def mock_port_client(mock_http_client: MagicMock) -> PortClient:
|
255
257
|
mock_port_client = PortClient(
|
256
|
-
MagicMock(),
|
258
|
+
MagicMock(),
|
259
|
+
MagicMock(),
|
260
|
+
MagicMock(),
|
261
|
+
MagicMock(),
|
262
|
+
MagicMock(),
|
263
|
+
MagicMock(),
|
264
|
+
MagicMock(),
|
257
265
|
)
|
258
266
|
mock_port_client.auth = AsyncMock()
|
259
267
|
mock_port_client.auth.headers = AsyncMock(
|
@@ -340,10 +348,11 @@ async def test_parse_raw_event_results_to_entities_creation(
|
|
340
348
|
calculation_result
|
341
349
|
)
|
342
350
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
351
|
+
(
|
352
|
+
entities_to_create,
|
353
|
+
entities_to_delete,
|
354
|
+
) = await mock_live_events_mixin._parse_raw_event_results_to_entities(
|
355
|
+
[one_webhook_event_raw_results_for_creation]
|
347
356
|
)
|
348
357
|
|
349
358
|
assert entities_to_create == [entity]
|
@@ -367,10 +376,11 @@ async def test_parse_raw_event_results_to_entities_deletion(
|
|
367
376
|
calculation_result
|
368
377
|
)
|
369
378
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
379
|
+
(
|
380
|
+
entities_to_create,
|
381
|
+
entities_to_delete,
|
382
|
+
) = await mock_live_events_mixin._parse_raw_event_results_to_entities(
|
383
|
+
[one_webhook_event_raw_results_for_deletion]
|
374
384
|
)
|
375
385
|
|
376
386
|
assert entities_to_create == []
|