port-ocean 0.20.3__tar.gz → 0.21.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of port-ocean might be problematic. Click here for more details.

Files changed (191) hide show
  1. {port_ocean-0.20.3 → port_ocean-0.21.0}/PKG-INFO +1 -1
  2. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +1 -1
  3. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/context/event.py +7 -2
  4. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/context/ocean.py +37 -1
  5. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +2 -2
  6. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/webhook/processor_manager.py +16 -12
  7. port_ocean-0.21.0/port_ocean/core/integrations/mixins/events.py +71 -0
  8. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/sync_raw.py +14 -1
  9. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/ocean_types.py +5 -0
  10. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +174 -0
  11. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +2 -2
  12. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +174 -36
  13. {port_ocean-0.20.3 → port_ocean-0.21.0}/pyproject.toml +1 -1
  14. port_ocean-0.20.3/port_ocean/core/integrations/mixins/events.py +0 -67
  15. {port_ocean-0.20.3 → port_ocean-0.21.0}/LICENSE.md +0 -0
  16. {port_ocean-0.20.3 → port_ocean-0.21.0}/README.md +0 -0
  17. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Dockerfile.Deb +0 -0
  18. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Dockerfile.alpine +0 -0
  19. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Dockerfile.base.builder +0 -0
  20. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Dockerfile.base.runner +0 -0
  21. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
  22. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/Makefile +0 -0
  23. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/grpcio.sh +0 -0
  24. {port_ocean-0.20.3 → port_ocean-0.21.0}/integrations/_infra/init.sh +0 -0
  25. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/__init__.py +0 -0
  26. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/bootstrap.py +0 -0
  27. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/__init__.py +0 -0
  28. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cli.py +0 -0
  29. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/__init__.py +0 -0
  30. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  31. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
  32. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
  33. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/defaults/group.py +0 -0
  34. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/list_integrations.py +0 -0
  35. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/main.py +0 -0
  36. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/new.py +0 -0
  37. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/pull.py +0 -0
  38. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/sail.py +0 -0
  39. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/commands/version.py +0 -0
  40. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  41. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  42. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  43. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  44. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  45. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  46. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  47. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  48. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  49. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  50. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  51. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  52. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  53. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  54. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  55. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  56. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  57. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  58. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  59. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  60. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/cli/utils.py +0 -0
  61. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/__init__.py +0 -0
  62. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/auth/__init__.py +0 -0
  63. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/auth/auth_client.py +0 -0
  64. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/auth/oauth_client.py +0 -0
  65. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/__init__.py +0 -0
  66. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/authentication.py +0 -0
  67. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/client.py +0 -0
  68. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
  69. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  70. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/entities.py +0 -0
  71. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
  72. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
  73. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/mixins/organization.py +0 -0
  74. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/retry_transport.py +0 -0
  75. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/types.py +0 -0
  76. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/clients/port/utils.py +0 -0
  77. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/config/__init__.py +0 -0
  78. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/config/base.py +0 -0
  79. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/config/dynamic.py +0 -0
  80. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/config/settings.py +0 -0
  81. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/consumers/__init__.py +0 -0
  82. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/consumers/kafka_consumer.py +0 -0
  83. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/context/__init__.py +0 -0
  84. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/context/resource.py +0 -0
  85. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/__init__.py +0 -0
  86. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/defaults/__init__.py +0 -0
  87. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/defaults/clean.py +0 -0
  88. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/defaults/common.py +0 -0
  89. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/defaults/initialize.py +0 -0
  90. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/__init__.py +0 -0
  91. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/base.py +0 -0
  92. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/factory.py +0 -0
  93. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/http.py +0 -0
  94. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/kafka.py +0 -0
  95. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/once.py +0 -0
  96. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/polling.py +0 -0
  97. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/event_listener/webhooks_only.py +0 -0
  98. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/__init__.py +0 -0
  99. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/base.py +0 -0
  100. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  101. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  102. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  103. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  104. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  105. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  106. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  107. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  108. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  109. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  110. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  111. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  112. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  113. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/queue/__init__.py +0 -0
  114. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
  115. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/queue/local_queue.py +0 -0
  116. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  117. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  118. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/webhook/__init__.py +0 -0
  119. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
  120. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/__init__.py +0 -0
  121. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/base.py +0 -0
  122. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  123. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
  124. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/live_events.py +0 -0
  125. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
  126. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/integrations/mixins/utils.py +0 -0
  127. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/models.py +0 -0
  128. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  129. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/core/utils/utils.py +0 -0
  130. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/debug_cli.py +0 -0
  131. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/__init__.py +0 -0
  132. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/api.py +0 -0
  133. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/base.py +0 -0
  134. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/clients.py +0 -0
  135. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/context.py +0 -0
  136. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/core.py +0 -0
  137. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/port_defaults.py +0 -0
  138. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/utils.py +0 -0
  139. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/exceptions/webhook_processor.py +0 -0
  140. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/helpers/__init__.py +0 -0
  141. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/helpers/async_client.py +0 -0
  142. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/helpers/retry.py +0 -0
  143. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/log/__init__.py +0 -0
  144. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/log/handlers.py +0 -0
  145. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/log/logger_setup.py +0 -0
  146. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/log/sensetive.py +0 -0
  147. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/middlewares.py +0 -0
  148. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/ocean.py +0 -0
  149. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/py.typed +0 -0
  150. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/run.py +0 -0
  151. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/sonar-project.properties +0 -0
  152. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/__init__.py +0 -0
  153. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/clients/__init__.py +0 -0
  154. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/clients/oauth/__init__.py +0 -0
  155. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
  156. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  157. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
  158. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/conftest.py +0 -0
  159. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
  160. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
  161. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
  162. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
  163. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
  164. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
  165. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
  166. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
  167. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/test_utils.py +0 -0
  168. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  169. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
  170. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/__init__.py +0 -0
  171. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  172. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/fixtures.py +0 -0
  173. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/integration.py +0 -0
  174. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
  175. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/port_client.py +0 -0
  176. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
  177. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/log/test_handlers.py +0 -0
  178. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/test_ocean.py +0 -0
  179. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/test_smoke.py +0 -0
  180. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  181. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/tests/utils/test_cache.py +0 -0
  182. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/__init__.py +0 -0
  183. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/async_http.py +0 -0
  184. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/async_iterators.py +0 -0
  185. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/cache.py +0 -0
  186. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/misc.py +0 -0
  187. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/queue_utils.py +0 -0
  188. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/repeat.py +0 -0
  189. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/signal.py +0 -0
  190. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/utils/time.py +0 -0
  191. {port_ocean-0.20.3 → port_ocean-0.21.0}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.20.3
3
+ Version: 0.21.0
4
4
  Summary: Port Ocean is a CLI tool for managing your Port projects.
5
5
  Home-page: https://app.getport.io
6
6
  Keywords: ocean,port-ocean,port
@@ -8,7 +8,7 @@ def handle_private_integration_flags():
8
8
  )
9
9
  root_dir = os.path.join("{{ cookiecutter._repo_dir }}", "../../../")
10
10
  infra_make_file = os.path.join(root_dir, "integrations/_infra/Makefile")
11
- infra_dockerfile = os.path.join(root_dir, "integrations/_infra/Dockerfile.deb")
11
+ infra_dockerfile = os.path.join(root_dir, "integrations/_infra/Dockerfile.Deb")
12
12
  infra_dockerignore = os.path.join(
13
13
  root_dir, "integrations/_infra/Dockerfile.dockerignore"
14
14
  )
@@ -182,9 +182,14 @@ async def event_context(
182
182
  f"Skipping resync due to empty mapping: {str(e)}", exc_info=True
183
183
  )
184
184
  raise
185
- except Exception as e:
185
+ except BaseException as e:
186
186
  success = False
187
- logger.error(f"Event failed with error: {str(e)}", exc_info=True)
187
+ if isinstance(e, KeyboardInterrupt):
188
+ logger.warning("Operation interrupted by user", exc_info=True)
189
+ elif isinstance(e, asyncio.CancelledError):
190
+ logger.warning("Operation was cancelled", exc_info=True)
191
+ else:
192
+ logger.error(f"Event failed with error: {repr(e)}", exc_info=True)
188
193
  raise
189
194
  else:
190
195
  success = True
@@ -12,6 +12,8 @@ from port_ocean.core.ocean_types import (
12
12
  START_EVENT_LISTENER,
13
13
  RawEntityDiff,
14
14
  EntityDiff,
15
+ BEFORE_RESYNC_EVENT_LISTENER,
16
+ AFTER_RESYNC_EVENT_LISTENER,
15
17
  )
16
18
  from port_ocean.exceptions.context import (
17
19
  PortOceanContextNotFoundError,
@@ -80,7 +82,7 @@ class PortOceanContext:
80
82
  ) -> RESYNC_EVENT_LISTENER | None:
81
83
  if not self.app.config.event_listener.should_resync:
82
84
  logger.debug(
83
- "Webhook only event listener is used, resync events are ignored"
85
+ f"Using event listener {self.app.config.event_listener.type}, which shouldn't perform any resyncs. Skipping resyncs setup..."
84
86
  )
85
87
  return None
86
88
  return self.integration.on_resync(function, kind)
@@ -93,6 +95,40 @@ class PortOceanContext:
93
95
 
94
96
  return wrapper
95
97
 
98
+ def on_resync_start(
99
+ self,
100
+ ) -> Callable[
101
+ [BEFORE_RESYNC_EVENT_LISTENER | None], BEFORE_RESYNC_EVENT_LISTENER | None
102
+ ]:
103
+ def wrapper(
104
+ function: BEFORE_RESYNC_EVENT_LISTENER | None,
105
+ ) -> BEFORE_RESYNC_EVENT_LISTENER | None:
106
+ if not self.app.config.event_listener.should_resync:
107
+ logger.debug(
108
+ f"Using event listener {self.app.config.event_listener.type}, which shouldn't perform any resyncs. Skipping resyncs setup..."
109
+ )
110
+ return None
111
+ return self.integration.on_resync_start(function)
112
+
113
+ return wrapper
114
+
115
+ def on_resync_complete(
116
+ self,
117
+ ) -> Callable[
118
+ [AFTER_RESYNC_EVENT_LISTENER | None], AFTER_RESYNC_EVENT_LISTENER | None
119
+ ]:
120
+ def wrapper(
121
+ function: AFTER_RESYNC_EVENT_LISTENER | None,
122
+ ) -> AFTER_RESYNC_EVENT_LISTENER | None:
123
+ if not self.app.config.event_listener.should_resync:
124
+ logger.debug(
125
+ f"Using event listener {self.app.config.event_listener.type}, which shouldn't perform any resyncs. Skipping resyncs setup..."
126
+ )
127
+ return None
128
+ return self.integration.on_resync_complete(function)
129
+
130
+ return wrapper
131
+
96
132
  async def update_raw_diff(
97
133
  self,
98
134
  kind: str,
@@ -109,9 +109,9 @@ class AbstractWebhookProcessor(ABC):
109
109
  pass
110
110
 
111
111
  @abstractmethod
112
- def should_process_event(self, event: WebhookEvent) -> bool:
112
+ async def should_process_event(self, event: WebhookEvent) -> bool:
113
113
  pass
114
114
 
115
115
  @abstractmethod
116
- def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
116
+ async def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
117
117
  pass
@@ -47,7 +47,7 @@ class LiveEventsProcessorManager(LiveEventsMixin, EventsMixin):
47
47
  except Exception as e:
48
48
  logger.exception(f"Error starting queue processor for {path}: {str(e)}")
49
49
 
50
- def _extract_matching_processors(
50
+ async def _extract_matching_processors(
51
51
  self, webhook_event: WebhookEvent, path: str
52
52
  ) -> list[tuple[ResourceConfig, AbstractWebhookProcessor]]:
53
53
  """Find and extract the matching processor for an event"""
@@ -56,8 +56,8 @@ class LiveEventsProcessorManager(LiveEventsMixin, EventsMixin):
56
56
 
57
57
  for processor_class in self._processors_classes[path]:
58
58
  processor = processor_class(webhook_event.clone())
59
- if processor.should_process_event(webhook_event):
60
- kinds = processor.get_matching_kinds(webhook_event)
59
+ if await processor.should_process_event(webhook_event):
60
+ kinds = await processor.get_matching_kinds(webhook_event)
61
61
  for kind in kinds:
62
62
  for resource in event.port_app_config.resources:
63
63
  if resource.kind == kind:
@@ -92,26 +92,30 @@ class LiveEventsProcessorManager(LiveEventsMixin, EventsMixin):
92
92
  parent_override=webhook_event.event_context,
93
93
  ):
94
94
  matching_processors_with_resource = (
95
- self._extract_matching_processors(webhook_event, path)
95
+ await self._extract_matching_processors(webhook_event, path)
96
96
  )
97
97
  webhook_event_raw_results_for_all_resources = await asyncio.gather(
98
98
  *(
99
99
  self._process_single_event(processor, path, resource)
100
100
  for resource, processor in matching_processors_with_resource
101
- )
101
+ ),
102
+ return_exceptions=True,
102
103
  )
103
- if webhook_event_raw_results_for_all_resources and all(
104
- webhook_event_raw_results_for_all_resources
105
- ):
104
+
105
+ successful_raw_results: list[WebhookEventRawResults] = [
106
+ result
107
+ for result in webhook_event_raw_results_for_all_resources
108
+ if isinstance(result, WebhookEventRawResults)
109
+ ]
110
+
111
+ if successful_raw_results:
106
112
  logger.info(
107
113
  "Exporting raw event results to entities",
108
114
  webhook_event_raw_results_for_all_resources_length=len(
109
- webhook_event_raw_results_for_all_resources
115
+ successful_raw_results
110
116
  ),
111
117
  )
112
- await self.sync_raw_results(
113
- webhook_event_raw_results_for_all_resources
114
- )
118
+ await self.sync_raw_results(successful_raw_results)
115
119
  except asyncio.CancelledError:
116
120
  logger.info(f"Queue processor for {path} is shutting down")
117
121
  for _, processor in matching_processors_with_resource:
@@ -0,0 +1,71 @@
1
+ from collections import defaultdict
2
+ from typing import Any
3
+
4
+ from loguru import logger
5
+
6
+ from port_ocean.core.ocean_types import (
7
+ IntegrationEventsCallbacks,
8
+ START_EVENT_LISTENER,
9
+ RESYNC_EVENT_LISTENER,
10
+ BEFORE_RESYNC_EVENT_LISTENER,
11
+ AFTER_RESYNC_EVENT_LISTENER,
12
+ )
13
+
14
+
15
+ class EventsMixin:
16
+ """A mixin class that provides event handling capabilities for the integration class.
17
+
18
+ This mixin allows classes to register event listeners and manage event callbacks.
19
+ It provides methods for attaching listeners to various lifecycle events.
20
+
21
+ Attributes:
22
+ event_strategy: A dictionary storing event callbacks for different event types.
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ self.event_strategy: IntegrationEventsCallbacks = {
27
+ "start": [],
28
+ "resync": defaultdict(list),
29
+ "resync_start": [],
30
+ "resync_complete": [],
31
+ }
32
+
33
+ @property
34
+ def available_resync_kinds(self) -> list[str]:
35
+ return list(self.event_strategy["resync"].keys())
36
+
37
+ def on_start(self, function: START_EVENT_LISTENER) -> START_EVENT_LISTENER:
38
+ """Register a function as a listener for the "start" event."""
39
+ logger.debug(f"Registering {function} as a start event listener")
40
+ self.event_strategy["start"].append(function)
41
+ return function
42
+
43
+ def on_resync(
44
+ self, function: RESYNC_EVENT_LISTENER | None, kind: str | None = None
45
+ ) -> RESYNC_EVENT_LISTENER | None:
46
+ """Register a function as a listener for a "resync" event."""
47
+ if function is not None:
48
+ if kind is None:
49
+ logger.debug("Registering resync event listener any kind")
50
+ else:
51
+ logger.info(f"Registering resync event listener for kind {kind}")
52
+ self.event_strategy["resync"][kind].append(function)
53
+ return function
54
+
55
+ def on_resync_start(
56
+ self, function: BEFORE_RESYNC_EVENT_LISTENER | None
57
+ ) -> BEFORE_RESYNC_EVENT_LISTENER | None:
58
+ """Register a function to be called when a resync operation starts."""
59
+ if function is not None:
60
+ logger.debug(f"Registering {function} as a resync_start event listener")
61
+ self.event_strategy["resync_start"].append(function)
62
+ return function
63
+
64
+ def on_resync_complete(
65
+ self, function: AFTER_RESYNC_EVENT_LISTENER | None
66
+ ) -> AFTER_RESYNC_EVENT_LISTENER | None:
67
+ """Register a function to be called when a resync operation completes."""
68
+ if function is not None:
69
+ logger.debug(f"Registering {function} as a resync_complete event listener")
70
+ self.event_strategy["resync_complete"].append(function)
71
+ return function
@@ -550,6 +550,10 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
550
550
  )
551
551
  logger.info(f"Resync will use the following mappings: {app_config.dict()}")
552
552
 
553
+ # Execute resync_start hooks
554
+ for resync_start_fn in self.event_strategy["resync_start"]:
555
+ await resync_start_fn()
556
+
553
557
  try:
554
558
  did_fetched_current_state = True
555
559
  except httpx.HTTPError as e:
@@ -598,7 +602,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
598
602
  if errors:
599
603
  message = f"Resync failed with {len(errors)}. Skipping delete phase due to incomplete state"
600
604
  error_group = ExceptionGroup(
601
- f"Resync failed with {len(errors)}. Skipping delete phase due to incomplete state",
605
+ message,
602
606
  errors,
603
607
  )
604
608
  if not silent:
@@ -618,3 +622,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
618
622
  )
619
623
 
620
624
  logger.info("Resync finished successfully")
625
+
626
+ # Execute resync_complete hooks
627
+ if "resync_complete" in self.event_strategy:
628
+ logger.info("Executing resync_complete hooks")
629
+
630
+ for resync_complete_fn in self.event_strategy["resync_complete"]:
631
+ await resync_complete_fn()
632
+
633
+ logger.info("Finished executing resync_complete hooks")
@@ -19,6 +19,9 @@ LISTENER_RESULT = Awaitable[RAW_RESULT] | ASYNC_GENERATOR_RESYNC_TYPE
19
19
  RESYNC_EVENT_LISTENER = Callable[[str], LISTENER_RESULT]
20
20
  START_EVENT_LISTENER = Callable[[], Awaitable[None]]
21
21
 
22
+ BEFORE_RESYNC_EVENT_LISTENER = Callable[[], Awaitable[None]]
23
+ AFTER_RESYNC_EVENT_LISTENER = Callable[[], Awaitable[None]]
24
+
22
25
 
23
26
  class RawEntityDiff(TypedDict):
24
27
  before: list[RAW_ITEM]
@@ -44,3 +47,5 @@ class CalculationResult(NamedTuple):
44
47
  class IntegrationEventsCallbacks(TypedDict):
45
48
  start: list[START_EVENT_LISTENER]
46
49
  resync: dict[str | None, list[RESYNC_EVENT_LISTENER]]
50
+ resync_start: list[BEFORE_RESYNC_EVENT_LISTENER]
51
+ resync_complete: list[AFTER_RESYNC_EVENT_LISTENER]
@@ -799,3 +799,177 @@ async def test_register_resource_raw_skip_event_type_http_request_upsert_called_
799
799
  mock_sync_raw_mixin._calculate_raw.assert_called_once()
800
800
  mock_sync_raw_mixin._map_entities_compared_with_port.assert_not_called()
801
801
  mock_sync_raw_mixin.entities_state_applier.upsert.assert_called_once()
802
+
803
+
804
+ @pytest.mark.asyncio
805
+ async def test_on_resync_start_hooks_are_called(
806
+ mock_sync_raw_mixin: SyncRawMixin,
807
+ mock_port_app_config: PortAppConfig,
808
+ ) -> None:
809
+ # Setup
810
+ resync_start_called = False
811
+
812
+ async def on_resync_start() -> None:
813
+ nonlocal resync_start_called
814
+ resync_start_called = True
815
+
816
+ mock_sync_raw_mixin.on_resync_start(on_resync_start)
817
+
818
+ # Execute
819
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
820
+ event.port_app_config = mock_port_app_config
821
+ await mock_sync_raw_mixin.sync_raw_all(
822
+ trigger_type="machine",
823
+ user_agent_type=UserAgentType.exporter,
824
+ )
825
+
826
+ # Verify
827
+ assert resync_start_called, "on_resync_start hook was not called"
828
+
829
+
830
+ @pytest.mark.asyncio
831
+ async def test_on_resync_complete_hooks_are_called_on_success(
832
+ mock_sync_raw_mixin: SyncRawMixin,
833
+ mock_port_app_config: PortAppConfig,
834
+ mock_ocean: Ocean,
835
+ ) -> None:
836
+ # Setup
837
+ resync_complete_called = False
838
+
839
+ async def on_resync_complete() -> None:
840
+ nonlocal resync_complete_called
841
+ resync_complete_called = True
842
+
843
+ mock_sync_raw_mixin.on_resync_complete(on_resync_complete)
844
+ mock_ocean.port_client.search_entities.return_value = [] # type: ignore
845
+
846
+ # Execute
847
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
848
+ event.port_app_config = mock_port_app_config
849
+ await mock_sync_raw_mixin.sync_raw_all(
850
+ trigger_type="machine",
851
+ user_agent_type=UserAgentType.exporter,
852
+ )
853
+
854
+ # Verify
855
+ assert resync_complete_called, "on_resync_complete hook was not called"
856
+
857
+
858
+ @pytest.mark.asyncio
859
+ async def test_on_resync_complete_hooks_not_called_on_error(
860
+ mock_sync_raw_mixin: SyncRawMixin,
861
+ mock_port_app_config: PortAppConfig,
862
+ ) -> None:
863
+ # Setup
864
+ resync_complete_called = False
865
+
866
+ async def on_resync_complete() -> None:
867
+ nonlocal resync_complete_called
868
+ resync_complete_called = True
869
+
870
+ mock_sync_raw_mixin.on_resync_complete(on_resync_complete)
871
+ mock_sync_raw_mixin._get_resource_raw_results.side_effect = Exception("Test error") # type: ignore
872
+
873
+ # Execute
874
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
875
+ event.port_app_config = mock_port_app_config
876
+ with pytest.raises(Exception):
877
+ await mock_sync_raw_mixin.sync_raw_all(
878
+ trigger_type="machine",
879
+ user_agent_type=UserAgentType.exporter,
880
+ )
881
+
882
+ # Verify
883
+ assert (
884
+ not resync_complete_called
885
+ ), "on_resync_complete hook should not have been called on error"
886
+
887
+
888
+ @pytest.mark.asyncio
889
+ async def test_multiple_on_resync_start_on_resync_complete_hooks_called_in_order(
890
+ mock_sync_raw_mixin: SyncRawMixin,
891
+ mock_port_app_config: PortAppConfig,
892
+ mock_ocean: Ocean,
893
+ ) -> None:
894
+ # Setup
895
+ call_order: list[str] = []
896
+
897
+ async def on_resync_start1() -> None:
898
+ call_order.append("on_resync_start1")
899
+
900
+ async def on_resync_start2() -> None:
901
+ call_order.append("on_resync_start2")
902
+
903
+ async def on_resync_complete1() -> None:
904
+ call_order.append("on_resync_complete1")
905
+
906
+ async def on_resync_complete2() -> None:
907
+ call_order.append("on_resync_complete2")
908
+
909
+ mock_sync_raw_mixin.on_resync_start(on_resync_start1)
910
+ mock_sync_raw_mixin.on_resync_start(on_resync_start2)
911
+ mock_sync_raw_mixin.on_resync_complete(on_resync_complete1)
912
+ mock_sync_raw_mixin.on_resync_complete(on_resync_complete2)
913
+ mock_ocean.port_client.search_entities.return_value = [] # type: ignore
914
+
915
+ # Execute
916
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
917
+ event.port_app_config = mock_port_app_config
918
+ await mock_sync_raw_mixin.sync_raw_all(
919
+ trigger_type="machine",
920
+ user_agent_type=UserAgentType.exporter,
921
+ )
922
+
923
+ # Verify
924
+ assert call_order == [
925
+ "on_resync_start1",
926
+ "on_resync_start2",
927
+ "on_resync_complete1",
928
+ "on_resync_complete2",
929
+ ], "Hooks were not called in the correct order"
930
+
931
+
932
+ @pytest.mark.asyncio
933
+ async def test_on_resync_start_hook_error_prevents_resync(
934
+ mock_sync_raw_mixin: SyncRawMixin,
935
+ mock_port_app_config: PortAppConfig,
936
+ ) -> None:
937
+ # Setup
938
+ resync_complete_called = False
939
+ resync_proceeded = False
940
+
941
+ async def on_resync_start() -> None:
942
+ raise Exception("Before resync error")
943
+
944
+ async def on_resync_complete() -> None:
945
+ nonlocal resync_complete_called
946
+ resync_complete_called = True
947
+
948
+ mock_sync_raw_mixin.on_resync_start(on_resync_start)
949
+ mock_sync_raw_mixin.on_resync_complete(on_resync_complete)
950
+
951
+ original_get_resource_raw_results = mock_sync_raw_mixin._get_resource_raw_results
952
+
953
+ async def track_resync(*args: Any, **kwargs: Any) -> Any:
954
+ nonlocal resync_proceeded
955
+ resync_proceeded = True
956
+ return await original_get_resource_raw_results(*args, **kwargs)
957
+
958
+ mock_sync_raw_mixin._get_resource_raw_results = track_resync # type: ignore
959
+
960
+ # Execute
961
+ async with event_context(EventType.RESYNC, trigger_type="machine") as event:
962
+ event.port_app_config = mock_port_app_config
963
+ with pytest.raises(Exception, match="Before resync error"):
964
+ await mock_sync_raw_mixin.sync_raw_all(
965
+ trigger_type="machine",
966
+ user_agent_type=UserAgentType.exporter,
967
+ )
968
+
969
+ # Verify
970
+ assert (
971
+ not resync_proceeded
972
+ ), "Resync should not have proceeded after before_resync hook error"
973
+ assert (
974
+ not resync_complete_called
975
+ ), "on_resync_complete hook should not have been called after error"
@@ -32,7 +32,7 @@ class ConcreteWebhookProcessor(AbstractWebhookProcessor):
32
32
  ) -> WebhookEventRawResults:
33
33
  return WebhookEventRawResults(updated_raw_results=[{}], deleted_raw_results=[])
34
34
 
35
- def should_process_event(self, webhook_event: WebhookEvent) -> bool:
35
+ async def should_process_event(self, webhook_event: WebhookEvent) -> bool:
36
36
  return True
37
37
 
38
38
  async def before_processing(self) -> None:
@@ -47,7 +47,7 @@ class ConcreteWebhookProcessor(AbstractWebhookProcessor):
47
47
  await super().cancel()
48
48
  self.cancel_called = True
49
49
 
50
- def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
50
+ async def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
51
51
  return ["test"]
52
52
 
53
53