port-ocean 0.28.8__tar.gz → 0.28.11__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 (218) hide show
  1. {port_ocean-0.28.8 → port_ocean-0.28.11}/PKG-INFO +1 -1
  2. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.Deb +0 -1
  3. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.local +0 -1
  4. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/integrations.py +1 -1
  5. port_ocean-0.28.11/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +357 -0
  6. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/models.py +4 -2
  7. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/sync_raw.py +1 -1
  8. port_ocean-0.28.11/port_ocean/core/integrations/mixins/utils.py +136 -0
  9. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/utils/utils.py +16 -5
  10. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +1 -1
  11. port_ocean-0.28.11/port_ocean/tests/core/utils/test_get_port_diff.py +164 -0
  12. {port_ocean-0.28.8 → port_ocean-0.28.11}/pyproject.toml +1 -1
  13. port_ocean-0.28.8/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -679
  14. port_ocean-0.28.8/port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +0 -69
  15. port_ocean-0.28.8/port_ocean/core/integrations/mixins/utils.py +0 -348
  16. {port_ocean-0.28.8 → port_ocean-0.28.11}/LICENSE.md +0 -0
  17. {port_ocean-0.28.8 → port_ocean-0.28.11}/README.md +0 -0
  18. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.alpine +0 -0
  19. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.base.builder +0 -0
  20. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.base.runner +0 -0
  21. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Dockerfile.dockerignore +0 -0
  22. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/Makefile +0 -0
  23. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/README.md +0 -0
  24. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/entry_local.sh +0 -0
  25. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/grpcio.sh +0 -0
  26. {port_ocean-0.28.8 → port_ocean-0.28.11}/integrations/_infra/init.sh +0 -0
  27. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/__init__.py +0 -0
  28. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/bootstrap.py +0 -0
  29. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/__init__.py +0 -0
  30. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/base.py +0 -0
  31. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/disk.py +0 -0
  32. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/errors.py +0 -0
  33. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cache/memory.py +0 -0
  34. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/__init__.py +0 -0
  35. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cli.py +0 -0
  36. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/__init__.py +0 -0
  37. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  38. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/clean.py +0 -0
  39. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/dock.py +0 -0
  40. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/defaults/group.py +0 -0
  41. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/list_integrations.py +0 -0
  42. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/main.py +0 -0
  43. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/new.py +0 -0
  44. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/pull.py +0 -0
  45. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/sail.py +0 -0
  46. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/commands/version.py +0 -0
  47. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  48. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  49. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  50. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  51. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  52. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  53. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  54. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  55. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  56. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  57. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  58. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  59. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  60. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  61. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  62. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  63. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  64. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  65. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  66. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  67. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  68. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/cli/utils.py +0 -0
  69. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/__init__.py +0 -0
  70. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/__init__.py +0 -0
  71. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/auth_client.py +0 -0
  72. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/auth/oauth_client.py +0 -0
  73. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/__init__.py +0 -0
  74. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/authentication.py +0 -0
  75. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/client.py +0 -0
  76. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/__init__.py +0 -0
  77. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  78. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/entities.py +0 -0
  79. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/migrations.py +0 -0
  80. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/mixins/organization.py +0 -0
  81. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/retry_transport.py +0 -0
  82. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/types.py +0 -0
  83. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/clients/port/utils.py +0 -0
  84. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/__init__.py +0 -0
  85. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/base.py +0 -0
  86. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/dynamic.py +0 -0
  87. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/config/settings.py +0 -0
  88. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/consumers/__init__.py +0 -0
  89. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/consumers/kafka_consumer.py +0 -0
  90. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/__init__.py +0 -0
  91. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/event.py +0 -0
  92. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/metric_resource.py +0 -0
  93. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/ocean.py +0 -0
  94. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/context/resource.py +0 -0
  95. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/__init__.py +0 -0
  96. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/__init__.py +0 -0
  97. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/clean.py +0 -0
  98. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/common.py +0 -0
  99. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/defaults/initialize.py +0 -0
  100. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/__init__.py +0 -0
  101. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/base.py +0 -0
  102. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/factory.py +0 -0
  103. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/http.py +0 -0
  104. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/kafka.py +0 -0
  105. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/once.py +0 -0
  106. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/polling.py +0 -0
  107. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/event_listener/webhooks_only.py +0 -0
  108. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/__init__.py +0 -0
  109. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/base.py +0 -0
  110. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  111. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  112. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  113. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  114. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  115. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  116. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  117. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  118. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  119. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  120. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  121. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/__init__.py +0 -0
  122. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
  123. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/group_queue.py +0 -0
  124. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/queue/local_queue.py +0 -0
  125. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  126. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  127. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/__init__.py +0 -0
  128. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
  129. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
  130. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
  131. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/__init__.py +0 -0
  132. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/base.py +0 -0
  133. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  134. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/events.py +0 -0
  135. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/handler.py +0 -0
  136. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/live_events.py +0 -0
  137. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/integrations/mixins/sync.py +0 -0
  138. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/models.py +0 -0
  139. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/ocean_types.py +0 -0
  140. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  141. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/debug_cli.py +0 -0
  142. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/__init__.py +0 -0
  143. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/api.py +0 -0
  144. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/base.py +0 -0
  145. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/clients.py +0 -0
  146. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/context.py +0 -0
  147. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/core.py +0 -0
  148. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/port_defaults.py +0 -0
  149. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/utils.py +0 -0
  150. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/exceptions/webhook_processor.py +0 -0
  151. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/__init__.py +0 -0
  152. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/async_client.py +0 -0
  153. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/metric/metric.py +0 -0
  154. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/metric/utils.py +0 -0
  155. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/retry.py +0 -0
  156. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/helpers/stream.py +0 -0
  157. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/__init__.py +0 -0
  158. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/handlers.py +0 -0
  159. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/logger_setup.py +0 -0
  160. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/log/sensetive.py +0 -0
  161. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/middlewares.py +0 -0
  162. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/ocean.py +0 -0
  163. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/py.typed +0 -0
  164. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/run.py +0 -0
  165. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/sonar-project.properties +0 -0
  166. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/__init__.py +0 -0
  167. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/__init__.py +0 -0
  168. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/test_disk_cache.py +0 -0
  169. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/cache/test_memory_cache.py +0 -0
  170. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/__init__.py +0 -0
  171. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/oauth/__init__.py +0 -0
  172. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
  173. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  174. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_integrations.py +0 -0
  175. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
  176. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/config/test_config.py +0 -0
  177. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/conftest.py +0 -0
  178. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/conftest.py +0 -0
  179. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/defaults/test_common.py +0 -0
  180. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/event_listener/test_kafka.py +0 -0
  181. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
  182. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
  183. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
  184. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
  185. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
  186. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/queue/test_group_queue.py +0 -0
  187. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
  188. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
  189. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
  190. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
  191. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/test_utils.py +0 -0
  192. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  193. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
  194. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/__init__.py +0 -0
  195. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  196. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/fixtures.py +0 -0
  197. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/integration.py +0 -0
  198. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/ocean_app.py +0 -0
  199. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/port_client.py +0 -0
  200. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/smoke_test.py +0 -0
  201. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/helpers/test_retry.py +0 -0
  202. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/log/test_handlers.py +0 -0
  203. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_metric.py +0 -0
  204. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_ocean.py +0 -0
  205. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/test_smoke.py +0 -0
  206. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  207. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/tests/utils/test_cache.py +0 -0
  208. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/__init__.py +0 -0
  209. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/async_http.py +0 -0
  210. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/async_iterators.py +0 -0
  211. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/cache.py +0 -0
  212. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/ipc.py +0 -0
  213. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/misc.py +0 -0
  214. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/queue_utils.py +0 -0
  215. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/repeat.py +0 -0
  216. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/signal.py +0 -0
  217. {port_ocean-0.28.8 → port_ocean-0.28.11}/port_ocean/utils/time.py +0 -0
  218. {port_ocean-0.28.8 → port_ocean-0.28.11}/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.8
3
+ Version: 0.28.11
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,6 @@ RUN apt-get update \
48
48
  curl \
49
49
  acl \
50
50
  sudo \
51
- jq \
52
51
  && apt-get clean
53
52
 
54
53
  LABEL INTEGRATION_VERSION=${INTEGRATION_VERSION}
@@ -26,7 +26,6 @@ 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 \
@@ -301,7 +301,7 @@ class IntegrationClientMixin:
301
301
  headers=headers,
302
302
  json={
303
303
  "items": raw_data,
304
- "extractionTimestamp": datetime.now().isoformat(),
304
+ "extractionTimestamp": int(datetime.now().timestamp()),
305
305
  },
306
306
  )
307
307
  handle_port_status_code(response, should_log=False)
@@ -0,0 +1,357 @@
1
+ import asyncio
2
+ from asyncio import Task
3
+ from dataclasses import dataclass, field
4
+
5
+ from functools import lru_cache
6
+ from typing import Any, Optional
7
+ import jq # type: ignore
8
+ from loguru import logger
9
+
10
+ from port_ocean.context.ocean import ocean
11
+ from port_ocean.core.handlers.entity_processor.base import BaseEntityProcessor
12
+ from port_ocean.core.handlers.port_app_config.models import ResourceConfig
13
+ from port_ocean.core.models import Entity
14
+ from port_ocean.core.ocean_types import (
15
+ RAW_ITEM,
16
+ EntitySelectorDiff,
17
+ CalculationResult,
18
+ )
19
+ from port_ocean.core.utils.utils import (
20
+ gather_and_split_errors_from_results,
21
+ zip_and_sum,
22
+ )
23
+ from port_ocean.exceptions.core import EntityProcessorException
24
+ from port_ocean.utils.queue_utils import process_in_queue
25
+
26
+
27
+ class ExampleStates:
28
+ __succeed: list[dict[str, Any]]
29
+ __errors: list[dict[str, Any]]
30
+ __max_size: int
31
+
32
+ def __init__(self, max_size: int = 0) -> None:
33
+ """
34
+ Store two sequences:
35
+ - succeed: items that succeeded
36
+ - errors: items that failed
37
+ """
38
+ self.__succeed = []
39
+ self.__errors = []
40
+ self.__max_size = max_size
41
+
42
+ def add_example(self, succeed: bool, item: dict[str, Any]) -> None:
43
+ if succeed:
44
+ self.__succeed.append(item)
45
+ else:
46
+ self.__errors.append(item)
47
+
48
+ def __len__(self) -> int:
49
+ """
50
+ Total number of items (successes + errors).
51
+ """
52
+ return len(self.__succeed) + len(self.__errors)
53
+
54
+ def get_examples(self, number: int = 0) -> list[dict[str, Any]]:
55
+ """
56
+ Return a list of up to number items, taking successes first,
57
+ """
58
+ if number <= 0:
59
+ number = self.__max_size
60
+ # how many from succeed?
61
+ s_count = min(number, len(self.__succeed))
62
+ result = list(self.__succeed[:s_count])
63
+ # how many more from errors?
64
+ e_count = number - s_count
65
+ if e_count > 0:
66
+ result.extend(self.__errors[:e_count])
67
+ return result
68
+
69
+
70
+ @dataclass
71
+ class MappedEntity:
72
+ """Represents the entity after applying the mapping
73
+
74
+ This class holds the mapping entity along with the selector boolean value and optionally the raw data.
75
+ """
76
+
77
+ entity: dict[str, Any] = field(default_factory=dict)
78
+ did_entity_pass_selector: bool = False
79
+ raw_data: Optional[dict[str, Any]] = None
80
+ misconfigurations: dict[str, str] = field(default_factory=dict)
81
+
82
+
83
+ class JQEntityProcessor(BaseEntityProcessor):
84
+ """Processes and parses entities using JQ expressions.
85
+
86
+ This class extends the BaseEntityProcessor and provides methods for processing and
87
+ parsing entities based on PyJQ queries. It supports compiling and executing PyJQ patterns,
88
+ searching for data in dictionaries, and transforming data based on object mappings.
89
+ """
90
+
91
+ @lru_cache
92
+ def _compile(self, pattern: str) -> Any:
93
+ if not ocean.config.allow_environment_variables_jq_access:
94
+ pattern = "def env: {}; {} as $ENV | " + pattern
95
+ return jq.compile(pattern)
96
+
97
+ @staticmethod
98
+ def _stop_iterator_handler(func: Any) -> Any:
99
+ """
100
+ Wrap the function to handle StopIteration exceptions.
101
+ Prevents StopIteration from stopping the thread and skipping further queue processing.
102
+ """
103
+
104
+ def inner() -> Any:
105
+ try:
106
+ return func()
107
+ except StopIteration:
108
+ return None
109
+
110
+ return inner
111
+
112
+ @staticmethod
113
+ def _notify_mapping_issues(
114
+ entity_misconfigurations: dict[str, str],
115
+ missing_required_fields: bool,
116
+ entity_mapping_fault_counter: int,
117
+ ) -> None:
118
+
119
+ if len(entity_misconfigurations) > 0:
120
+ logger.info(
121
+ f"Unable to find valid data for: {entity_misconfigurations} (null, missing, or misconfigured)"
122
+ )
123
+ if missing_required_fields:
124
+ logger.info(
125
+ f"{entity_mapping_fault_counter} transformations of batch failed due to empty, null or missing values"
126
+ )
127
+
128
+ async def _search(self, data: dict[str, Any], pattern: str) -> Any:
129
+ try:
130
+ loop = asyncio.get_event_loop()
131
+ compiled_pattern = self._compile(pattern)
132
+ func = compiled_pattern.input_value(data)
133
+ return await loop.run_in_executor(
134
+ None, self._stop_iterator_handler(func.first)
135
+ )
136
+ except Exception as exc:
137
+ logger.debug(
138
+ f"Search failed for pattern '{pattern}' in data: {data}, Error: {exc}"
139
+ )
140
+ return None
141
+
142
+ async def _search_as_bool(self, data: dict[str, Any], pattern: str) -> bool:
143
+ loop = asyncio.get_event_loop()
144
+
145
+ compiled_pattern = self._compile(pattern)
146
+ func = compiled_pattern.input_value(data)
147
+
148
+ value = await loop.run_in_executor(
149
+ None, self._stop_iterator_handler(func.first)
150
+ )
151
+ if isinstance(value, bool):
152
+ return value
153
+ raise EntityProcessorException(
154
+ f"Expected boolean value, got value:{value} of type: {type(value)} instead"
155
+ )
156
+
157
+ async def _search_as_object(
158
+ self,
159
+ data: dict[str, Any],
160
+ obj: dict[str, Any],
161
+ misconfigurations: dict[str, str] | None = None,
162
+ ) -> dict[str, Any | None]:
163
+ """
164
+ Identify and extract the relevant value for the chosen key and populate it into the entity
165
+ :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,
166
+ if the data is a dict, we will recursively call this function again.
167
+ :param obj: the key that we want its value to be mapped into our entity.
168
+ :param misconfigurations: due to the recursive nature of this function,
169
+ we aim to have a dict that represents all of the misconfigured properties and when used recursively,
170
+ we pass this reference to misfoncigured object to add the relevant misconfigured keys.
171
+ :return: Mapped object with found value.
172
+ """
173
+
174
+ search_tasks: dict[
175
+ str, Task[dict[str, Any | None]] | list[Task[dict[str, Any | None]]]
176
+ ] = {}
177
+ for key, value in obj.items():
178
+ if isinstance(value, list):
179
+ search_tasks[key] = [
180
+ asyncio.create_task(
181
+ self._search_as_object(data, obj, misconfigurations)
182
+ )
183
+ for obj in value
184
+ ]
185
+
186
+ elif isinstance(value, dict):
187
+ search_tasks[key] = asyncio.create_task(
188
+ self._search_as_object(data, value, misconfigurations)
189
+ )
190
+ else:
191
+ search_tasks[key] = asyncio.create_task(self._search(data, value))
192
+
193
+ result: dict[str, Any | None] = {}
194
+ for key, task in search_tasks.items():
195
+ try:
196
+ if isinstance(task, list):
197
+ result_list = []
198
+ for task in task:
199
+ task_result = await task
200
+ if task_result is None and misconfigurations is not None:
201
+ misconfigurations[key] = obj[key]
202
+ result_list.append(task_result)
203
+ result[key] = result_list
204
+ else:
205
+ task_result = await task
206
+ if task_result is None and misconfigurations is not None:
207
+ misconfigurations[key] = obj[key]
208
+ result[key] = task_result
209
+ except Exception:
210
+ result[key] = None
211
+ return result
212
+
213
+ async def _get_mapped_entity(
214
+ self,
215
+ data: dict[str, Any],
216
+ raw_entity_mappings: dict[str, Any],
217
+ selector_query: str,
218
+ parse_all: bool = False,
219
+ ) -> MappedEntity:
220
+ should_run = await self._search_as_bool(data, selector_query)
221
+ if parse_all or should_run:
222
+ misconfigurations: dict[str, str] = {}
223
+ mapped_entity = await self._search_as_object(
224
+ data, raw_entity_mappings, misconfigurations
225
+ )
226
+ return MappedEntity(
227
+ mapped_entity,
228
+ did_entity_pass_selector=should_run,
229
+ raw_data=data,
230
+ misconfigurations=misconfigurations,
231
+ )
232
+
233
+ return MappedEntity(
234
+ {},
235
+ did_entity_pass_selector=False,
236
+ raw_data=data,
237
+ misconfigurations={},
238
+ )
239
+
240
+ async def _calculate_entity(
241
+ self,
242
+ data: dict[str, Any],
243
+ raw_entity_mappings: dict[str, Any],
244
+ items_to_parse: str | None,
245
+ items_to_parse_name: str,
246
+ selector_query: str,
247
+ parse_all: bool = False,
248
+ ) -> tuple[list[MappedEntity], list[Exception]]:
249
+ raw_data = [data.copy()]
250
+ if not ocean.config.yield_items_to_parse:
251
+ if items_to_parse:
252
+ items = await self._search(data, items_to_parse)
253
+ if not isinstance(items, list):
254
+ logger.warning(
255
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
256
+ f" Skipping..."
257
+ )
258
+ return [], []
259
+ raw_data = [{items_to_parse_name: item, **data} for item in items]
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.port.items_to_parse,
308
+ mapping.port.items_to_parse_name,
309
+ mapping.selector.query,
310
+ parse_all,
311
+ )
312
+ )
313
+ logger.debug(
314
+ f"Finished parsing raw results into entities with {len(errors)} errors. errors: {errors}"
315
+ )
316
+
317
+ passed_entities = []
318
+ failed_entities = []
319
+ examples_to_send = ExampleStates(send_raw_data_examples_amount)
320
+ entity_misconfigurations: dict[str, str] = {}
321
+ missing_required_fields: bool = False
322
+ entity_mapping_fault_counter: int = 0
323
+ for result in calculated_entities_results:
324
+ if len(result.misconfigurations) > 0:
325
+ entity_misconfigurations |= result.misconfigurations
326
+
327
+ if (
328
+ len(examples_to_send) < send_raw_data_examples_amount
329
+ and result.raw_data is not None
330
+ ):
331
+ examples_to_send.add_example(
332
+ result.did_entity_pass_selector, result.raw_data
333
+ )
334
+
335
+ if result.entity.get("identifier") and result.entity.get("blueprint"):
336
+ parsed_entity = Entity.parse_obj(result.entity)
337
+ if result.did_entity_pass_selector:
338
+ passed_entities.append(parsed_entity)
339
+ else:
340
+ failed_entities.append(parsed_entity)
341
+ else:
342
+ missing_required_fields = True
343
+ entity_mapping_fault_counter += 1
344
+
345
+ self._notify_mapping_issues(
346
+ entity_misconfigurations,
347
+ missing_required_fields,
348
+ entity_mapping_fault_counter,
349
+ )
350
+
351
+ await self._send_examples(examples_to_send.get_examples(), mapping.kind)
352
+
353
+ return CalculationResult(
354
+ EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
355
+ errors,
356
+ misconfigured_entity_keys=entity_misconfigurations,
357
+ )
@@ -29,7 +29,9 @@ class EntityMapping(BaseModel):
29
29
 
30
30
  @property
31
31
  def is_using_search_identifier(self) -> bool:
32
- return isinstance(self.identifier, dict)
32
+ return isinstance(self.identifier, dict) or isinstance(
33
+ self.identifier, IngestSearchQuery
34
+ )
33
35
 
34
36
 
35
37
  class MappingsConfig(BaseModel):
@@ -39,7 +41,7 @@ class MappingsConfig(BaseModel):
39
41
  class PortResourceConfig(BaseModel):
40
42
  entity: MappingsConfig
41
43
  items_to_parse: str | None = Field(alias="itemsToParse")
42
- items_to_parse_name: str = Field(alias="itemsToParseName", default="item")
44
+ items_to_parse_name: str | None = Field(alias="itemsToParseName", default="item")
43
45
 
44
46
 
45
47
  class Selector(BaseModel):
@@ -117,7 +117,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
117
117
  logger.info(
118
118
  f"Found async generator function for {resource_config.kind} name: {task.__qualname__}"
119
119
  )
120
- results.append(resync_generator_wrapper(task, resource_config.kind, resource_config.port.items_to_parse_name, resource_config.port.items_to_parse))
120
+ results.append(resync_generator_wrapper(task, resource_config.kind,resource_config.port.items_to_parse))
121
121
  else:
122
122
  logger.info(
123
123
  f"Found sync function for {resource_config.kind} name: {task.__qualname__}"
@@ -0,0 +1,136 @@
1
+ from contextlib import contextmanager
2
+ from typing import Awaitable, Generator, Callable, cast
3
+
4
+ from loguru import logger
5
+
6
+ import asyncio
7
+ import multiprocessing
8
+
9
+ from port_ocean.core.handlers.entity_processor.jq_entity_processor import JQEntityProcessor
10
+ from port_ocean.core.handlers.port_app_config.models import ResourceConfig
11
+ from port_ocean.core.ocean_types import (
12
+ ASYNC_GENERATOR_RESYNC_TYPE,
13
+ RAW_RESULT,
14
+ RESYNC_EVENT_LISTENER,
15
+ RESYNC_RESULT,
16
+ )
17
+ from port_ocean.core.utils.utils import validate_result
18
+ from port_ocean.exceptions.core import (
19
+ RawObjectValidationException,
20
+ OceanAbortException,
21
+ KindNotImplementedException,
22
+ )
23
+
24
+ from port_ocean.utils.async_http import _http_client
25
+ from port_ocean.clients.port.utils import _http_client as _port_http_client
26
+ from port_ocean.helpers.metric.metric import MetricType, MetricPhase
27
+ from port_ocean.context.ocean import ocean
28
+
29
+ @contextmanager
30
+ def resync_error_handling() -> Generator[None, None, None]:
31
+ try:
32
+ yield
33
+ except RawObjectValidationException as error:
34
+ err_msg = f"Failed to validate raw data for returned data from resync function, error: {error}"
35
+ logger.exception(err_msg)
36
+ raise OceanAbortException(err_msg) from error
37
+ except StopAsyncIteration:
38
+ raise
39
+ except Exception as error:
40
+ err_msg = f"Failed to execute resync function, error: {error}"
41
+ logger.exception(err_msg)
42
+ raise OceanAbortException(err_msg) from error
43
+
44
+
45
+ async def resync_function_wrapper(
46
+ fn: Callable[[str], Awaitable[RAW_RESULT]], kind: str
47
+ ) -> RAW_RESULT:
48
+ with resync_error_handling():
49
+ results = await fn(kind)
50
+ return validate_result(results)
51
+
52
+
53
+ async def resync_generator_wrapper(
54
+ fn: Callable[[str], ASYNC_GENERATOR_RESYNC_TYPE], kind: str, items_to_parse: str | None = None
55
+ ) -> ASYNC_GENERATOR_RESYNC_TYPE:
56
+ generator = fn(kind)
57
+ errors = []
58
+ try:
59
+ while True:
60
+ try:
61
+ with resync_error_handling():
62
+ result = await anext(generator)
63
+ if not ocean.config.yield_items_to_parse:
64
+ yield validate_result(result)
65
+ else:
66
+ batch_size = ocean.config.yield_items_to_parse_batch_size
67
+ if items_to_parse:
68
+ for data in result:
69
+ items = await cast(JQEntityProcessor, ocean.app.integration.entity_processor)._search(data, items_to_parse)
70
+ if not isinstance(items, list):
71
+ logger.warning(
72
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
73
+ f" Skipping..."
74
+ )
75
+ yield []
76
+ raw_data = [{"item": item, **data} for item in items]
77
+ while True:
78
+ raw_data_batch = raw_data[:batch_size]
79
+ yield raw_data_batch
80
+ raw_data = raw_data[batch_size:]
81
+ if len(raw_data) == 0:
82
+ break
83
+ else:
84
+ yield validate_result(result)
85
+ except OceanAbortException as error:
86
+ errors.append(error)
87
+ ocean.metrics.inc_metric(
88
+ name=MetricType.OBJECT_COUNT_NAME,
89
+ labels=[ocean.metrics.current_resource_kind(), MetricPhase.EXTRACT , MetricPhase.ExtractResult.FAILED],
90
+ value=1
91
+ )
92
+ except StopAsyncIteration:
93
+ if errors:
94
+ raise ExceptionGroup(
95
+ "At least one of the resync generator iterations failed", errors
96
+ )
97
+
98
+
99
+ def is_resource_supported(
100
+ kind: str, resync_event_mapping: dict[str | None, list[RESYNC_EVENT_LISTENER]]
101
+ ) -> bool:
102
+ return bool(resync_event_mapping[kind] or resync_event_mapping[None])
103
+
104
+
105
+ def unsupported_kind_response(
106
+ kind: str, available_resync_kinds: list[str]
107
+ ) -> tuple[RESYNC_RESULT, list[Exception]]:
108
+ logger.error(f"Kind {kind} is not supported in this integration")
109
+ return [], [KindNotImplementedException(kind, available_resync_kinds)]
110
+
111
+
112
+ class ProcessWrapper(multiprocessing.Process):
113
+ def __init__(self, *args, **kwargs):
114
+ super().__init__(*args, **kwargs)
115
+
116
+ async def join_async(self) -> None:
117
+ while self.exitcode is None:
118
+ await asyncio.sleep(2)
119
+ if self.exitcode != 0:
120
+ logger.error(f"Process {self.pid} failed with exit code {self.exitcode}")
121
+ else:
122
+ logger.info(f"Process {self.pid} finished with exit code {self.exitcode}")
123
+ return super().join()
124
+
125
+ def clear_http_client_context() -> None:
126
+ try:
127
+ while _http_client.top is not None:
128
+ _http_client.pop()
129
+ except (RuntimeError, AttributeError):
130
+ pass
131
+
132
+ try:
133
+ while _port_http_client.top is not None:
134
+ _port_http_client.pop()
135
+ except (RuntimeError, AttributeError):
136
+ pass
@@ -4,7 +4,7 @@ import json
4
4
  from typing import Iterable, Any, TypeVar, Callable, Awaitable
5
5
 
6
6
  from loguru import logger
7
- from pydantic import parse_obj_as, ValidationError
7
+ from pydantic import BaseModel, parse_obj_as, ValidationError
8
8
 
9
9
 
10
10
  from port_ocean.clients.port.client import PortClient
@@ -79,6 +79,19 @@ async def gather_and_split_errors_from_results(
79
79
  return valid_items, errors
80
80
 
81
81
 
82
+ def _get_entity_key(entity: Entity) -> tuple[str, str]:
83
+ identifier = entity.identifier
84
+ if isinstance(identifier, BaseModel):
85
+ identifier = identifier.dict()
86
+
87
+ key_part = (
88
+ json.dumps(identifier, sort_keys=True)
89
+ if isinstance(identifier, dict)
90
+ else str(identifier)
91
+ )
92
+ return key_part, entity.blueprint
93
+
94
+
82
95
  def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPortDiff:
83
96
  before_dict = {}
84
97
  after_dict = {}
@@ -88,12 +101,10 @@ def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPo
88
101
 
89
102
  # Create dictionaries for before and after lists
90
103
  for entity in before:
91
- key = (entity.identifier, entity.blueprint)
92
- before_dict[key] = entity
104
+ before_dict[_get_entity_key(entity)] = entity
93
105
 
94
106
  for entity in after:
95
- key = (entity.identifier, entity.blueprint)
96
- after_dict[key] = entity
107
+ after_dict[_get_entity_key(entity)] = entity
97
108
 
98
109
  # Find created, modified, and deleted objects
99
110
  for key, obj in after_dict.items():
@@ -50,7 +50,7 @@ class TestJQEntityProcessor:
50
50
  raw_entity_mappings = {"foo": ".foo"}
51
51
  selector_query = '.foo == "bar"'
52
52
  result = await mocked_processor._get_mapped_entity(
53
- data, raw_entity_mappings, None, selector_query
53
+ data, raw_entity_mappings, selector_query
54
54
  )
55
55
  assert result.entity == {"foo": "bar"}
56
56
  assert result.did_entity_pass_selector is True