port-ocean 0.28.5__tar.gz → 0.28.7__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 (217) hide show
  1. {port_ocean-0.28.5 → port_ocean-0.28.7}/PKG-INFO +1 -1
  2. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.Deb +1 -0
  3. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.local +1 -0
  4. port_ocean-0.28.7/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +679 -0
  5. port_ocean-0.28.7/port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +69 -0
  6. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/port_app_config/models.py +1 -1
  7. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/sync_raw.py +1 -1
  8. port_ocean-0.28.7/port_ocean/core/integrations/mixins/utils.py +348 -0
  9. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +1 -1
  10. {port_ocean-0.28.5 → port_ocean-0.28.7}/pyproject.toml +1 -1
  11. port_ocean-0.28.5/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -357
  12. port_ocean-0.28.5/port_ocean/core/integrations/mixins/utils.py +0 -136
  13. {port_ocean-0.28.5 → port_ocean-0.28.7}/LICENSE.md +0 -0
  14. {port_ocean-0.28.5 → port_ocean-0.28.7}/README.md +0 -0
  15. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.alpine +0 -0
  16. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.base.builder +0 -0
  17. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.base.runner +0 -0
  18. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Dockerfile.dockerignore +0 -0
  19. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/Makefile +0 -0
  20. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/README.md +0 -0
  21. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/entry_local.sh +0 -0
  22. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/grpcio.sh +0 -0
  23. {port_ocean-0.28.5 → port_ocean-0.28.7}/integrations/_infra/init.sh +0 -0
  24. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/__init__.py +0 -0
  25. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/bootstrap.py +0 -0
  26. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cache/__init__.py +0 -0
  27. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cache/base.py +0 -0
  28. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cache/disk.py +0 -0
  29. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cache/errors.py +0 -0
  30. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cache/memory.py +0 -0
  31. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/__init__.py +0 -0
  32. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cli.py +0 -0
  33. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/__init__.py +0 -0
  34. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  35. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/defaults/clean.py +0 -0
  36. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/defaults/dock.py +0 -0
  37. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/defaults/group.py +0 -0
  38. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/list_integrations.py +0 -0
  39. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/main.py +0 -0
  40. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/new.py +0 -0
  41. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/pull.py +0 -0
  42. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/sail.py +0 -0
  43. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/commands/version.py +0 -0
  44. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  45. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  46. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  47. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  48. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  49. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  50. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  51. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  52. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  53. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  54. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  55. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  56. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  57. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  58. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  59. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  60. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  61. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  62. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  63. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  64. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  65. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/cli/utils.py +0 -0
  66. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/__init__.py +0 -0
  67. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/auth/__init__.py +0 -0
  68. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/auth/auth_client.py +0 -0
  69. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/auth/oauth_client.py +0 -0
  70. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/__init__.py +0 -0
  71. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/authentication.py +0 -0
  72. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/client.py +0 -0
  73. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/__init__.py +0 -0
  74. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  75. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/entities.py +0 -0
  76. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/integrations.py +0 -0
  77. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/migrations.py +0 -0
  78. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/mixins/organization.py +0 -0
  79. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/retry_transport.py +0 -0
  80. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/types.py +0 -0
  81. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/clients/port/utils.py +0 -0
  82. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/config/__init__.py +0 -0
  83. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/config/base.py +0 -0
  84. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/config/dynamic.py +0 -0
  85. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/config/settings.py +0 -0
  86. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/consumers/__init__.py +0 -0
  87. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/consumers/kafka_consumer.py +0 -0
  88. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/context/__init__.py +0 -0
  89. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/context/event.py +0 -0
  90. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/context/metric_resource.py +0 -0
  91. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/context/ocean.py +0 -0
  92. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/context/resource.py +0 -0
  93. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/__init__.py +0 -0
  94. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/defaults/__init__.py +0 -0
  95. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/defaults/clean.py +0 -0
  96. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/defaults/common.py +0 -0
  97. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/defaults/initialize.py +0 -0
  98. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/__init__.py +0 -0
  99. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/base.py +0 -0
  100. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/factory.py +0 -0
  101. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/http.py +0 -0
  102. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/kafka.py +0 -0
  103. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/once.py +0 -0
  104. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/polling.py +0 -0
  105. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/event_listener/webhooks_only.py +0 -0
  106. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/__init__.py +0 -0
  107. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/base.py +0 -0
  108. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  109. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  110. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  111. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  112. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  113. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  114. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  115. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  116. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  117. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  118. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  119. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/queue/__init__.py +0 -0
  120. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
  121. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/queue/group_queue.py +0 -0
  122. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/queue/local_queue.py +0 -0
  123. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  124. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  125. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/webhook/__init__.py +0 -0
  126. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
  127. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
  128. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
  129. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/__init__.py +0 -0
  130. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/base.py +0 -0
  131. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  132. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/events.py +0 -0
  133. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/handler.py +0 -0
  134. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/live_events.py +0 -0
  135. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/integrations/mixins/sync.py +0 -0
  136. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/models.py +0 -0
  137. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/ocean_types.py +0 -0
  138. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  139. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/core/utils/utils.py +0 -0
  140. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/debug_cli.py +0 -0
  141. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/__init__.py +0 -0
  142. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/api.py +0 -0
  143. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/base.py +0 -0
  144. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/clients.py +0 -0
  145. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/context.py +0 -0
  146. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/core.py +0 -0
  147. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/port_defaults.py +0 -0
  148. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/utils.py +0 -0
  149. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/exceptions/webhook_processor.py +0 -0
  150. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/__init__.py +0 -0
  151. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/async_client.py +0 -0
  152. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/metric/metric.py +0 -0
  153. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/metric/utils.py +0 -0
  154. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/retry.py +0 -0
  155. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/helpers/stream.py +0 -0
  156. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/log/__init__.py +0 -0
  157. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/log/handlers.py +0 -0
  158. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/log/logger_setup.py +0 -0
  159. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/log/sensetive.py +0 -0
  160. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/middlewares.py +0 -0
  161. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/ocean.py +0 -0
  162. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/py.typed +0 -0
  163. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/run.py +0 -0
  164. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/sonar-project.properties +0 -0
  165. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/__init__.py +0 -0
  166. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/cache/__init__.py +0 -0
  167. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/cache/test_disk_cache.py +0 -0
  168. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/cache/test_memory_cache.py +0 -0
  169. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/__init__.py +0 -0
  170. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/oauth/__init__.py +0 -0
  171. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
  172. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  173. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
  174. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
  175. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/config/test_config.py +0 -0
  176. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/conftest.py +0 -0
  177. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/conftest.py +0 -0
  178. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/defaults/test_common.py +0 -0
  179. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
  180. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
  181. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
  182. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
  183. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
  184. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
  185. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
  186. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
  187. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
  188. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
  189. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
  190. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/test_utils.py +0 -0
  191. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  192. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
  193. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/__init__.py +0 -0
  194. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  195. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/fixtures.py +0 -0
  196. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/integration.py +0 -0
  197. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/ocean_app.py +0 -0
  198. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/port_client.py +0 -0
  199. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/smoke_test.py +0 -0
  200. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/helpers/test_retry.py +0 -0
  201. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/log/test_handlers.py +0 -0
  202. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/test_metric.py +0 -0
  203. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/test_ocean.py +0 -0
  204. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/test_smoke.py +0 -0
  205. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  206. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/tests/utils/test_cache.py +0 -0
  207. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/__init__.py +0 -0
  208. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/async_http.py +0 -0
  209. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/async_iterators.py +0 -0
  210. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/cache.py +0 -0
  211. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/ipc.py +0 -0
  212. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/misc.py +0 -0
  213. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/queue_utils.py +0 -0
  214. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/repeat.py +0 -0
  215. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/signal.py +0 -0
  216. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/utils/time.py +0 -0
  217. {port_ocean-0.28.5 → port_ocean-0.28.7}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.28.5
3
+ Version: 0.28.7
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,6 +48,7 @@ RUN apt-get update \
48
48
  curl \
49
49
  acl \
50
50
  sudo \
51
+ jq \
51
52
  && apt-get clean
52
53
 
53
54
  LABEL INTEGRATION_VERSION=${INTEGRATION_VERSION}
@@ -26,6 +26,7 @@ RUN apt-get update \
26
26
  python3-pip \
27
27
  python3-poetry \
28
28
  build-essential\
29
+ jq \
29
30
  git \
30
31
  python3-venv \
31
32
  acl \
@@ -0,0 +1,679 @@
1
+ import asyncio
2
+ from asyncio import Task
3
+ from dataclasses import dataclass, field
4
+ from functools import lru_cache
5
+ import json
6
+ from typing import Any, Optional
7
+ import jq # type: ignore
8
+ from loguru import logger
9
+ from port_ocean.context.ocean import ocean
10
+ from port_ocean.core.handlers.entity_processor.base import BaseEntityProcessor
11
+ from port_ocean.core.handlers.port_app_config.models import ResourceConfig
12
+ from port_ocean.core.models import Entity
13
+ from port_ocean.core.ocean_types import (
14
+ RAW_ITEM,
15
+ EntitySelectorDiff,
16
+ CalculationResult,
17
+ )
18
+ from port_ocean.core.utils.utils import (
19
+ gather_and_split_errors_from_results,
20
+ zip_and_sum,
21
+ )
22
+ from port_ocean.exceptions.core import EntityProcessorException
23
+ from port_ocean.utils.queue_utils import process_in_queue
24
+ from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
25
+ InputEvaluationResult,
26
+ evaluate_input,
27
+ should_shortcut_no_input,
28
+ )
29
+
30
+
31
+ class ExampleStates:
32
+ __succeed: list[dict[str, Any]]
33
+ __errors: list[dict[str, Any]]
34
+ __max_size: int
35
+
36
+ def __init__(self, max_size: int = 0) -> None:
37
+ """
38
+ Store two sequences:
39
+ - succeed: items that succeeded
40
+ - errors: items that failed
41
+ """
42
+ self.__succeed = []
43
+ self.__errors = []
44
+ self.__max_size = max_size
45
+
46
+ def add_example(self, succeed: bool, item: dict[str, Any]) -> None:
47
+ if succeed:
48
+ self.__succeed.append(item)
49
+ else:
50
+ self.__errors.append(item)
51
+
52
+ def __len__(self) -> int:
53
+ """
54
+ Total number of items (successes + errors).
55
+ """
56
+ return len(self.__succeed) + len(self.__errors)
57
+
58
+ def get_examples(self, number: int = 0) -> list[dict[str, Any]]:
59
+ """
60
+ Return a list of up to number items, taking successes first,
61
+ """
62
+ if number <= 0:
63
+ number = self.__max_size
64
+ # how many from succeed?
65
+ s_count = min(number, len(self.__succeed))
66
+ result = list(self.__succeed[:s_count])
67
+ # how many more from errors?
68
+ e_count = number - s_count
69
+ if e_count > 0:
70
+ result.extend(self.__errors[:e_count])
71
+ return result
72
+
73
+
74
+ @dataclass
75
+ class MappedEntity:
76
+ """Represents the entity after applying the mapping
77
+
78
+ This class holds the mapping entity along with the selector boolean value and optionally the raw data.
79
+ """
80
+
81
+ entity: dict[str, Any] = field(default_factory=dict)
82
+ did_entity_pass_selector: bool = False
83
+ raw_data: Optional[dict[str, Any] | tuple[dict[str, Any], str]] = None
84
+ misconfigurations: dict[str, str] = field(default_factory=dict)
85
+
86
+
87
+ class JQEntityProcessor(BaseEntityProcessor):
88
+ """Processes and parses entities using JQ expressions.
89
+
90
+ This class extends the BaseEntityProcessor and provides methods for processing and
91
+ parsing entities based on PyJQ queries. It supports compiling and executing PyJQ patterns,
92
+ searching for data in dictionaries, and transforming data based on object mappings.
93
+ """
94
+
95
+ @lru_cache
96
+ def _compile(self, pattern: str) -> Any:
97
+ if not ocean.config.allow_environment_variables_jq_access:
98
+ pattern = "def env: {}; {} as $ENV | " + pattern
99
+ return jq.compile(pattern)
100
+
101
+ @staticmethod
102
+ def _stop_iterator_handler(func: Any) -> Any:
103
+ """
104
+ Wrap the function to handle StopIteration exceptions.
105
+ Prevents StopIteration from stopping the thread and skipping further queue processing.
106
+ """
107
+
108
+ def inner() -> Any:
109
+ try:
110
+ return func()
111
+ except StopIteration:
112
+ return None
113
+
114
+ return inner
115
+
116
+ @staticmethod
117
+ def _notify_mapping_issues(
118
+ entity_misconfigurations: dict[str, str],
119
+ missing_required_fields: bool,
120
+ entity_mapping_fault_counter: int,
121
+ ) -> None:
122
+
123
+ if len(entity_misconfigurations) > 0:
124
+ logger.info(
125
+ f"Unable to find valid data for: {entity_misconfigurations} (null, missing, or misconfigured)"
126
+ )
127
+ if missing_required_fields:
128
+ logger.info(
129
+ f"{entity_mapping_fault_counter} transformations of batch failed due to empty, null or missing values"
130
+ )
131
+
132
+ async def _search(self, data: dict[str, Any], pattern: str) -> Any:
133
+ try:
134
+ loop = asyncio.get_event_loop()
135
+ compiled_pattern = self._compile(pattern)
136
+ func = compiled_pattern.input_value(data)
137
+ return await loop.run_in_executor(
138
+ None, self._stop_iterator_handler(func.first)
139
+ )
140
+ except Exception as exc:
141
+ logger.error(
142
+ f"Search failed for pattern '{pattern}' in data: {data}, Error: {exc}"
143
+ )
144
+ return None
145
+
146
+ @lru_cache
147
+ async def _search_stringified(self, data: str, pattern: str) -> Any:
148
+ try:
149
+ loop = asyncio.get_event_loop()
150
+ compiled_pattern = self._compile(pattern)
151
+ func = compiled_pattern.input_text(data)
152
+ return await loop.run_in_executor(
153
+ None, self._stop_iterator_handler(func.first)
154
+ )
155
+ except Exception as exc:
156
+ logger.debug(
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
+ loop = asyncio.get_event_loop()
163
+
164
+ compiled_pattern = self._compile(pattern)
165
+ if isinstance(data, str):
166
+ func = compiled_pattern.input_text(data)
167
+ else:
168
+ func = compiled_pattern.input_value(data)
169
+
170
+ value = await loop.run_in_executor(
171
+ None, self._stop_iterator_handler(func.first)
172
+ )
173
+ if isinstance(value, bool):
174
+ return value
175
+ raise EntityProcessorException(
176
+ f"Expected boolean value, got value:{value} of type: {type(value)} instead"
177
+ )
178
+
179
+ async def _search_as_object(
180
+ self,
181
+ data: dict[str, Any] | str,
182
+ obj: dict[str, Any],
183
+ misconfigurations: dict[str, str] | None = None,
184
+ ) -> dict[str, Any | None]:
185
+ """
186
+ Identify and extract the relevant value for the chosen key and populate it into the entity
187
+ :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,
188
+ if the data is a dict, we will recursively call this function again.
189
+ :param obj: the key that we want its value to be mapped into our entity.
190
+ :param misconfigurations: due to the recursive nature of this function,
191
+ we aim to have a dict that represents all of the misconfigured properties and when used recursively,
192
+ we pass this reference to misfoncigured object to add the relevant misconfigured keys.
193
+ :return: Mapped object with found value.
194
+ """
195
+
196
+ search_tasks: dict[
197
+ str, Task[dict[str, Any | None]] | list[Task[dict[str, Any | None]]]
198
+ ] = {}
199
+ for key, value in obj.items():
200
+ if isinstance(value, list):
201
+ search_tasks[key] = [
202
+ asyncio.create_task(
203
+ self._search_as_object(data, obj, misconfigurations)
204
+ )
205
+ for obj in value
206
+ ]
207
+
208
+ elif isinstance(value, dict):
209
+ search_tasks[key] = asyncio.create_task(
210
+ self._search_as_object(data, value, misconfigurations)
211
+ )
212
+ else:
213
+ if isinstance(data, str):
214
+ search_tasks[key] = asyncio.create_task(
215
+ self._search_stringified(data, value)
216
+ )
217
+ else:
218
+ search_tasks[key] = asyncio.create_task(self._search(data, value))
219
+
220
+ result: dict[str, Any | None] = {}
221
+ for key, task in search_tasks.items():
222
+ try:
223
+ if isinstance(task, list):
224
+ result_list = []
225
+ for task in task:
226
+ task_result = await task
227
+ if task_result is None and misconfigurations is not None:
228
+ misconfigurations[key] = obj[key]
229
+ result_list.append(task_result)
230
+ result[key] = result_list
231
+ else:
232
+ task_result = await task
233
+ if task_result is None and misconfigurations is not None:
234
+ misconfigurations[key] = obj[key]
235
+ result[key] = task_result
236
+ except Exception:
237
+ result[key] = None
238
+ return result
239
+
240
+ async def _get_mapped_entity(
241
+ self,
242
+ data: dict[str, Any] | tuple[dict[str, Any], str],
243
+ raw_entity_mappings: dict[str, Any],
244
+ items_to_parse_key: str | None,
245
+ selector_query: str,
246
+ parse_all: bool = False,
247
+ ) -> MappedEntity:
248
+ should_run = await self._should_map_entity(
249
+ data, selector_query, items_to_parse_key
250
+ )
251
+ if parse_all or should_run:
252
+ misconfigurations, mapped_entity = await self._map_entity(
253
+ data, raw_entity_mappings, items_to_parse_key
254
+ )
255
+ return MappedEntity(
256
+ mapped_entity,
257
+ did_entity_pass_selector=should_run,
258
+ raw_data=data,
259
+ misconfigurations=misconfigurations,
260
+ )
261
+
262
+ return MappedEntity(
263
+ {},
264
+ did_entity_pass_selector=False,
265
+ raw_data=data,
266
+ misconfigurations={},
267
+ )
268
+
269
+ async def _map_entity(
270
+ self,
271
+ data: dict[str, Any] | tuple[dict[str, Any], str],
272
+ raw_entity_mappings: dict[str, Any],
273
+ items_to_parse_key: str | None,
274
+ ) -> tuple[dict[str, str], dict[str, Any]]:
275
+ if not items_to_parse_key:
276
+ misconfigurations: dict[str, str] = {}
277
+ data_to_search = data if isinstance(data, dict) else data[0]
278
+ mapped_entity = await self._search_as_object(
279
+ data_to_search, raw_entity_mappings, misconfigurations
280
+ )
281
+ return misconfigurations, mapped_entity
282
+
283
+ modified_data: tuple[dict[str, Any], str | dict[str, Any]] = (
284
+ data
285
+ if isinstance(data, tuple)
286
+ else (
287
+ {items_to_parse_key: data[items_to_parse_key]},
288
+ data,
289
+ )
290
+ )
291
+
292
+ misconfigurations_item: dict[str, str] = {}
293
+ misconfigurations_all: dict[str, str] = {}
294
+ mapped_entity_item = await self._search_as_object(
295
+ modified_data[0], raw_entity_mappings["item"], misconfigurations_item
296
+ )
297
+ if misconfigurations_item:
298
+ filtered_item_mappings = self._filter_mappings_by_keys(
299
+ raw_entity_mappings["item"], list(misconfigurations_item.keys())
300
+ )
301
+ raw_entity_mappings["all"] = self._deep_merge(
302
+ raw_entity_mappings["all"], filtered_item_mappings
303
+ )
304
+ mapped_entity_all = await self._search_as_object(
305
+ modified_data[1], raw_entity_mappings["all"], misconfigurations_all
306
+ )
307
+ mapped_entity_empty = await self._search_as_object(
308
+ {}, raw_entity_mappings["empty"], misconfigurations_all
309
+ )
310
+ mapped_entity = self._deep_merge(mapped_entity_item, mapped_entity_all)
311
+ mapped_entity = self._deep_merge(mapped_entity, mapped_entity_empty)
312
+ return misconfigurations_all, mapped_entity
313
+
314
+ async def _should_map_entity(
315
+ self,
316
+ data: dict[str, Any] | tuple[dict[str, Any], str],
317
+ selector_query: str,
318
+ items_to_parse_key: str | None,
319
+ ) -> bool:
320
+ if should_shortcut_no_input(selector_query):
321
+ return await self._search_as_bool({}, selector_query)
322
+ if isinstance(data, tuple):
323
+ return await self._search_as_bool(
324
+ data[0], selector_query
325
+ ) or await self._search_as_bool(data[1], selector_query)
326
+ if items_to_parse_key:
327
+ return await self._search_as_bool(
328
+ data[items_to_parse_key], selector_query
329
+ ) or await self._search_as_bool(data, selector_query)
330
+ return await self._search_as_bool(data, selector_query)
331
+
332
+ async def _calculate_entity(
333
+ self,
334
+ data: dict[str, Any],
335
+ raw_entity_mappings: dict[str, Any],
336
+ items_to_parse: str | None,
337
+ items_to_parse_name: str,
338
+ selector_query: str,
339
+ parse_all: bool = False,
340
+ ) -> tuple[list[MappedEntity], list[Exception]]:
341
+ raw_data: list[dict[str, Any]] | list[tuple[dict[str, Any], str]] = [
342
+ data.copy()
343
+ ]
344
+ items_to_parse_key = None
345
+ if items_to_parse:
346
+ items_to_parse_key = items_to_parse_name
347
+ if not ocean.config.yield_items_to_parse:
348
+ if data.get("file", {}).get("content", {}).get("path", None):
349
+ with open(data["file"]["content"]["path"], "r") as f:
350
+ data["file"]["content"] = json.loads(f.read())
351
+ items = await self._search(data, items_to_parse)
352
+ if not isinstance(items, list):
353
+ logger.warning(
354
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
355
+ f" Skipping..."
356
+ )
357
+ return [], []
358
+ raw_all_payload_stringified = json.dumps(data)
359
+ raw_data = [
360
+ ({items_to_parse_name: item}, raw_all_payload_stringified)
361
+ for item in items
362
+ ]
363
+ single_item_mappings, all_items_mappings, empty_items_mappings = (
364
+ self._build_raw_entity_mappings(
365
+ raw_entity_mappings, items_to_parse_name
366
+ )
367
+ )
368
+ raw_entity_mappings = {
369
+ "item": single_item_mappings,
370
+ "all": all_items_mappings,
371
+ "empty": empty_items_mappings,
372
+ }
373
+
374
+ entities, errors = await gather_and_split_errors_from_results(
375
+ [
376
+ self._get_mapped_entity(
377
+ raw,
378
+ raw_entity_mappings,
379
+ items_to_parse_key,
380
+ selector_query,
381
+ parse_all,
382
+ )
383
+ for raw in raw_data
384
+ ]
385
+ )
386
+ if errors:
387
+ logger.error(
388
+ f"Failed to calculate entities with {len(errors)} errors. errors: {errors}"
389
+ )
390
+ return entities, errors
391
+
392
+ def _build_raw_entity_mappings(
393
+ self, raw_entity_mappings: dict[str, Any], items_to_parse_name: str
394
+ ) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
395
+ """Filter entity mappings to only include values that start with f'.{items_to_parse_name}'"""
396
+ mappings: dict[InputEvaluationResult, dict[str, Any]] = {
397
+ InputEvaluationResult.NONE: {},
398
+ InputEvaluationResult.SINGLE: {},
399
+ InputEvaluationResult.ALL: {},
400
+ }
401
+ pattern = f".{items_to_parse_name}"
402
+ for key, value in raw_entity_mappings.items():
403
+ if isinstance(value, str):
404
+ # Direct string values (identifier, title, icon, blueprint, team)
405
+ self.group_string_mapping_value(
406
+ pattern,
407
+ mappings,
408
+ key,
409
+ value,
410
+ )
411
+ elif isinstance(value, dict):
412
+ # Complex objects (IngestSearchQuery for identifier/team, properties, relations)
413
+ self.group_complex_mapping_value(
414
+ pattern,
415
+ mappings,
416
+ key,
417
+ value,
418
+ )
419
+ return (
420
+ mappings[InputEvaluationResult.SINGLE],
421
+ mappings[InputEvaluationResult.ALL],
422
+ mappings[InputEvaluationResult.NONE],
423
+ )
424
+
425
+ def group_complex_mapping_value(
426
+ self,
427
+ pattern: str,
428
+ mappings: dict[InputEvaluationResult, dict[str, Any]],
429
+ key: str,
430
+ value: dict[str, Any],
431
+ ) -> None:
432
+ mapping_dicts: dict[InputEvaluationResult, dict[str, Any]] = {
433
+ InputEvaluationResult.SINGLE: {},
434
+ InputEvaluationResult.ALL: {},
435
+ InputEvaluationResult.NONE: {},
436
+ }
437
+ if key in ["properties", "relations"]:
438
+ # For properties and relations, filter the dictionary values
439
+ for dict_key, dict_value in value.items():
440
+ if isinstance(dict_value, str):
441
+ self.group_string_mapping_value(
442
+ pattern,
443
+ mapping_dicts,
444
+ dict_key,
445
+ dict_value,
446
+ )
447
+ elif isinstance(dict_value, dict):
448
+ # Handle IngestSearchQuery objects
449
+ self.group_search_query_mapping_value(
450
+ pattern,
451
+ mapping_dicts[InputEvaluationResult.SINGLE],
452
+ mapping_dicts[InputEvaluationResult.ALL],
453
+ dict_key,
454
+ dict_value,
455
+ )
456
+ else:
457
+ # For identifier/team IngestSearchQuery objects
458
+ self.group_search_query_mapping_value(
459
+ pattern,
460
+ mapping_dicts[InputEvaluationResult.SINGLE],
461
+ mapping_dicts[InputEvaluationResult.ALL],
462
+ key,
463
+ value,
464
+ )
465
+ if mapping_dicts[InputEvaluationResult.SINGLE]:
466
+ mappings[InputEvaluationResult.SINGLE][key] = mapping_dicts[
467
+ InputEvaluationResult.SINGLE
468
+ ][key]
469
+ if mapping_dicts[InputEvaluationResult.ALL]:
470
+ mappings[InputEvaluationResult.ALL][key] = mapping_dicts[
471
+ InputEvaluationResult.ALL
472
+ ][key]
473
+ if mapping_dicts[InputEvaluationResult.NONE]:
474
+ mappings[InputEvaluationResult.NONE][key] = mapping_dicts[
475
+ InputEvaluationResult.NONE
476
+ ][key]
477
+
478
+ def group_search_query_mapping_value(
479
+ self,
480
+ pattern: str,
481
+ single_item_dict: dict[str, Any],
482
+ all_item_dict: dict[str, Any],
483
+ dict_key: str,
484
+ dict_value: dict[str, Any],
485
+ ) -> None:
486
+ if self._should_keep_ingest_search_query(dict_value, pattern):
487
+ single_item_dict[dict_key] = dict_value
488
+ else:
489
+ all_item_dict[dict_key] = dict_value
490
+
491
+ def group_string_mapping_value(
492
+ self,
493
+ pattern: str,
494
+ mappings: dict[InputEvaluationResult, dict[str, Any]],
495
+ key: str,
496
+ value: str,
497
+ ) -> None:
498
+ input_evaluation_result = evaluate_input(value, pattern)
499
+ mappings[input_evaluation_result][key] = value
500
+
501
+ def _should_keep_ingest_search_query(
502
+ self, query_dict: dict[str, Any], pattern: str
503
+ ) -> bool:
504
+ """Check if an IngestSearchQuery should be kept based on its rules"""
505
+ if "rules" not in query_dict:
506
+ return False
507
+
508
+ rules = query_dict["rules"]
509
+ if not isinstance(rules, list):
510
+ return False
511
+
512
+ # Check if any rule contains a value starting with the pattern
513
+ for rule in rules:
514
+ if isinstance(rule, dict):
515
+ if "value" in rule and isinstance(rule["value"], str):
516
+ if pattern in rule["value"]:
517
+ return True
518
+ # Recursively check nested IngestSearchQuery objects
519
+ elif "rules" in rule:
520
+ if self._should_keep_ingest_search_query(rule, pattern):
521
+ return True
522
+ return False
523
+
524
+ def _filter_mappings_by_keys(
525
+ self, mappings: dict[str, Any], target_keys: list[str]
526
+ ) -> dict[str, Any]:
527
+ """
528
+ Filter mappings to preserve structure with only the specified keys present.
529
+ Recursively handles nested dictionaries and lists, searching for keys at any level.
530
+ """
531
+ if not target_keys:
532
+ return {}
533
+
534
+ filtered_mappings: dict[str, Any] = {}
535
+
536
+ for key, value in mappings.items():
537
+ filtered_value = self._process_mapping_value(key, value, target_keys)
538
+
539
+ # Include if it's a direct match or contains nested target keys
540
+ if key in target_keys or filtered_value:
541
+ filtered_mappings[key] = filtered_value
542
+
543
+ return filtered_mappings
544
+
545
+ def _process_mapping_value(
546
+ self, key: str, value: Any, target_keys: list[str]
547
+ ) -> Any:
548
+ """Process a single mapping value, handling different types recursively."""
549
+ if isinstance(value, dict):
550
+ # Recursively filter nested dictionary
551
+ filtered_dict = self._filter_mappings_by_keys(value, target_keys)
552
+ return filtered_dict if filtered_dict else None
553
+ else:
554
+ # Return simple values as-is
555
+ return value if key in target_keys else None
556
+
557
+ def _deep_merge(
558
+ self, dict1: dict[str, Any], dict2: dict[str, Any]
559
+ ) -> dict[str, Any]:
560
+ """
561
+ Deep merge two dictionaries, preserving nested structures.
562
+ Values from dict2 override values from dict1 for the same keys.
563
+ """
564
+ result = dict1.copy()
565
+
566
+ for key, value in dict2.items():
567
+ if (
568
+ key in result
569
+ and isinstance(result[key], dict)
570
+ and isinstance(value, dict)
571
+ ):
572
+ # Recursively merge nested dictionaries
573
+ result[key] = self._deep_merge(result[key], value)
574
+ elif (
575
+ key in result
576
+ and isinstance(result[key], list)
577
+ and isinstance(value, list)
578
+ ):
579
+ # Merge lists by extending
580
+ result[key].extend(value)
581
+ else:
582
+ # Override with value from dict2
583
+ result[key] = value
584
+
585
+ return result
586
+
587
+ @staticmethod
588
+ async def _send_examples(data: list[dict[str, Any]], kind: str) -> None:
589
+ try:
590
+ if data:
591
+ await ocean.port_client.ingest_integration_kind_examples(
592
+ kind, data, should_log=False
593
+ )
594
+ except Exception as ex:
595
+ logger.warning(
596
+ f"Failed to send raw data example {ex}",
597
+ exc_info=True,
598
+ )
599
+
600
+ async def _parse_items(
601
+ self,
602
+ mapping: ResourceConfig,
603
+ raw_results: list[RAW_ITEM],
604
+ parse_all: bool = False,
605
+ send_raw_data_examples_amount: int = 0,
606
+ ) -> CalculationResult:
607
+ raw_entity_mappings: dict[str, Any] = mapping.port.entity.mappings.dict(
608
+ exclude_unset=True
609
+ )
610
+ logger.info(f"Parsing {len(raw_results)} raw results into entities")
611
+ calculated_entities_results, errors = zip_and_sum(
612
+ await process_in_queue(
613
+ raw_results,
614
+ self._calculate_entity,
615
+ raw_entity_mappings,
616
+ mapping.port.items_to_parse,
617
+ mapping.port.items_to_parse_name,
618
+ mapping.selector.query,
619
+ parse_all,
620
+ )
621
+ )
622
+ logger.debug(
623
+ f"Finished parsing raw results into entities with {len(errors)} errors. errors: {errors}"
624
+ )
625
+
626
+ passed_entities = []
627
+ failed_entities = []
628
+ examples_to_send = ExampleStates(send_raw_data_examples_amount)
629
+ entity_misconfigurations: dict[str, str] = {}
630
+ missing_required_fields: bool = False
631
+ entity_mapping_fault_counter: int = 0
632
+ for result in calculated_entities_results:
633
+ if len(result.misconfigurations) > 0:
634
+ entity_misconfigurations |= result.misconfigurations
635
+
636
+ if (
637
+ len(examples_to_send) < send_raw_data_examples_amount
638
+ and result.raw_data is not None
639
+ ):
640
+ examples_to_send.add_example(
641
+ result.did_entity_pass_selector,
642
+ self._get_raw_data_for_example(
643
+ result.raw_data, mapping.port.items_to_parse_name
644
+ ),
645
+ )
646
+
647
+ if result.entity.get("identifier") and result.entity.get("blueprint"):
648
+ parsed_entity = Entity.parse_obj(result.entity)
649
+ if result.did_entity_pass_selector:
650
+ passed_entities.append(parsed_entity)
651
+ else:
652
+ failed_entities.append(parsed_entity)
653
+ else:
654
+ missing_required_fields = True
655
+ entity_mapping_fault_counter += 1
656
+
657
+ self._notify_mapping_issues(
658
+ entity_misconfigurations,
659
+ missing_required_fields,
660
+ entity_mapping_fault_counter,
661
+ )
662
+
663
+ await self._send_examples(examples_to_send.get_examples(), mapping.kind)
664
+
665
+ return CalculationResult(
666
+ EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
667
+ errors,
668
+ misconfigured_entity_keys=entity_misconfigurations,
669
+ )
670
+
671
+ def _get_raw_data_for_example(
672
+ self,
673
+ data: dict[str, Any] | tuple[dict[str, Any], str],
674
+ items_to_parse_name: str,
675
+ ) -> dict[str, Any]:
676
+ if isinstance(data, tuple):
677
+ raw_data = json.loads(data[1])
678
+ return {items_to_parse_name: data[0], **raw_data}
679
+ return data