port-ocean 0.23.4__tar.gz → 0.24.0__tar.gz

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

Potentially problematic release.


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

Files changed (208) hide show
  1. {port_ocean-0.23.4 → port_ocean-0.24.0}/PKG-INFO +1 -1
  2. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.Deb +6 -6
  3. port_ocean-0.24.0/port_ocean/clients/port/mixins/entities.py +570 -0
  4. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/config/dynamic.py +1 -1
  5. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/config/settings.py +4 -0
  6. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +1 -1
  7. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/sync_raw.py +2 -3
  8. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/models.py +20 -1
  9. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/core.py +4 -0
  10. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/helpers/metric/metric.py +3 -1
  11. port_ocean-0.24.0/port_ocean/tests/clients/port/mixins/test_entities.py +174 -0
  12. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/conftest.py +50 -15
  13. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +41 -69
  14. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +32 -10
  15. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/log/test_handlers.py +2 -1
  16. {port_ocean-0.23.4 → port_ocean-0.24.0}/pyproject.toml +1 -1
  17. port_ocean-0.23.4/port_ocean/clients/port/mixins/entities.py +0 -309
  18. port_ocean-0.23.4/port_ocean/tests/clients/port/mixins/test_entities.py +0 -56
  19. {port_ocean-0.23.4 → port_ocean-0.24.0}/LICENSE.md +0 -0
  20. {port_ocean-0.23.4 → port_ocean-0.24.0}/README.md +0 -0
  21. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.alpine +0 -0
  22. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.base.builder +0 -0
  23. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.base.runner +0 -0
  24. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
  25. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Dockerfile.local +0 -0
  26. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/Makefile +0 -0
  27. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/entry_local.sh +0 -0
  28. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/grpcio.sh +0 -0
  29. {port_ocean-0.23.4 → port_ocean-0.24.0}/integrations/_infra/init.sh +0 -0
  30. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/__init__.py +0 -0
  31. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/bootstrap.py +0 -0
  32. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cache/__init__.py +0 -0
  33. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cache/base.py +0 -0
  34. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cache/disk.py +0 -0
  35. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cache/errors.py +0 -0
  36. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cache/memory.py +0 -0
  37. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/__init__.py +0 -0
  38. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cli.py +0 -0
  39. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/__init__.py +0 -0
  40. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  41. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
  42. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
  43. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/defaults/group.py +0 -0
  44. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/list_integrations.py +0 -0
  45. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/main.py +0 -0
  46. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/new.py +0 -0
  47. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/pull.py +0 -0
  48. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/sail.py +0 -0
  49. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/commands/version.py +0 -0
  50. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  51. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  52. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  53. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  54. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  55. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  56. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  57. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  58. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  59. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  60. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  61. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  62. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  63. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  64. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  65. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  66. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  67. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  68. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  69. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  70. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  71. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/cli/utils.py +0 -0
  72. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/__init__.py +0 -0
  73. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/auth/__init__.py +0 -0
  74. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/auth/auth_client.py +0 -0
  75. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/auth/oauth_client.py +0 -0
  76. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/__init__.py +0 -0
  77. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/authentication.py +0 -0
  78. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/client.py +0 -0
  79. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
  80. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  81. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
  82. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
  83. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/mixins/organization.py +0 -0
  84. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/retry_transport.py +0 -0
  85. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/types.py +0 -0
  86. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/clients/port/utils.py +0 -0
  87. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/config/__init__.py +0 -0
  88. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/config/base.py +0 -0
  89. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/consumers/__init__.py +0 -0
  90. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/consumers/kafka_consumer.py +0 -0
  91. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/context/__init__.py +0 -0
  92. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/context/event.py +0 -0
  93. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/context/metric_resource.py +0 -0
  94. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/context/ocean.py +0 -0
  95. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/context/resource.py +0 -0
  96. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/__init__.py +0 -0
  97. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/defaults/__init__.py +0 -0
  98. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/defaults/clean.py +0 -0
  99. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/defaults/common.py +0 -0
  100. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/defaults/initialize.py +0 -0
  101. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/__init__.py +0 -0
  102. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/base.py +0 -0
  103. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/factory.py +0 -0
  104. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/http.py +0 -0
  105. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/kafka.py +0 -0
  106. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/once.py +0 -0
  107. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/polling.py +0 -0
  108. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/event_listener/webhooks_only.py +0 -0
  109. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/__init__.py +0 -0
  110. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/base.py +0 -0
  111. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  112. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  113. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  114. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  115. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  116. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  117. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  118. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  119. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  120. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  121. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  122. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  123. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/queue/__init__.py +0 -0
  124. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
  125. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/queue/local_queue.py +0 -0
  126. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  127. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  128. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/webhook/__init__.py +0 -0
  129. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
  130. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
  131. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
  132. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/__init__.py +0 -0
  133. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/base.py +0 -0
  134. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  135. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/events.py +0 -0
  136. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
  137. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/live_events.py +0 -0
  138. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
  139. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/integrations/mixins/utils.py +0 -0
  140. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/ocean_types.py +0 -0
  141. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  142. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/core/utils/utils.py +0 -0
  143. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/debug_cli.py +0 -0
  144. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/__init__.py +0 -0
  145. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/api.py +0 -0
  146. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/base.py +0 -0
  147. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/clients.py +0 -0
  148. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/context.py +0 -0
  149. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/port_defaults.py +0 -0
  150. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/utils.py +0 -0
  151. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/exceptions/webhook_processor.py +0 -0
  152. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/helpers/__init__.py +0 -0
  153. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/helpers/async_client.py +0 -0
  154. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/helpers/metric/utils.py +0 -0
  155. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/helpers/retry.py +0 -0
  156. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/log/__init__.py +0 -0
  157. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/log/handlers.py +0 -0
  158. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/log/logger_setup.py +0 -0
  159. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/log/sensetive.py +0 -0
  160. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/middlewares.py +0 -0
  161. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/ocean.py +0 -0
  162. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/py.typed +0 -0
  163. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/run.py +0 -0
  164. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/sonar-project.properties +0 -0
  165. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/__init__.py +0 -0
  166. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/cache/__init__.py +0 -0
  167. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/cache/test_disk_cache.py +0 -0
  168. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/cache/test_memory_cache.py +0 -0
  169. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/clients/__init__.py +0 -0
  170. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/clients/oauth/__init__.py +0 -0
  171. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
  172. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
  173. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/conftest.py +0 -0
  174. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
  175. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
  176. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
  177. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
  178. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
  179. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
  180. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
  181. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
  182. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
  183. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/test_utils.py +0 -0
  184. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  185. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
  186. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/__init__.py +0 -0
  187. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  188. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/fixtures.py +0 -0
  189. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/integration.py +0 -0
  190. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
  191. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/port_client.py +0 -0
  192. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
  193. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/test_metric.py +0 -0
  194. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/test_ocean.py +0 -0
  195. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/test_smoke.py +0 -0
  196. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  197. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/tests/utils/test_cache.py +0 -0
  198. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/__init__.py +0 -0
  199. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/async_http.py +0 -0
  200. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/async_iterators.py +0 -0
  201. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/cache.py +0 -0
  202. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/ipc.py +0 -0
  203. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/misc.py +0 -0
  204. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/queue_utils.py +0 -0
  205. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/repeat.py +0 -0
  206. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/signal.py +0 -0
  207. {port_ocean-0.23.4 → port_ocean-0.24.0}/port_ocean/utils/time.py +0 -0
  208. {port_ocean-0.23.4 → port_ocean-0.24.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.23.4
3
+ Version: 0.24.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
@@ -5,15 +5,11 @@ FROM ${BASE_BUILDER_PYTHON_IMAGE} AS base
5
5
 
6
6
  ARG BUILD_CONTEXT
7
7
  ARG BUILDPLATFORM
8
- ARG PROMETHEUS_MULTIPROC_DIR=/tmp/ocean/prometheus/metrics
9
8
 
10
9
  ENV LIBRDKAFKA_VERSION=2.8.2 \
11
10
  PYTHONUNBUFFERED=1 \
12
11
  POETRY_VIRTUALENVS_IN_PROJECT=1 \
13
- PIP_ROOT_USER_ACTION=ignore \
14
- PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR}
15
-
16
- RUN mkdir -p ${PROMETHEUS_MULTIPROC_DIR}
12
+ PIP_ROOT_USER_ACTION=ignore
17
13
 
18
14
  WORKDIR /app
19
15
 
@@ -25,8 +21,12 @@ FROM ${BASE_RUNNER_PYTHON_IMAGE} AS prod
25
21
 
26
22
  ARG INTEGRATION_VERSION
27
23
  ARG BUILD_CONTEXT
24
+ ARG PROMETHEUS_MULTIPROC_DIR=/tmp/ocean/prometheus/metrics
28
25
 
29
- ENV LIBRDKAFKA_VERSION=2.8.2
26
+ ENV LIBRDKAFKA_VERSION=2.8.2 \
27
+ PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR}
28
+
29
+ RUN mkdir -p ${PROMETHEUS_MULTIPROC_DIR}
30
30
 
31
31
  RUN apt-get update \
32
32
  && apt-get install -y \
@@ -0,0 +1,570 @@
1
+ import asyncio
2
+ from typing import Any, Literal
3
+ from urllib.parse import quote_plus
4
+ import json
5
+
6
+ import httpx
7
+ from loguru import logger
8
+ from port_ocean.context.ocean import ocean
9
+ from port_ocean.clients.port.authentication import PortAuthentication
10
+ from port_ocean.clients.port.types import RequestOptions, UserAgentType
11
+ from port_ocean.clients.port.utils import (
12
+ handle_port_status_code,
13
+ PORT_HTTP_MAX_CONNECTIONS_LIMIT,
14
+ )
15
+ from port_ocean.core.models import (
16
+ BulkUpsertResponse,
17
+ Entity,
18
+ PortAPIErrorMessage,
19
+ )
20
+ from starlette import status
21
+
22
+ from port_ocean.helpers.metric.metric import MetricPhase, MetricType
23
+
24
+ ENTITIES_BULK_SAMPLES_SIZE = 10
25
+ ENTITIES_BULK_ESTIMATED_SIZE_MULTIPLIER = 1.5
26
+ ENTITIES_BULK_MINIMUM_BATCH_SIZE = 1
27
+
28
+
29
+ class EntityClientMixin:
30
+ def __init__(self, auth: PortAuthentication, client: httpx.AsyncClient):
31
+ self.auth = auth
32
+ self.client = client
33
+ # Semaphore is used to limit the number of concurrent requests to port, to avoid overloading it.
34
+ # The number of concurrent requests is set to 90% of the max connections limit, to leave some room for other
35
+ # requests that are not related to entities.
36
+ self.semaphore = asyncio.Semaphore(
37
+ round(0.5 * PORT_HTTP_MAX_CONNECTIONS_LIMIT)
38
+ ) # 50% of the max connections limit in order to avoid overloading port
39
+
40
+ def calculate_entities_batch_size(self, entities: list[Entity]) -> int:
41
+ """
42
+ Calculate the optimal batch size based on entity size and configured limits.
43
+
44
+ Args:
45
+ entities: List of entities to calculate batch size for
46
+
47
+ Returns:
48
+ int: The optimal batch size to use
49
+ """
50
+ if not entities:
51
+ return ENTITIES_BULK_MINIMUM_BATCH_SIZE
52
+
53
+ # Calculate average entity size from a sample
54
+ SAMPLE_SIZE = min(ENTITIES_BULK_SAMPLES_SIZE, len(entities))
55
+ sample_entities = entities[:SAMPLE_SIZE]
56
+ average_entity_size = (
57
+ sum(
58
+ len(json.dumps(entity.dict(exclude_unset=True, by_alias=True)).encode())
59
+ for entity in sample_entities
60
+ )
61
+ / SAMPLE_SIZE
62
+ )
63
+
64
+ # Use a conservative estimate to ensure we stay under the limit
65
+ estimated_entity_size = int(
66
+ average_entity_size * ENTITIES_BULK_ESTIMATED_SIZE_MULTIPLIER
67
+ )
68
+ max_entities_per_batch = min(
69
+ ocean.config.upsert_entities_batch_max_length,
70
+ ocean.config.upsert_entities_batch_max_size_in_bytes
71
+ // estimated_entity_size,
72
+ )
73
+
74
+ return max(ENTITIES_BULK_MINIMUM_BATCH_SIZE, max_entities_per_batch)
75
+
76
+ async def upsert_entity(
77
+ self,
78
+ entity: Entity,
79
+ request_options: RequestOptions,
80
+ user_agent_type: UserAgentType | None = None,
81
+ should_raise: bool = True,
82
+ ) -> Entity | None | Literal[False]:
83
+ """
84
+ This function upserts an entity into Port.
85
+
86
+ Usage:
87
+ ```python
88
+ upsertedEntity = await self.context.port_client.upsert_entity(
89
+ entity,
90
+ event.port_app_config.get_port_request_options(),
91
+ user_agent_type,
92
+ should_raise=False,
93
+ )
94
+ ```
95
+ :param entity: An Entity to be upserted
96
+ :param request_options: A dictionary specifying how to upsert the entity
97
+ :param user_agent_type: a UserAgentType specifying who is preforming the action
98
+ :param should_raise: A boolean specifying whether the error should be raised or handled silently
99
+ :return: [Entity] if the upsert occured successfully
100
+ :return: [None] will be returned if entity is using search identifier
101
+ :return: [False] will be returned if upsert failed because of unmet dependency
102
+ """
103
+ validation_only = request_options["validation_only"]
104
+ async with self.semaphore:
105
+ logger.debug(
106
+ f"{'Validating' if validation_only else 'Upserting'} entity: {entity.identifier} of blueprint: {entity.blueprint}"
107
+ )
108
+ headers = await self.auth.headers(user_agent_type)
109
+ response = await self.client.post(
110
+ f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities",
111
+ json=entity.dict(exclude_unset=True, by_alias=True),
112
+ headers=headers,
113
+ params={
114
+ "upsert": "true",
115
+ "merge": str(request_options["merge"]).lower(),
116
+ "create_missing_related_entities": str(
117
+ request_options["create_missing_related_entities"]
118
+ ).lower(),
119
+ "validation_only": str(validation_only).lower(),
120
+ },
121
+ extensions={"retryable": True},
122
+ )
123
+ if response.is_error:
124
+ logger.error(
125
+ f"Error {'Validating' if validation_only else 'Upserting'} "
126
+ f"entity: {entity.identifier} of "
127
+ f"blueprint: {entity.blueprint}"
128
+ )
129
+ result = response.json()
130
+ ocean.metrics.inc_metric(
131
+ name=MetricType.OBJECT_COUNT_NAME,
132
+ labels=[
133
+ ocean.metrics.current_resource_kind(),
134
+ MetricPhase.LOAD,
135
+ MetricPhase.LoadResult.FAILED,
136
+ ],
137
+ value=1,
138
+ )
139
+
140
+ if (
141
+ response.status_code == status.HTTP_404_NOT_FOUND
142
+ and not result.get("ok")
143
+ and result.get("error") == PortAPIErrorMessage.NOT_FOUND.value
144
+ ):
145
+ # Return false to differentiate from `result_entity.is_using_search_identifier`
146
+ return False
147
+ else:
148
+ ocean.metrics.inc_metric(
149
+ name=MetricType.OBJECT_COUNT_NAME,
150
+ labels=[
151
+ ocean.metrics.current_resource_kind(),
152
+ MetricPhase.LOAD,
153
+ MetricPhase.LoadResult.LOADED,
154
+ ],
155
+ value=1,
156
+ )
157
+
158
+ handle_port_status_code(response, should_raise)
159
+ result = response.json()
160
+
161
+ result_entity = (
162
+ Entity.parse_obj(result["entity"]) if result.get("entity") else entity
163
+ )
164
+
165
+ # Happens when upsert fails and search identifier is defined.
166
+ # We return None to ignore the entity later in the delete process
167
+ if result_entity.is_using_search_identifier:
168
+ return None
169
+ return self._reduce_entity(result_entity)
170
+
171
+ async def upsert_entities_bulk(
172
+ self,
173
+ blueprint: str,
174
+ entities: list[Entity],
175
+ request_options: RequestOptions,
176
+ user_agent_type: UserAgentType | None = None,
177
+ should_raise: bool = True,
178
+ ) -> list[tuple[bool | None, Entity]] | httpx.HTTPStatusError:
179
+ """
180
+ This function upserts a list of entities into Port.
181
+
182
+ Usage:
183
+ ```python
184
+ upsertedEntities = await self.context.port_client.upsert_entities_batch(
185
+ entities,
186
+ event.port_app_config.get_port_request_options(),
187
+ user_agent_type,
188
+ should_raise=False,
189
+ )
190
+ ```
191
+ :param blueprint: The blueprint of the entities to be upserted
192
+ :param entities: A list of Entities to be upserted
193
+ :param request_options: A dictionary specifying how to upsert the entity
194
+ :param user_agent_type: a UserAgentType specifying who is preforming the action
195
+ :param should_raise: A boolean specifying whether the error should be raised or handled silently
196
+ :return: A list of tuples where each tuple contains:
197
+ - First value: True if entity was created successfully, False if there was an error, None if there was an error and the entity use search identifier
198
+ - Second value: The original entity (if failed) or the reduced entity with updated identifier (if successful)
199
+ :return: httpx.HTTPStatusError if there was an HTTP error and should_raise is False
200
+ """
201
+ validation_only = request_options["validation_only"]
202
+ async with self.semaphore:
203
+ logger.debug(
204
+ f"{'Validating' if validation_only else 'Upserting'} {len(entities)} of blueprint: {blueprint}"
205
+ )
206
+ headers = await self.auth.headers(user_agent_type)
207
+ response = await self.client.post(
208
+ f"{self.auth.api_url}/blueprints/{blueprint}/entities/bulk",
209
+ json={
210
+ "entities": [
211
+ entity.dict(exclude_unset=True, by_alias=True)
212
+ for entity in entities
213
+ ]
214
+ },
215
+ headers=headers,
216
+ params={
217
+ "upsert": "true",
218
+ "merge": str(request_options["merge"]).lower(),
219
+ "create_missing_related_entities": str(
220
+ request_options["create_missing_related_entities"]
221
+ ).lower(),
222
+ "validation_only": str(validation_only).lower(),
223
+ },
224
+ extensions={"retryable": True},
225
+ )
226
+ if response.is_error:
227
+ logger.error(
228
+ f"Error {'Validating' if validation_only else 'Upserting'} "
229
+ f"{len(entities)} entities of "
230
+ f"blueprint: {blueprint}"
231
+ )
232
+ handle_port_status_code(response, should_raise)
233
+ return httpx.HTTPStatusError(
234
+ f"HTTP {response.status_code}",
235
+ request=response.request,
236
+ response=response,
237
+ )
238
+ handle_port_status_code(response, should_raise)
239
+ result = response.json()
240
+
241
+ return self._parse_upsert_entities_batch_response(entities, result)
242
+
243
+ def _parse_upsert_entities_batch_response(
244
+ self,
245
+ entities: list[Entity],
246
+ result: BulkUpsertResponse,
247
+ ) -> list[tuple[bool | None, Entity]]:
248
+ """
249
+ Parse the response from a bulk upsert operation and map it to the original entities.
250
+
251
+ :param entities: The original entities
252
+ :param result: The response from the bulk upsert operation
253
+ :return: A list of tuples containing the success status and the entity
254
+ """
255
+ index_to_entity = {i: entity for i, entity in enumerate(entities)}
256
+ successful_entities = {
257
+ entity_result["index"]: entity_result
258
+ for entity_result in result.get("entities", [])
259
+ }
260
+ error_entities = {error["index"]: error for error in result.get("errors", [])}
261
+
262
+ batch_results: list[tuple[bool | None, Entity]] = []
263
+ for entity_index, original_entity in index_to_entity.items():
264
+ reduced_entity = self._reduce_entity(original_entity)
265
+ if entity_index in successful_entities:
266
+ ocean.metrics.inc_metric(
267
+ name=MetricType.OBJECT_COUNT_NAME,
268
+ labels=[
269
+ ocean.metrics.current_resource_kind(),
270
+ MetricPhase.LOAD,
271
+ MetricPhase.LoadResult.LOADED,
272
+ ],
273
+ value=1,
274
+ )
275
+ success_entity = successful_entities[entity_index]
276
+ # Create a copy of the original entity with the new identifier
277
+ updated_entity = reduced_entity.copy()
278
+ updated_entity.identifier = success_entity["identifier"]
279
+ batch_results.append((True, updated_entity))
280
+ elif entity_index in error_entities:
281
+ ocean.metrics.inc_metric(
282
+ name=MetricType.OBJECT_COUNT_NAME,
283
+ labels=[
284
+ ocean.metrics.current_resource_kind(),
285
+ MetricPhase.LOAD,
286
+ MetricPhase.LoadResult.FAILED,
287
+ ],
288
+ value=1,
289
+ )
290
+ error = error_entities[entity_index]
291
+ if (
292
+ error.get("identifier") == "unknown"
293
+ ): # when using the search identifier we might not have an actual identifier
294
+ batch_results.append((None, reduced_entity))
295
+ else:
296
+ batch_results.append((False, reduced_entity))
297
+ else:
298
+ batch_results.append((False, reduced_entity))
299
+
300
+ return batch_results
301
+
302
+ async def _upsert_entities_batch_individually(
303
+ self,
304
+ entities: list[Entity],
305
+ request_options: RequestOptions,
306
+ user_agent_type: UserAgentType | None = None,
307
+ should_raise: bool = True,
308
+ ) -> list[tuple[bool, Entity]]:
309
+ entities_results: list[tuple[bool, Entity]] = []
310
+ modified_entities_results = await asyncio.gather(
311
+ *(
312
+ self.upsert_entity(
313
+ entity,
314
+ request_options,
315
+ user_agent_type,
316
+ should_raise=should_raise,
317
+ )
318
+ for entity in entities
319
+ ),
320
+ return_exceptions=True,
321
+ )
322
+
323
+ for original_entity, single_result in zip(entities, modified_entities_results):
324
+ if isinstance(single_result, Exception) and should_raise:
325
+ raise single_result
326
+ elif isinstance(single_result, Entity):
327
+ entities_results.append((True, single_result))
328
+ elif single_result is False:
329
+ entities_results.append((False, original_entity))
330
+
331
+ return entities_results
332
+
333
+ async def upsert_entities_in_batches(
334
+ self,
335
+ entities: list[Entity],
336
+ request_options: RequestOptions,
337
+ user_agent_type: UserAgentType | None = None,
338
+ should_raise: bool = True,
339
+ ) -> list[tuple[bool, Entity]]:
340
+ """
341
+ This function upserts a list of entities into Port in batches.
342
+ The batch size is calculated based on both the number of entities and their size.
343
+ Batches are processed in parallel using asyncio.gather, with concurrency controlled by the semaphore.
344
+
345
+ :param entities: A list of Entities to be upserted
346
+ :param request_options: A dictionary specifying how to upsert the entity
347
+ :param user_agent_type: a UserAgentType specifying who is preforming the action
348
+ :param should_raise: A boolean specifying whether the error should be raised or handled silently
349
+ :return: A list of tuples where each tuple contains:
350
+ - First value: True if entity was created successfully, False if there was an error
351
+ - Second value: The reduced entity with updated identifier (if successful) or the original entity (if failed)
352
+ """
353
+ entities_results: list[tuple[bool, Entity]] = []
354
+ blueprint = entities[0].blueprint
355
+
356
+ if ocean.config.bulk_upserts_enabled:
357
+ bulk_size = self.calculate_entities_batch_size(entities)
358
+ bulks = [
359
+ entities[i : i + bulk_size] for i in range(0, len(entities), bulk_size)
360
+ ]
361
+
362
+ bulk_results = await asyncio.gather(
363
+ *(
364
+ self.upsert_entities_bulk(
365
+ blueprint,
366
+ bulk,
367
+ request_options,
368
+ user_agent_type,
369
+ should_raise=should_raise,
370
+ )
371
+ for bulk in bulks
372
+ ),
373
+ return_exceptions=True,
374
+ )
375
+
376
+ for bulk, bulk_result in zip(bulks, bulk_results):
377
+ if isinstance(bulk_result, httpx.HTTPStatusError) or isinstance(
378
+ bulk_result, Exception
379
+ ):
380
+ if should_raise:
381
+ raise bulk_result
382
+ # If should_raise is False, retry batch in sequential order as a fallback only for 413 errors
383
+ if (
384
+ isinstance(bulk_result, httpx.HTTPStatusError)
385
+ and bulk_result.response.status_code == 413
386
+ ):
387
+ individual_upsert_results = (
388
+ await self._upsert_entities_batch_individually(
389
+ bulk, request_options, user_agent_type, should_raise
390
+ )
391
+ )
392
+ entities_results.extend(individual_upsert_results)
393
+ else:
394
+ # For other errors, mark all entities in the batch as failed
395
+ for entity in bulk:
396
+ failed_result: tuple[bool, Entity] = (
397
+ False,
398
+ self._reduce_entity(entity),
399
+ )
400
+ entities_results.append(failed_result)
401
+ elif isinstance(bulk_result, list):
402
+ for status, entity in bulk_result:
403
+ if (
404
+ status is not None
405
+ ): # when using the search identifier we might not have an actual identifier
406
+ bulk_result_tuple: tuple[bool, Entity] = (
407
+ bool(status),
408
+ entity,
409
+ )
410
+ entities_results.append(bulk_result_tuple)
411
+ else:
412
+ individual_upsert_results = await self._upsert_entities_batch_individually(
413
+ entities, request_options, user_agent_type, should_raise
414
+ )
415
+ entities_results.extend(individual_upsert_results)
416
+
417
+ return entities_results
418
+
419
+ async def delete_entity(
420
+ self,
421
+ entity: Entity,
422
+ request_options: RequestOptions,
423
+ user_agent_type: UserAgentType | None = None,
424
+ should_raise: bool = True,
425
+ ) -> None:
426
+ async with self.semaphore:
427
+ logger.info(
428
+ f"Delete entity: {entity.identifier} of blueprint: {entity.blueprint}"
429
+ )
430
+ response = await self.client.delete(
431
+ f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities/{quote_plus(entity.identifier)}",
432
+ headers=await self.auth.headers(user_agent_type),
433
+ params={
434
+ "delete_dependents": str(
435
+ request_options["delete_dependent_entities"]
436
+ ).lower()
437
+ },
438
+ )
439
+
440
+ if response.is_error:
441
+ if response.status_code == 404:
442
+ logger.info(
443
+ f"Failed to delete entity: {entity.identifier} of blueprint: {entity.blueprint},"
444
+ f" as it was already deleted from port"
445
+ )
446
+ return
447
+ logger.error(
448
+ f"Error deleting "
449
+ f"entity: {entity.identifier} of "
450
+ f"blueprint: {entity.blueprint}"
451
+ )
452
+
453
+ handle_port_status_code(response, should_raise)
454
+
455
+ async def batch_delete_entities(
456
+ self,
457
+ entities: list[Entity],
458
+ request_options: RequestOptions,
459
+ user_agent_type: UserAgentType | None = None,
460
+ should_raise: bool = True,
461
+ ) -> None:
462
+ await asyncio.gather(
463
+ *(
464
+ self.delete_entity(
465
+ entity,
466
+ request_options,
467
+ user_agent_type,
468
+ should_raise=should_raise,
469
+ )
470
+ for entity in entities
471
+ ),
472
+ return_exceptions=True,
473
+ )
474
+
475
+ async def search_entities(
476
+ self,
477
+ user_agent_type: UserAgentType,
478
+ query: dict[Any, Any] | None = None,
479
+ parameters_to_include: list[str] | None = None,
480
+ ) -> list[Entity]:
481
+ default_query = {
482
+ "combinator": "and",
483
+ "rules": [
484
+ {
485
+ "property": "$datasource",
486
+ "operator": "contains",
487
+ "value": f"port-ocean/{self.auth.integration_type}/",
488
+ },
489
+ {
490
+ "property": "$datasource",
491
+ "operator": "contains",
492
+ "value": f"/{self.auth.integration_identifier}/{user_agent_type.value}",
493
+ },
494
+ ],
495
+ }
496
+
497
+ if query is None:
498
+ query = default_query
499
+ elif query.get("rules"):
500
+ query["rules"].append(default_query)
501
+
502
+ logger.info(f"Searching entities with query {query}")
503
+ response = await self.client.post(
504
+ f"{self.auth.api_url}/entities/search",
505
+ json=query,
506
+ headers=await self.auth.headers(user_agent_type),
507
+ params={
508
+ "exclude_calculated_properties": "true",
509
+ "include": parameters_to_include or ["blueprint", "identifier"],
510
+ },
511
+ extensions={"retryable": True},
512
+ )
513
+ handle_port_status_code(response)
514
+ return [Entity.parse_obj(result) for result in response.json()["entities"]]
515
+
516
+ async def search_batch_entities(
517
+ self, user_agent_type: UserAgentType, entities_to_search: list[Entity]
518
+ ) -> list[Entity]:
519
+ search_rules = []
520
+ for entity in entities_to_search:
521
+ search_rules.append(
522
+ {
523
+ "combinator": "and",
524
+ "rules": [
525
+ {
526
+ "property": "$identifier",
527
+ "operator": "=",
528
+ "value": entity.identifier,
529
+ },
530
+ {
531
+ "property": "$blueprint",
532
+ "operator": "=",
533
+ "value": entity.blueprint,
534
+ },
535
+ ],
536
+ }
537
+ )
538
+
539
+ return await self.search_entities(
540
+ user_agent_type,
541
+ {
542
+ "combinator": "and",
543
+ "rules": [{"combinator": "or", "rules": search_rules}],
544
+ },
545
+ )
546
+
547
+ @staticmethod
548
+ def _reduce_entity(entity: Entity) -> Entity:
549
+ """
550
+ Reduces an entity to only keep identifier, blueprint and processed relations.
551
+ This helps save memory by removing unnecessary data.
552
+
553
+ Args:
554
+ entity: The entity to reduce
555
+
556
+ Returns:
557
+ Entity: A new entity with only the essential data
558
+ """
559
+ reduced_entity = Entity(
560
+ identifier=entity.identifier, blueprint=entity.blueprint
561
+ )
562
+
563
+ # Turning dict typed relations (raw search relations) is required
564
+ # for us to be able to successfully calculate the participation related entities
565
+ # and ignore the ones that don't as they weren't upserted
566
+ reduced_entity.relations = {
567
+ key: None if isinstance(relation, dict) else relation
568
+ for key, relation in entity.relations.items()
569
+ }
570
+ return reduced_entity
@@ -51,7 +51,7 @@ def default_config_factory(configurations: Any) -> Type[BaseModel]:
51
51
  case _:
52
52
  raise ValueError(f"Unknown type: {config.type}")
53
53
 
54
- default = ... if config.required else None
54
+ default: Any = ... if config.required else None
55
55
  if config.default is not None:
56
56
  default = parse_obj_as(field_type, config.default)
57
57
  fields[decamelize(config.name)] = (
@@ -101,6 +101,10 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
101
101
  caching_storage_mode: Optional[CachingStorageMode] = Field(default=None)
102
102
  process_execution_mode: Optional[ProcessExecutionMode] = Field(default=None)
103
103
 
104
+ upsert_entities_batch_max_length: int = 20
105
+ upsert_entities_batch_max_size_in_bytes: int = 1024 * 1024
106
+ bulk_upserts_enabled: bool = False
107
+
104
108
  @validator("metrics", pre=True)
105
109
  def validate_metrics(cls, v: Any) -> MetricsSettings | dict[str, Any] | None:
106
110
  if v is None:
@@ -127,7 +127,7 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
127
127
  modified_entities: list[Entity] = []
128
128
  upserted_entities: list[tuple[bool, Entity]] = []
129
129
 
130
- upserted_entities = await self.context.port_client.batch_upsert_entities(
130
+ upserted_entities = await self.context.port_client.upsert_entities_in_batches(
131
131
  entities,
132
132
  event.port_app_config.get_port_request_options(),
133
133
  user_agent_type,
@@ -33,7 +33,7 @@ from port_ocean.core.ocean_types import (
33
33
  CalculationResult,
34
34
  )
35
35
  from port_ocean.core.utils.utils import resolve_entities_diff, zip_and_sum, gather_and_split_errors_from_results
36
- from port_ocean.exceptions.core import OceanAbortException
36
+ from port_ocean.exceptions.core import IntegrationSubProcessFailedException, OceanAbortException
37
37
  from port_ocean.helpers.metric.metric import MetricResourceKind, SyncState, MetricType, MetricPhase
38
38
  from port_ocean.helpers.metric.utils import TimeMetric, TimeMetricWithResourceKind
39
39
  from port_ocean.utils.ipc import FileIPC
@@ -626,14 +626,13 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
626
626
  id = uuid.uuid4()
627
627
  logger.info(f"Starting subprocess with id {id}")
628
628
  file_ipc_map = {
629
- "process_resource": FileIPC(id, "process_resource",([],[])),
629
+ "process_resource": FileIPC(id, "process_resource",([],[IntegrationSubProcessFailedException(f"Subprocess failed for {resource.kind} with index {index}")])),
630
630
  "topological_entities": FileIPC(id, "topological_entities",[]),
631
631
  }
632
632
  process = ProcessWrapper(target=self.process_resource_in_subprocess, args=(file_ipc_map,resource,index,user_agent_type))
633
633
  process.start()
634
634
  await process.join_async()
635
635
 
636
-
637
636
  event.entity_topological_sorter.entities.extend(file_ipc_map["topological_entities"].load())
638
637
  return file_ipc_map["process_resource"].load()
639
638