port-ocean 0.23.3__tar.gz → 0.23.5__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 (207) hide show
  1. {port_ocean-0.23.3 → port_ocean-0.23.5}/PKG-INFO +1 -1
  2. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.Deb +6 -6
  3. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/mixins/integrations.py +3 -2
  4. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/config/dynamic.py +1 -1
  5. port_ocean-0.23.5/port_ocean/context/metric_resource.py +63 -0
  6. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/resync_state_updater/updater.py +3 -3
  7. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/sync_raw.py +108 -66
  8. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/core.py +4 -0
  9. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/helpers/metric/metric.py +35 -11
  10. port_ocean-0.23.5/port_ocean/helpers/metric/utils.py +52 -0
  11. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +29 -5
  12. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/log/test_handlers.py +2 -1
  13. {port_ocean-0.23.3 → port_ocean-0.23.5}/pyproject.toml +1 -1
  14. port_ocean-0.23.3/port_ocean/helpers/metric/utils.py +0 -28
  15. {port_ocean-0.23.3 → port_ocean-0.23.5}/LICENSE.md +0 -0
  16. {port_ocean-0.23.3 → port_ocean-0.23.5}/README.md +0 -0
  17. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.alpine +0 -0
  18. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.base.builder +0 -0
  19. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.base.runner +0 -0
  20. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.dockerignore +0 -0
  21. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Dockerfile.local +0 -0
  22. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/Makefile +0 -0
  23. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/entry_local.sh +0 -0
  24. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/grpcio.sh +0 -0
  25. {port_ocean-0.23.3 → port_ocean-0.23.5}/integrations/_infra/init.sh +0 -0
  26. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/__init__.py +0 -0
  27. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/bootstrap.py +0 -0
  28. {port_ocean-0.23.3/port_ocean/tests/helpers → port_ocean-0.23.5/port_ocean/cache}/__init__.py +0 -0
  29. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cache/base.py +0 -0
  30. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cache/disk.py +0 -0
  31. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cache/errors.py +0 -0
  32. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cache/memory.py +0 -0
  33. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/__init__.py +0 -0
  34. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cli.py +0 -0
  35. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/__init__.py +0 -0
  36. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  37. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/defaults/clean.py +0 -0
  38. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/defaults/dock.py +0 -0
  39. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/defaults/group.py +0 -0
  40. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/list_integrations.py +0 -0
  41. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/main.py +0 -0
  42. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/new.py +0 -0
  43. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/pull.py +0 -0
  44. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/sail.py +0 -0
  45. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/commands/version.py +0 -0
  46. {port_ocean-0.23.3/port_ocean/tests/clients/oauth → port_ocean-0.23.5/port_ocean/cli/cookiecutter}/__init__.py +0 -0
  47. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  48. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  49. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  50. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  51. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  52. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  53. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  54. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  55. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  56. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  57. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  58. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  59. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  60. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  61. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  62. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  63. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  64. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  65. {port_ocean-0.23.3/port_ocean/tests/clients → port_ocean-0.23.5/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests}/__init__.py +0 -0
  66. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  67. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/cli/utils.py +0 -0
  68. {port_ocean-0.23.3/port_ocean/tests → port_ocean-0.23.5/port_ocean/clients}/__init__.py +0 -0
  69. {port_ocean-0.23.3/port_ocean/log → port_ocean-0.23.5/port_ocean/clients/auth}/__init__.py +0 -0
  70. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/auth/auth_client.py +0 -0
  71. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/auth/oauth_client.py +0 -0
  72. {port_ocean-0.23.3/port_ocean/helpers → port_ocean-0.23.5/port_ocean/clients/port}/__init__.py +0 -0
  73. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/authentication.py +0 -0
  74. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/client.py +0 -0
  75. {port_ocean-0.23.3/port_ocean/exceptions → port_ocean-0.23.5/port_ocean/clients/port/mixins}/__init__.py +0 -0
  76. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  77. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/mixins/entities.py +0 -0
  78. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/mixins/migrations.py +0 -0
  79. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/mixins/organization.py +0 -0
  80. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/retry_transport.py +0 -0
  81. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/types.py +0 -0
  82. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/clients/port/utils.py +0 -0
  83. {port_ocean-0.23.3/port_ocean/core/integrations → port_ocean-0.23.5/port_ocean/config}/__init__.py +0 -0
  84. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/config/base.py +0 -0
  85. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/config/settings.py +0 -0
  86. {port_ocean-0.23.3/port_ocean/core/handlers/webhook → port_ocean-0.23.5/port_ocean/consumers}/__init__.py +0 -0
  87. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/consumers/kafka_consumer.py +0 -0
  88. {port_ocean-0.23.3/port_ocean/core/handlers/entities_state_applier/port → port_ocean-0.23.5/port_ocean/context}/__init__.py +0 -0
  89. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/context/event.py +0 -0
  90. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/context/ocean.py +0 -0
  91. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/context/resource.py +0 -0
  92. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/__init__.py +0 -0
  93. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/defaults/__init__.py +0 -0
  94. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/defaults/clean.py +0 -0
  95. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/defaults/common.py +0 -0
  96. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/defaults/initialize.py +0 -0
  97. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/__init__.py +0 -0
  98. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/base.py +0 -0
  99. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/factory.py +0 -0
  100. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/http.py +0 -0
  101. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/kafka.py +0 -0
  102. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/once.py +0 -0
  103. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/polling.py +0 -0
  104. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/event_listener/webhooks_only.py +0 -0
  105. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/__init__.py +0 -0
  106. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/base.py +0 -0
  107. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  108. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  109. {port_ocean-0.23.3/port_ocean/context → port_ocean-0.23.5/port_ocean/core/handlers/entities_state_applier/port}/__init__.py +0 -0
  110. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  111. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  112. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  113. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  114. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  115. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  116. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  117. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  118. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  119. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  120. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/queue/__init__.py +0 -0
  121. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/queue/abstract_queue.py +0 -0
  122. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/queue/local_queue.py +0 -0
  123. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  124. {port_ocean-0.23.3/port_ocean/consumers → port_ocean-0.23.5/port_ocean/core/handlers/webhook}/__init__.py +0 -0
  125. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/webhook/abstract_webhook_processor.py +0 -0
  126. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/webhook/processor_manager.py +0 -0
  127. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/handlers/webhook/webhook_event.py +0 -0
  128. {port_ocean-0.23.3/port_ocean/config → port_ocean-0.23.5/port_ocean/core/integrations}/__init__.py +0 -0
  129. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/base.py +0 -0
  130. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  131. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/events.py +0 -0
  132. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/handler.py +0 -0
  133. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/live_events.py +0 -0
  134. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/sync.py +0 -0
  135. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/integrations/mixins/utils.py +0 -0
  136. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/models.py +0 -0
  137. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/ocean_types.py +0 -0
  138. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  139. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/core/utils/utils.py +0 -0
  140. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/debug_cli.py +0 -0
  141. {port_ocean-0.23.3/port_ocean/clients/port/mixins → port_ocean-0.23.5/port_ocean/exceptions}/__init__.py +0 -0
  142. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/api.py +0 -0
  143. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/base.py +0 -0
  144. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/clients.py +0 -0
  145. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/context.py +0 -0
  146. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/port_defaults.py +0 -0
  147. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/utils.py +0 -0
  148. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/exceptions/webhook_processor.py +0 -0
  149. {port_ocean-0.23.3/port_ocean/clients/port → port_ocean-0.23.5/port_ocean/helpers}/__init__.py +0 -0
  150. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/helpers/async_client.py +0 -0
  151. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/helpers/retry.py +0 -0
  152. {port_ocean-0.23.3/port_ocean/clients/auth → port_ocean-0.23.5/port_ocean/log}/__init__.py +0 -0
  153. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/log/handlers.py +0 -0
  154. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/log/logger_setup.py +0 -0
  155. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/log/sensetive.py +0 -0
  156. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/middlewares.py +0 -0
  157. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/ocean.py +0 -0
  158. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/py.typed +0 -0
  159. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/run.py +0 -0
  160. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/sonar-project.properties +0 -0
  161. {port_ocean-0.23.3/port_ocean/clients → port_ocean-0.23.5/port_ocean/tests}/__init__.py +0 -0
  162. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/cache/__init__.py +0 -0
  163. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/cache/test_disk_cache.py +0 -0
  164. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/cache/test_memory_cache.py +0 -0
  165. {port_ocean-0.23.3/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests → port_ocean-0.23.5/port_ocean/tests/clients}/__init__.py +0 -0
  166. {port_ocean-0.23.3/port_ocean/cli/cookiecutter → port_ocean-0.23.5/port_ocean/tests/clients/oauth}/__init__.py +0 -0
  167. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/clients/oauth/test_oauth_client.py +0 -0
  168. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  169. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/clients/port/mixins/test_organization_mixin.py +0 -0
  170. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/conftest.py +0 -0
  171. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/conftest.py +0 -0
  172. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/defaults/test_common.py +0 -0
  173. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/entities_state_applier/test_applier.py +0 -0
  174. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
  175. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/mixins/test_live_events.py +0 -0
  176. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/port_app_config/test_api.py +0 -0
  177. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/port_app_config/test_base.py +0 -0
  178. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/queue/test_local_queue.py +0 -0
  179. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/webhook/test_abstract_webhook_processor.py +0 -0
  180. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/webhook/test_processor_manager.py +0 -0
  181. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/handlers/webhook/test_webhook_event.py +0 -0
  182. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/test_utils.py +0 -0
  183. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  184. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/core/utils/test_resolve_entities_diff.py +0 -0
  185. {port_ocean-0.23.3/port_ocean/cache → port_ocean-0.23.5/port_ocean/tests/helpers}/__init__.py +0 -0
  186. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  187. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/fixtures.py +0 -0
  188. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/integration.py +0 -0
  189. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/ocean_app.py +0 -0
  190. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/port_client.py +0 -0
  191. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/helpers/smoke_test.py +0 -0
  192. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/test_metric.py +0 -0
  193. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/test_ocean.py +0 -0
  194. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/test_smoke.py +0 -0
  195. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  196. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/tests/utils/test_cache.py +0 -0
  197. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/__init__.py +0 -0
  198. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/async_http.py +0 -0
  199. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/async_iterators.py +0 -0
  200. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/cache.py +0 -0
  201. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/ipc.py +0 -0
  202. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/misc.py +0 -0
  203. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/queue_utils.py +0 -0
  204. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/repeat.py +0 -0
  205. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/signal.py +0 -0
  206. {port_ocean-0.23.3 → port_ocean-0.23.5}/port_ocean/utils/time.py +0 -0
  207. {port_ocean-0.23.3 → port_ocean-0.23.5}/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.3
3
+ Version: 0.23.5
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 \
@@ -214,8 +214,9 @@ class IntegrationClientMixin:
214
214
  logger.debug("starting POST metrics request", metrics=metrics)
215
215
  metrics_attributes = await self.get_metrics_attributes()
216
216
  headers = await self.auth.headers()
217
+ url = metrics_attributes["ingestUrl"] + "/syncMetrics"
217
218
  response = await self.client.post(
218
- metrics_attributes["ingestUrl"],
219
+ url,
219
220
  headers=headers,
220
221
  json={
221
222
  "syncKindsMetrics": metrics,
@@ -229,7 +230,7 @@ class IntegrationClientMixin:
229
230
  metrics_attributes = await self.get_metrics_attributes()
230
231
  url = (
231
232
  metrics_attributes["ingestUrl"]
232
- + f"/resync/{kind_metrics['eventId']}/kind/{kind_metrics['kindIdentifier']}"
233
+ + f"/syncMetrics/resync/{kind_metrics['eventId']}/kind/{kind_metrics['kindIdentifier']}"
233
234
  )
234
235
  headers = await self.auth.headers()
235
236
  response = await self.client.put(
@@ -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)] = (
@@ -0,0 +1,63 @@
1
+ from contextlib import asynccontextmanager
2
+ from dataclasses import dataclass
3
+ from typing import AsyncIterator, TYPE_CHECKING
4
+
5
+ from loguru import logger
6
+ from werkzeug.local import LocalStack, LocalProxy
7
+
8
+ from port_ocean.exceptions.context import (
9
+ ResourceContextNotFoundError,
10
+ )
11
+
12
+ if TYPE_CHECKING:
13
+ pass
14
+
15
+
16
+ @dataclass
17
+ class MetricResourceContext:
18
+ """
19
+ The metric resource context is a context manager that allows you to access the current metric resource if there is one.
20
+ This is useful for getting the metric resource kind
21
+ """
22
+
23
+ metric_resource_kind: str
24
+ index: int
25
+
26
+ @property
27
+ def kind(self) -> str:
28
+ return self.metric_resource_kind
29
+
30
+
31
+ _resource_context_stack: LocalStack[MetricResourceContext] = LocalStack()
32
+
33
+
34
+ def _get_metric_resource_context() -> MetricResourceContext:
35
+ """
36
+ Get the context from the current thread.
37
+ """
38
+ top_resource_context = _resource_context_stack.top
39
+ if top_resource_context is None:
40
+ raise ResourceContextNotFoundError(
41
+ "You must be inside an metric resource context in order to use it"
42
+ )
43
+
44
+ return top_resource_context
45
+
46
+
47
+ metric_resource: MetricResourceContext = LocalProxy(lambda: _get_metric_resource_context()) # type: ignore
48
+
49
+
50
+ @asynccontextmanager
51
+ async def metric_resource_context(
52
+ metric_resource_kind: str, index: int = 0
53
+ ) -> AsyncIterator[MetricResourceContext]:
54
+ _resource_context_stack.push(
55
+ MetricResourceContext(metric_resource_kind=metric_resource_kind, index=index)
56
+ )
57
+
58
+ with logger.contextualize(
59
+ metric_resource_kind=metric_resource.metric_resource_kind
60
+ ):
61
+ yield metric_resource
62
+
63
+ _resource_context_stack.pop()
@@ -94,6 +94,6 @@ class ResyncStateUpdater:
94
94
  await ocean.metrics.send_metrics_to_webhook(
95
95
  kind=ocean.metrics.current_resource_kind()
96
96
  )
97
- # await ocean.metrics.report_sync_metrics(
98
- # kinds=[ocean.metrics.current_resource_kind()]
99
- # ) # TODO: uncomment this when end points are ready
97
+ await ocean.metrics.report_sync_metrics(
98
+ kinds=[ocean.metrics.current_resource_kind()]
99
+ )
@@ -9,6 +9,7 @@ import httpx
9
9
  from loguru import logger
10
10
  from port_ocean.clients.port.types import UserAgentType
11
11
  from port_ocean.context.event import TriggerType, event_context, EventType, event
12
+ from port_ocean.context.metric_resource import metric_resource_context
12
13
  from port_ocean.context.ocean import ocean
13
14
  from port_ocean.context.resource import resource_context
14
15
  from port_ocean.context import resource
@@ -32,9 +33,9 @@ from port_ocean.core.ocean_types import (
32
33
  CalculationResult,
33
34
  )
34
35
  from port_ocean.core.utils.utils import resolve_entities_diff, zip_and_sum, gather_and_split_errors_from_results
35
- from port_ocean.exceptions.core import OceanAbortException
36
- from port_ocean.helpers.metric.metric import SyncState, MetricType, MetricPhase
37
- from port_ocean.helpers.metric.utils import TimeMetric
36
+ from port_ocean.exceptions.core import IntegrationSubProcessFailedException, OceanAbortException
37
+ from port_ocean.helpers.metric.metric import MetricResourceKind, SyncState, MetricType, MetricPhase
38
+ from port_ocean.helpers.metric.utils import TimeMetric, TimeMetricWithResourceKind
38
39
  from port_ocean.utils.ipc import FileIPC
39
40
 
40
41
  SEND_RAW_DATA_EXAMPLES_AMOUNT = 5
@@ -249,9 +250,16 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
249
250
  labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.SKIPPED],
250
251
  value=len(objects_diff[0].entity_selector_diff.passed) - len(changed_entities)
251
252
  )
252
- await self.entities_state_applier.upsert(
253
+ upserted_entities = await self.entities_state_applier.upsert(
253
254
  changed_entities, user_agent_type
254
255
  )
256
+
257
+ ocean.metrics.set_metric(
258
+ name=MetricType.OBJECT_COUNT_NAME,
259
+ labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.LOADED],
260
+ value=len(upserted_entities)
261
+ )
262
+
255
263
  else:
256
264
  logger.info("Entities in batch didn't changed since last sync, skipping", total_entities=len(objects_diff[0].entity_selector_diff.passed))
257
265
  ocean.metrics.inc_metric(
@@ -265,6 +273,11 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
265
273
  modified_objects = await self.entities_state_applier.upsert(
266
274
  objects_diff[0].entity_selector_diff.passed, user_agent_type
267
275
  )
276
+ ocean.metrics.set_metric(
277
+ name=MetricType.OBJECT_COUNT_NAME,
278
+ labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.LOADED],
279
+ value=len(upserted_entities)
280
+ )
268
281
  else:
269
282
  modified_objects = await self.entities_state_applier.upsert(
270
283
  objects_diff[0].entity_selector_diff.passed, user_agent_type
@@ -613,14 +626,13 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
613
626
  id = uuid.uuid4()
614
627
  logger.info(f"Starting subprocess with id {id}")
615
628
  file_ipc_map = {
616
- "process_resource": FileIPC(id, "process_resource",([],[])),
629
+ "process_resource": FileIPC(id, "process_resource",([],[IntegrationSubProcessFailedException(f"Subprocess failed for {resource.kind} with index {index}")])),
617
630
  "topological_entities": FileIPC(id, "topological_entities",[]),
618
631
  }
619
632
  process = ProcessWrapper(target=self.process_resource_in_subprocess, args=(file_ipc_map,resource,index,user_agent_type))
620
633
  process.start()
621
634
  await process.join_async()
622
635
 
623
-
624
636
  event.entity_topological_sorter.entities.extend(file_ipc_map["topological_entities"].load())
625
637
  return file_ipc_map["process_resource"].load()
626
638
 
@@ -633,16 +645,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
633
645
  async with resource_context(resource,index):
634
646
  resource_kind_id = f"{resource.kind}-{index}"
635
647
  ocean.metrics.sync_state = SyncState.SYNCING
648
+
636
649
  task = asyncio.create_task(
637
650
  self._register_in_batches(resource, user_agent_type)
638
651
  )
639
652
  event.on_abort(lambda: task.cancel())
640
653
  kind_results: tuple[list[Entity], list[Exception]] = await task
641
- ocean.metrics.set_metric(
642
- name=MetricType.OBJECT_COUNT_NAME,
643
- labels=[ocean.metrics.current_resource_kind(), MetricPhase.LOAD, MetricPhase.LoadResult.LOADED],
644
- value=len(kind_results[0])
645
- )
646
654
 
647
655
  if ocean.metrics.sync_state != SyncState.FAILED:
648
656
  ocean.metrics.sync_state = SyncState.COMPLETED
@@ -650,10 +658,88 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
650
658
  await ocean.metrics.send_metrics_to_webhook(
651
659
  kind=resource_kind_id
652
660
  )
653
- # await ocean.metrics.report_kind_sync_metrics(kind=resource_kind_id) # TODO: uncomment this when end points are ready
661
+ await ocean.metrics.report_kind_sync_metrics(kind=resource_kind_id, blueprint=resource.port.entity.mappings.blueprint)
654
662
 
655
663
  return kind_results
656
664
 
665
+ @TimeMetricWithResourceKind(MetricPhase.RESYNC)
666
+ async def resync_reconciliation(
667
+ self,
668
+ creation_results: list[tuple[list[Entity], list[Exception]]],
669
+ did_fetched_current_state: bool,
670
+ user_agent_type: UserAgentType,
671
+ app_config: Any,
672
+ silent: bool = True,
673
+ ) -> None:
674
+ """Handle the reconciliation phase of the resync process.
675
+
676
+ This method handles:
677
+ 1. Sorting and upserting failed entities
678
+ 2. Checking if current state was fetched
679
+ 3. Calculating resync diff
680
+ 4. Handling errors
681
+ 5. Deleting entities that are no longer needed
682
+ 6. Executing resync complete hooks
683
+
684
+ Args:
685
+ creation_results (list[tuple[list[Entity], list[Exception]]]): Results from entity creation
686
+ did_fetched_current_state (bool): Whether the current state was successfully fetched
687
+ user_agent_type (UserAgentType): The type of user agent
688
+ app_config (Any): The application configuration
689
+ silent (bool): Whether to raise exceptions or handle them silently
690
+
691
+ """
692
+ await self.sort_and_upsert_failed_entities(user_agent_type)
693
+
694
+ if not did_fetched_current_state:
695
+ logger.warning(
696
+ "Due to an error before the resync, the previous state of entities at Port is unknown."
697
+ " Skipping delete phase due to unknown initial state."
698
+ )
699
+ return False
700
+
701
+ logger.info("Starting resync diff calculation")
702
+ generated_entities, errors = zip_and_sum(creation_results) or [
703
+ [],
704
+ [],
705
+ ]
706
+
707
+ if errors:
708
+ message = f"Resync failed with {len(errors)} errors, skipping delete phase due to incomplete state"
709
+ error_group = ExceptionGroup(
710
+ message,
711
+ errors,
712
+ )
713
+ if not silent:
714
+ raise error_group
715
+
716
+ logger.error(message, exc_info=error_group)
717
+ return False
718
+
719
+ logger.info(
720
+ f"Running resync diff calculation, number of entities created during sync: {len(generated_entities)}"
721
+ )
722
+ entities_at_port = await ocean.port_client.search_entities(
723
+ user_agent_type
724
+ )
725
+
726
+ await self.entities_state_applier.delete_diff(
727
+ {"before": entities_at_port, "after": generated_entities},
728
+ user_agent_type, app_config.get_entity_deletion_threshold()
729
+ )
730
+
731
+ logger.info("Resync finished successfully")
732
+
733
+ # Execute resync_complete hooks
734
+ if "resync_complete" in self.event_strategy:
735
+ logger.info("Executing resync_complete hooks")
736
+
737
+ for resync_complete_fn in self.event_strategy["resync_complete"]:
738
+ await resync_complete_fn()
739
+
740
+ logger.info("Finished executing resync_complete hooks")
741
+
742
+
657
743
  @TimeMetric(MetricPhase.RESYNC)
658
744
  async def sync_raw_all(
659
745
  self,
@@ -689,8 +775,9 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
689
775
  logger.info(f"Resync will use the following mappings: {app_config.dict()}")
690
776
 
691
777
  kinds = [f"{resource.kind}-{index}" for index, resource in enumerate(app_config.resources)]
778
+ blueprints = [resource.port.entity.mappings.blueprint for resource in app_config.resources]
692
779
  ocean.metrics.initialize_metrics(kinds)
693
- # await ocean.metrics.report_sync_metrics(kinds=kinds) # TODO: uncomment this when end points are ready
780
+ await ocean.metrics.report_sync_metrics(kinds=kinds, blueprints=blueprints)
694
781
 
695
782
  # Clear cache
696
783
  await ocean.app.cache_provider.clear()
@@ -716,65 +803,20 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
716
803
  multiprocessing.set_start_method('fork', True)
717
804
  try:
718
805
  for index,resource in enumerate(app_config.resources):
719
-
720
806
  logger.info(f"Starting processing resource {resource.kind} with index {index}")
721
-
722
807
  creation_results.append(await self.process_resource(resource,index,user_agent_type))
723
-
724
- await self.sort_and_upsert_failed_entities(user_agent_type)
725
-
726
808
  except asyncio.CancelledError as e:
727
809
  logger.warning("Resync aborted successfully, skipping delete phase. This leads to an incomplete state")
728
810
  raise
729
811
  else:
730
- if not did_fetched_current_state:
731
- logger.warning(
732
- "Due to an error before the resync, the previous state of entities at Port is unknown."
733
- " Skipping delete phase due to unknown initial state."
734
- )
735
- return
736
-
737
- logger.info("Starting resync diff calculation")
738
- generated_entities, errors = zip_and_sum(creation_results) or [
739
- [],
740
- [],
741
- ]
742
-
743
- if errors:
744
- message = f"Resync failed with {len(errors)} errors, skipping delete phase due to incomplete state"
745
- error_group = ExceptionGroup(
746
- message,
747
- errors,
748
- )
749
- if not silent:
750
- raise error_group
751
-
752
- logger.error(message, exc_info=error_group)
753
- return False
754
- else:
755
- logger.info(
756
- f"Running resync diff calculation, number of entities created during sync: {len(generated_entities)}"
757
- )
758
- entities_at_port = await ocean.port_client.search_entities(
759
- user_agent_type
760
- )
761
- await self.entities_state_applier.delete_diff(
762
- {"before": entities_at_port, "after": generated_entities},
763
- user_agent_type, app_config.get_entity_deletion_threshold()
764
- )
765
-
766
- logger.info("Resync finished successfully")
767
-
768
- # Execute resync_complete hooks
769
- if "resync_complete" in self.event_strategy:
770
- logger.info("Executing resync_complete hooks")
771
-
772
- for resync_complete_fn in self.event_strategy["resync_complete"]:
773
- await resync_complete_fn()
774
-
775
- logger.info("Finished executing resync_complete hooks")
776
-
777
- return True
812
+ await self.resync_reconciliation(
813
+ creation_results,
814
+ did_fetched_current_state,
815
+ user_agent_type,
816
+ app_config,
817
+ silent
818
+ )
819
+ await ocean.metrics.report_sync_metrics(kinds=[MetricResourceKind.RECONCILIATION])
778
820
  finally:
779
821
  await ocean.app.cache_provider.clear()
780
822
  if ocean.app.process_execution_mode == ProcessExecutionMode.multi_process:
@@ -37,3 +37,7 @@ class IntegrationNotStartedException(BaseOceanException):
37
37
 
38
38
  class IntegrationRuntimeException(BaseOceanException):
39
39
  pass
40
+
41
+
42
+ class IntegrationSubProcessFailedException(BaseOceanException):
43
+ pass
@@ -6,7 +6,7 @@ import prometheus_client
6
6
  from httpx import AsyncClient
7
7
  from fastapi.responses import PlainTextResponse
8
8
  from loguru import logger
9
- from port_ocean.context import resource
9
+ from port_ocean.context import metric_resource, resource
10
10
  from prometheus_client import Gauge
11
11
  import prometheus_client.openmetrics
12
12
  import prometheus_client.openmetrics.exposition
@@ -57,6 +57,11 @@ class SyncState:
57
57
  FAILED = "failed"
58
58
 
59
59
 
60
+ class MetricResourceKind:
61
+ RECONCILIATION = "__reconciliation__"
62
+ RESYNC = "__resync__"
63
+
64
+
60
65
  # Registry for core and custom metrics
61
66
  _metrics_registry: Dict[str, Tuple[str, str, List[str]]] = {
62
67
  MetricType.DURATION_NAME: (
@@ -118,6 +123,7 @@ class Metrics:
118
123
  self.registry = prometheus_client.CollectorRegistry()
119
124
  if multiprocessing_enabled:
120
125
  multiprocess.MultiProcessCollector(self.registry)
126
+ self.multiprocessing_enabled = multiprocessing_enabled
121
127
  self.metrics: dict[str, Gauge] = {}
122
128
  self.load_metrics()
123
129
  self._integration_version: Optional[str] = None
@@ -208,7 +214,8 @@ class Metrics:
208
214
  logger.error(f"Failed to cleanup prometheus metrics: {e}")
209
215
 
210
216
  def initialize_metrics(self, kind_blockes: list[str]) -> None:
211
- self.cleanup_prometheus_metrics()
217
+ if self.multiprocessing_enabled:
218
+ self.cleanup_prometheus_metrics()
212
219
  for kind in kind_blockes:
213
220
  self.set_metric(MetricType.SUCCESS_NAME, [kind, MetricPhase.RESYNC], 0)
214
221
  self.set_metric(MetricType.DURATION_NAME, [kind, MetricPhase.RESYNC], 0)
@@ -252,8 +259,6 @@ class Metrics:
252
259
  )
253
260
 
254
261
  def create_mertic_router(self) -> APIRouter:
255
- if not self.enabled:
256
- return APIRouter()
257
262
  router = APIRouter()
258
263
 
259
264
  @router.get("/", response_class=PlainTextResponse)
@@ -265,6 +270,12 @@ class Metrics:
265
270
  def current_resource_kind(self) -> str:
266
271
  try:
267
272
  return f"{resource.resource.kind}-{resource.resource.index}"
273
+ except ResourceContextNotFoundError:
274
+ return self.current_metric_resource_kind()
275
+
276
+ def current_metric_resource_kind(self) -> str:
277
+ try:
278
+ return metric_resource.metric_resource.metric_resource_kind
268
279
  except ResourceContextNotFoundError:
269
280
  return "__runtime__"
270
281
 
@@ -274,15 +285,21 @@ class Metrics:
274
285
  ).decode()
275
286
 
276
287
  async def report_sync_metrics(
277
- self, metric_name: Optional[str] = None, kinds: Optional[list[str]] = None
288
+ self,
289
+ metric_name: Optional[str] = None,
290
+ kinds: Optional[list[str]] = None,
291
+ blueprints: Optional[list[Optional[str]]] = None,
278
292
  ) -> None:
279
293
  if kinds is None:
280
294
  return None
281
295
 
282
296
  metrics = []
283
297
 
284
- for kind in kinds:
285
- metric = self.generate_metrics(metric_name, kind)
298
+ if blueprints is None:
299
+ blueprints = [None] * len(kinds)
300
+
301
+ for kind, blueprint in zip(kinds, blueprints):
302
+ metric = self.generate_metrics(metric_name, kind, blueprint)
286
303
  metrics.extend(metric)
287
304
 
288
305
  try:
@@ -291,9 +308,12 @@ class Metrics:
291
308
  logger.error(f"Error posting metrics: {e}", metrics=metrics)
292
309
 
293
310
  async def report_kind_sync_metrics(
294
- self, metric_name: Optional[str] = None, kind: Optional[str] = None
311
+ self,
312
+ metric_name: Optional[str] = None,
313
+ kind: Optional[str] = None,
314
+ blueprint: Optional[str] = None,
295
315
  ) -> None:
296
- metrics = self.generate_metrics(metric_name, kind)
316
+ metrics = self.generate_metrics(metric_name, kind, blueprint)
297
317
  if not metrics:
298
318
  return None
299
319
 
@@ -304,7 +324,10 @@ class Metrics:
304
324
  logger.error(f"Error putting metrics: {e}", metrics=metrics)
305
325
 
306
326
  def generate_metrics(
307
- self, metric_name: Optional[str] = None, kind: Optional[str] = None
327
+ self,
328
+ metric_name: Optional[str] = None,
329
+ kind: Optional[str] = None,
330
+ blueprint: Optional[str] = None,
308
331
  ) -> list[dict[str, Any]]:
309
332
  try:
310
333
  latest_raw = self.generate_latest()
@@ -363,9 +386,10 @@ class Metrics:
363
386
  if "-" in kind_key
364
387
  else kind_key
365
388
  ),
366
- "kindIndex": 0 if kind_key == "__runtime__" else int(kind_key[-1]),
389
+ "kindIndex": int(kind_key[-1]) if kind_key[-1].isdigit() else 0,
367
390
  "eventId": self.event_id,
368
391
  "syncState": self.sync_state,
392
+ "blueprint": blueprint if blueprint else "",
369
393
  "metrics": metrics,
370
394
  }
371
395
  events.append(event)
@@ -0,0 +1,52 @@
1
+ from functools import wraps
2
+ import time
3
+ from typing import Any, Callable
4
+
5
+ from port_ocean.context.metric_resource import metric_resource_context
6
+ from port_ocean.context.ocean import ocean
7
+ from port_ocean.helpers.metric.metric import MetricResourceKind, MetricType
8
+
9
+
10
+ def TimeMetric(phase: str) -> Any:
11
+ def decorator(func: Callable[..., Any]) -> Any:
12
+
13
+ @wraps(func)
14
+ async def wrapper(*args: Any, **kwargs: dict[Any, Any]) -> Any:
15
+ start = time.monotonic()
16
+ res = await func(*args, **kwargs)
17
+ end = time.monotonic()
18
+ duration = end - start
19
+ ocean.metrics.inc_metric(
20
+ name=MetricType.DURATION_NAME,
21
+ labels=[ocean.metrics.current_resource_kind(), phase],
22
+ value=duration,
23
+ )
24
+
25
+ return res
26
+
27
+ return wrapper
28
+
29
+ return decorator
30
+
31
+
32
+ def TimeMetricWithResourceKind(phase: str) -> Any:
33
+ def decorator(func: Callable[..., Any]) -> Any:
34
+
35
+ @wraps(func)
36
+ async def wrapper(*args: Any, **kwargs: dict[Any, Any]) -> Any:
37
+ async with metric_resource_context(MetricResourceKind.RECONCILIATION):
38
+ start = time.monotonic()
39
+ res = await func(*args, **kwargs)
40
+ end = time.monotonic()
41
+ duration = end - start
42
+ ocean.metrics.inc_metric(
43
+ name=MetricType.DURATION_NAME,
44
+ labels=[ocean.metrics.current_resource_kind(), phase],
45
+ value=duration,
46
+ )
47
+
48
+ return res
49
+
50
+ return wrapper
51
+
52
+ return decorator
@@ -154,7 +154,7 @@ async def test_sync_raw_mixin_self_dependency(
154
154
 
155
155
  # Add assertions for actual metrics
156
156
  metrics = mock_ocean.metrics.generate_metrics()
157
- assert len(metrics) == 2
157
+ assert len(metrics) == 3
158
158
 
159
159
  # Verify object counts
160
160
  for metric in metrics:
@@ -187,7 +187,7 @@ async def test_sync_raw_mixin_self_dependency(
187
187
  metric["metrics"]["phase"]["load"]["object_count_type"][
188
188
  "loaded"
189
189
  ]["object_count"]
190
- == 2
190
+ == 1
191
191
  )
192
192
 
193
193
  # Verify success
@@ -196,6 +196,14 @@ async def test_sync_raw_mixin_self_dependency(
196
196
  # Verify sync state
197
197
  assert metric["syncState"] == "completed"
198
198
 
199
+ if metric["kind"] == "reconciliation":
200
+ assert (
201
+ metric["metrics"]["phase"]["load"]["object_count_type"][
202
+ "failed"
203
+ ]["object_count"]
204
+ == 1
205
+ )
206
+
199
207
 
200
208
  @pytest.mark.asyncio
201
209
  async def test_sync_raw_mixin_circular_dependency(
@@ -282,7 +290,7 @@ async def test_sync_raw_mixin_circular_dependency(
282
290
 
283
291
  # Add assertions for actual metrics
284
292
  metrics = mock_ocean.metrics.generate_metrics()
285
- assert len(metrics) == 2
293
+ assert len(metrics) == 3
286
294
 
287
295
  # Verify object counts
288
296
  for metric in metrics:
@@ -315,7 +323,7 @@ async def test_sync_raw_mixin_circular_dependency(
315
323
  metric["metrics"]["phase"]["load"]["object_count_type"][
316
324
  "loaded"
317
325
  ]["object_count"]
318
- == 2
326
+ == 0
319
327
  )
320
328
 
321
329
  # Verify success
@@ -324,6 +332,14 @@ async def test_sync_raw_mixin_circular_dependency(
324
332
  # Verify sync state
325
333
  assert metric["syncState"] == "completed"
326
334
 
335
+ if metric["kind"] == "reconciliation":
336
+ assert (
337
+ metric["metrics"]["phase"]["load"]["object_count_type"][
338
+ "loaded"
339
+ ]["object_count"]
340
+ == 2
341
+ )
342
+
327
343
 
328
344
  @pytest.mark.asyncio
329
345
  async def test_sync_raw_mixin_dependency(
@@ -422,7 +438,7 @@ async def test_sync_raw_mixin_dependency(
422
438
 
423
439
  # Add assertions for actual metrics
424
440
  metrics = mock_ocean.metrics.generate_metrics()
425
- assert len(metrics) == 2
441
+ assert len(metrics) == 3
426
442
 
427
443
  # Verify object counts
428
444
  for metric in metrics:
@@ -458,6 +474,14 @@ async def test_sync_raw_mixin_dependency(
458
474
  # Verify sync state
459
475
  assert metric["syncState"] == "completed"
460
476
 
477
+ if metric["kind"] == "reconciliation":
478
+ assert (
479
+ metric["metrics"]["phase"]["load"]["object_count_type"][
480
+ "loaded"
481
+ ]["object_count"]
482
+ == 5
483
+ )
484
+
461
485
 
462
486
  @pytest.mark.asyncio
463
487
  async def test_register_raw(