port-ocean 0.30.7__tar.gz → 0.31.0__tar.gz

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