port-ocean 0.16.0__tar.gz → 0.17.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 (163) hide show
  1. {port_ocean-0.16.0 → port_ocean-0.17.0}/PKG-INFO +1 -1
  2. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/context/ocean.py +12 -3
  3. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/__init__.py +8 -0
  4. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/base.py +1 -0
  5. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/factory.py +9 -1
  6. port_ocean-0.17.0/port_ocean/core/event_listener/webhooks_only.py +41 -0
  7. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +47 -7
  8. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/base.py +5 -1
  9. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/sync_raw.py +2 -2
  10. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/ocean_types.py +11 -2
  11. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/run.py +0 -1
  12. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +34 -0
  13. {port_ocean-0.16.0 → port_ocean-0.17.0}/pyproject.toml +1 -1
  14. {port_ocean-0.16.0 → port_ocean-0.17.0}/LICENSE.md +0 -0
  15. {port_ocean-0.16.0 → port_ocean-0.17.0}/README.md +0 -0
  16. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Dockerfile.Deb +0 -0
  17. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Dockerfile.alpine +0 -0
  18. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Dockerfile.base.builder +0 -0
  19. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Dockerfile.base.runner +0 -0
  20. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
  21. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/Makefile +0 -0
  22. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/grpcio.sh +0 -0
  23. {port_ocean-0.16.0 → port_ocean-0.17.0}/integrations/_infra/init.sh +0 -0
  24. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/__init__.py +0 -0
  25. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/bootstrap.py +0 -0
  26. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/__init__.py +0 -0
  27. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cli.py +0 -0
  28. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/__init__.py +0 -0
  29. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  30. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
  31. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
  32. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/defaults/group.py +0 -0
  33. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/list_integrations.py +0 -0
  34. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/main.py +0 -0
  35. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/new.py +0 -0
  36. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/pull.py +0 -0
  37. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/sail.py +0 -0
  38. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/commands/version.py +0 -0
  39. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  40. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  41. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  42. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  43. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  44. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  45. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  46. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  47. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  48. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  49. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  50. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  51. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  52. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  53. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  54. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  55. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  56. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  57. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  58. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  59. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  60. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/cli/utils.py +0 -0
  61. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/__init__.py +0 -0
  62. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/__init__.py +0 -0
  63. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/authentication.py +0 -0
  64. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/client.py +0 -0
  65. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
  66. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  67. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/mixins/entities.py +0 -0
  68. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
  69. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
  70. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/retry_transport.py +0 -0
  71. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/types.py +0 -0
  72. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/clients/port/utils.py +0 -0
  73. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/config/__init__.py +0 -0
  74. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/config/base.py +0 -0
  75. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/config/dynamic.py +0 -0
  76. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/config/settings.py +0 -0
  77. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/consumers/__init__.py +0 -0
  78. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/consumers/kafka_consumer.py +0 -0
  79. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/context/__init__.py +0 -0
  80. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/context/event.py +0 -0
  81. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/context/resource.py +0 -0
  82. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/__init__.py +0 -0
  83. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/defaults/__init__.py +0 -0
  84. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/defaults/clean.py +0 -0
  85. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/defaults/common.py +0 -0
  86. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/defaults/initialize.py +0 -0
  87. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/http.py +0 -0
  88. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/kafka.py +0 -0
  89. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/once.py +0 -0
  90. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/event_listener/polling.py +0 -0
  91. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/__init__.py +0 -0
  92. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/base.py +0 -0
  93. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  94. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  95. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  96. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  97. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  98. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  99. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  100. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  101. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  102. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  103. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  104. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  105. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  106. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  107. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/__init__.py +0 -0
  108. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  109. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/events.py +0 -0
  110. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
  111. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
  112. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/integrations/mixins/utils.py +0 -0
  113. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/models.py +0 -0
  114. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/utils/entity_topological_sorter.py +0 -0
  115. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/core/utils/utils.py +0 -0
  116. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/debug_cli.py +0 -0
  117. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/__init__.py +0 -0
  118. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/api.py +0 -0
  119. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/base.py +0 -0
  120. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/clients.py +0 -0
  121. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/context.py +0 -0
  122. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/core.py +0 -0
  123. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/port_defaults.py +0 -0
  124. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/exceptions/utils.py +0 -0
  125. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/helpers/__init__.py +0 -0
  126. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/helpers/async_client.py +0 -0
  127. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/helpers/retry.py +0 -0
  128. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/log/__init__.py +0 -0
  129. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/log/handlers.py +0 -0
  130. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/log/logger_setup.py +0 -0
  131. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/log/sensetive.py +0 -0
  132. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/middlewares.py +0 -0
  133. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/ocean.py +0 -0
  134. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/py.typed +0 -0
  135. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/sonar-project.properties +0 -0
  136. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/__init__.py +0 -0
  137. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  138. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/conftest.py +0 -0
  139. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
  140. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +0 -0
  141. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/core/test_utils.py +0 -0
  142. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/core/utils/test_entity_topological_sorter.py +0 -0
  143. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/__init__.py +0 -0
  144. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  145. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/fixtures.py +0 -0
  146. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/integration.py +0 -0
  147. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
  148. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/port_client.py +0 -0
  149. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
  150. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/log/test_handlers.py +0 -0
  151. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/test_smoke.py +0 -0
  152. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  153. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/tests/utils/test_cache.py +0 -0
  154. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/__init__.py +0 -0
  155. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/async_http.py +0 -0
  156. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/async_iterators.py +0 -0
  157. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/cache.py +0 -0
  158. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/misc.py +0 -0
  159. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/queue_utils.py +0 -0
  160. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/repeat.py +0 -0
  161. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/signal.py +0 -0
  162. {port_ocean-0.16.0 → port_ocean-0.17.0}/port_ocean/utils/time.py +0 -0
  163. {port_ocean-0.16.0 → port_ocean-0.17.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.16.0
3
+ Version: 0.17.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
@@ -23,6 +23,8 @@ if TYPE_CHECKING:
23
23
  from port_ocean.ocean import Ocean
24
24
  from port_ocean.clients.port.client import PortClient
25
25
 
26
+ from loguru import logger
27
+
26
28
 
27
29
  class PortOceanContext:
28
30
  def __init__(self, app: Union["Ocean", None]) -> None:
@@ -63,14 +65,21 @@ class PortOceanContext:
63
65
  return self.app.port_client
64
66
 
65
67
  @property
66
- def event_listener_type(self) -> Literal["WEBHOOK", "KAFKA", "POLLING", "ONCE"]:
68
+ def event_listener_type(
69
+ self,
70
+ ) -> Literal["WEBHOOK", "KAFKA", "POLLING", "ONCE", "WEBHOOKS_ONLY"]:
67
71
  return self.app.config.event_listener.type
68
72
 
69
73
  def on_resync(
70
74
  self,
71
75
  kind: str | None = None,
72
- ) -> Callable[[RESYNC_EVENT_LISTENER], RESYNC_EVENT_LISTENER]:
73
- def wrapper(function: RESYNC_EVENT_LISTENER) -> RESYNC_EVENT_LISTENER:
76
+ ) -> Callable[[RESYNC_EVENT_LISTENER], RESYNC_EVENT_LISTENER | None]:
77
+ def wrapper(function: RESYNC_EVENT_LISTENER) -> RESYNC_EVENT_LISTENER | None:
78
+ if not self.app.config.event_listener.should_resync:
79
+ logger.debug(
80
+ "Webhook only event listener is used, resync events are ignored"
81
+ )
82
+ return None
74
83
  return self.integration.on_resync(function, kind)
75
84
 
76
85
  return wrapper
@@ -16,12 +16,18 @@ from port_ocean.core.event_listener.once import (
16
16
  OnceEventListener,
17
17
  )
18
18
 
19
+ from port_ocean.core.event_listener.webhooks_only import (
20
+ WebhooksOnlyEventListener,
21
+ WebhooksOnlyEventListenerSettings,
22
+ )
23
+
19
24
 
20
25
  EventListenerSettingsType = (
21
26
  HttpEventListenerSettings
22
27
  | KafkaEventListenerSettings
23
28
  | PollingEventListenerSettings
24
29
  | OnceEventListenerSettings
30
+ | WebhooksOnlyEventListenerSettings
25
31
  )
26
32
 
27
33
  __all__ = [
@@ -34,4 +40,6 @@ __all__ = [
34
40
  "PollingEventListenerSettings",
35
41
  "OnceEventListener",
36
42
  "OnceEventListenerSettings",
43
+ "WebhooksOnlyEventListener",
44
+ "WebhooksOnlyEventListenerSettings",
37
45
  ]
@@ -76,6 +76,7 @@ class BaseEventListener:
76
76
 
77
77
  class EventListenerSettings(BaseOceanModel, extra=Extra.allow):
78
78
  type: str
79
+ should_resync: bool = True
79
80
 
80
81
  def to_request(self) -> dict[str, Any]:
81
82
  """
@@ -17,6 +17,10 @@ from port_ocean.core.event_listener.base import (
17
17
  BaseEventListener,
18
18
  EventListenerEvents,
19
19
  )
20
+ from port_ocean.core.event_listener.webhooks_only import (
21
+ WebhooksOnlyEventListener,
22
+ WebhooksOnlyEventListenerSettings,
23
+ )
20
24
  from port_ocean.exceptions.core import UnsupportedEventListenerTypeException
21
25
 
22
26
 
@@ -88,7 +92,11 @@ class EventListenerFactory:
88
92
  config, OnceEventListenerSettings
89
93
  ), assert_message.format(type(config))
90
94
  event_listener = OnceEventListener(wrapped_events, config)
91
-
95
+ case "webhooks_only":
96
+ assert isinstance(
97
+ config, WebhooksOnlyEventListenerSettings
98
+ ), assert_message.format(type(config))
99
+ event_listener = WebhooksOnlyEventListener(wrapped_events, config)
92
100
  case _:
93
101
  raise UnsupportedEventListenerTypeException(
94
102
  f"Event listener {_type} not supported"
@@ -0,0 +1,41 @@
1
+ from typing import Literal
2
+
3
+ from loguru import logger
4
+
5
+ from port_ocean.core.event_listener.base import (
6
+ BaseEventListener,
7
+ EventListenerEvents,
8
+ EventListenerSettings,
9
+ )
10
+
11
+
12
+ class WebhooksOnlyEventListenerSettings(EventListenerSettings):
13
+ """
14
+ This class inherits from `EventListenerSettings`, which provides a foundation for creating event listener settings.
15
+ """
16
+
17
+ type: Literal["WEBHOOKS_ONLY"]
18
+ should_resync: bool = False
19
+
20
+
21
+ class WebhooksOnlyEventListener(BaseEventListener):
22
+ """
23
+ No resync event listener.
24
+
25
+ It is used to handle events exclusively through webhooks without supporting resync events.
26
+
27
+ Parameters:
28
+ events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
29
+ event_listener_config (OnceEventListenerSettings): The event listener configuration settings.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ events: EventListenerEvents,
35
+ event_listener_config: WebhooksOnlyEventListenerSettings,
36
+ ):
37
+ super().__init__(events)
38
+ self.event_listener_config = event_listener_config
39
+
40
+ async def _start(self) -> None:
41
+ logger.info("Starting Webhooks-only event listener")
@@ -34,6 +34,7 @@ class MappedEntity:
34
34
  entity: dict[str, Any] = field(default_factory=dict)
35
35
  did_entity_pass_selector: bool = False
36
36
  raw_data: Optional[dict[str, Any]] = None
37
+ misconfigurations: dict[str, str] = field(default_factory=dict)
37
38
 
38
39
 
39
40
  class JQEntityProcessor(BaseEntityProcessor):
@@ -95,21 +96,37 @@ class JQEntityProcessor(BaseEntityProcessor):
95
96
  )
96
97
 
97
98
  async def _search_as_object(
98
- self, data: dict[str, Any], obj: dict[str, Any]
99
+ self,
100
+ data: dict[str, Any],
101
+ obj: dict[str, Any],
102
+ misconfigurations: dict[str, str] | None = None,
99
103
  ) -> dict[str, Any | None]:
104
+ """
105
+ Identify and extract the relevant value for the chosen key and populate it into the entity
106
+ :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,
107
+ if the data is a dict, we will recursively call this function again.
108
+ :param obj: the key that we want its value to be mapped into our entity.
109
+ :param misconfigurations: due to the recursive nature of this function,
110
+ we aim to have a dict that represents all of the misconfigured properties and when used recursively,
111
+ we pass this reference to misfoncigured object to add the relevant misconfigured keys.
112
+ :return: Mapped object with found value.
113
+ """
114
+
100
115
  search_tasks: dict[
101
116
  str, Task[dict[str, Any | None]] | list[Task[dict[str, Any | None]]]
102
117
  ] = {}
103
118
  for key, value in obj.items():
104
119
  if isinstance(value, list):
105
120
  search_tasks[key] = [
106
- asyncio.create_task(self._search_as_object(data, obj))
121
+ asyncio.create_task(
122
+ self._search_as_object(data, obj, misconfigurations)
123
+ )
107
124
  for obj in value
108
125
  ]
109
126
 
110
127
  elif isinstance(value, dict):
111
128
  search_tasks[key] = asyncio.create_task(
112
- self._search_as_object(data, value)
129
+ self._search_as_object(data, value, misconfigurations)
113
130
  )
114
131
  else:
115
132
  search_tasks[key] = asyncio.create_task(self._search(data, value))
@@ -118,12 +135,20 @@ class JQEntityProcessor(BaseEntityProcessor):
118
135
  for key, task in search_tasks.items():
119
136
  try:
120
137
  if isinstance(task, list):
121
- result[key] = [await task for task in task]
138
+ result_list = []
139
+ for task in task:
140
+ task_result = await task
141
+ if task_result is None and misconfigurations is not None:
142
+ misconfigurations[key] = obj[key]
143
+ result_list.append(task_result)
144
+ result[key] = result_list
122
145
  else:
123
- result[key] = await task
146
+ task_result = await task
147
+ if task_result is None and misconfigurations is not None:
148
+ misconfigurations[key] = obj[key]
149
+ result[key] = task_result
124
150
  except Exception:
125
151
  result[key] = None
126
-
127
152
  return result
128
153
 
129
154
  async def _get_mapped_entity(
@@ -135,11 +160,15 @@ class JQEntityProcessor(BaseEntityProcessor):
135
160
  ) -> MappedEntity:
136
161
  should_run = await self._search_as_bool(data, selector_query)
137
162
  if parse_all or should_run:
138
- mapped_entity = await self._search_as_object(data, raw_entity_mappings)
163
+ misconfigurations: dict[str, str] = {}
164
+ mapped_entity = await self._search_as_object(
165
+ data, raw_entity_mappings, misconfigurations
166
+ )
139
167
  return MappedEntity(
140
168
  mapped_entity,
141
169
  did_entity_pass_selector=should_run,
142
170
  raw_data=data if should_run else None,
171
+ misconfigurations=misconfigurations,
143
172
  )
144
173
 
145
174
  return MappedEntity()
@@ -221,7 +250,11 @@ class JQEntityProcessor(BaseEntityProcessor):
221
250
  passed_entities = []
222
251
  failed_entities = []
223
252
  examples_to_send: list[dict[str, Any]] = []
253
+ entity_misconfigurations: dict[str, str] = {}
254
+ missing_required_fields: bool = False
224
255
  for result in calculated_entities_results:
256
+ if len(result.misconfigurations) > 0:
257
+ entity_misconfigurations |= result.misconfigurations
225
258
  if result.entity.get("identifier") and result.entity.get("blueprint"):
226
259
  parsed_entity = Entity.parse_obj(result.entity)
227
260
  if result.did_entity_pass_selector:
@@ -233,6 +266,12 @@ class JQEntityProcessor(BaseEntityProcessor):
233
266
  examples_to_send.append(result.raw_data)
234
267
  else:
235
268
  failed_entities.append(parsed_entity)
269
+ else:
270
+ missing_required_fields = True
271
+ if len(entity_misconfigurations) > 0:
272
+ logger.info(
273
+ f"The mapping resulted with invalid values for{" identifier, blueprint," if missing_required_fields else " "} properties. Mapping result: {entity_misconfigurations}"
274
+ )
236
275
  if (
237
276
  not calculated_entities_results
238
277
  and raw_results
@@ -248,4 +287,5 @@ class JQEntityProcessor(BaseEntityProcessor):
248
287
  return CalculationResult(
249
288
  EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
250
289
  errors,
290
+ misonfigured_entity_keys=entity_misconfigurations,
251
291
  )
@@ -54,13 +54,17 @@ class BaseIntegration(SyncRawMixin, SyncMixin):
54
54
  """
55
55
  Initializes handlers, establishes integration at the specified port, and starts the event listener.
56
56
  """
57
- logger.info("Starting integration", integration_type=self.context.config.integration.type)
57
+ logger.info(
58
+ "Starting integration",
59
+ integration_type=self.context.config.integration.type,
60
+ )
58
61
  if self.started:
59
62
  raise IntegrationAlreadyStartedException("Integration already started")
60
63
 
61
64
  if (
62
65
  not self.event_strategy["resync"]
63
66
  and self.__class__._on_resync == BaseIntegration._on_resync
67
+ and self.context.config.event_listener.should_resync
64
68
  ):
65
69
  raise NotImplementedError("on_resync is not implemented")
66
70
 
@@ -185,7 +185,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
185
185
  send_raw_data_examples_amount = (
186
186
  SEND_RAW_DATA_EXAMPLES_AMOUNT if ocean.config.send_raw_data_examples else 0
187
187
  )
188
- all_entities, register_errors = await self._register_resource_raw(
188
+ all_entities, register_errors,_ = await self._register_resource_raw(
189
189
  resource_config,
190
190
  raw_results,
191
191
  user_agent_type,
@@ -202,7 +202,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
202
202
  0, send_raw_data_examples_amount - len(passed_entities)
203
203
  )
204
204
 
205
- entities, register_errors = await self._register_resource_raw(
205
+ entities, register_errors,_ = await self._register_resource_raw(
206
206
  resource_config,
207
207
  items,
208
208
  user_agent_type,
@@ -1,5 +1,13 @@
1
- from typing import TypedDict, Any, AsyncIterator, Callable, Awaitable, NamedTuple
2
-
1
+ from typing import (
2
+ TypedDict,
3
+ Any,
4
+ AsyncIterator,
5
+ Callable,
6
+ Awaitable,
7
+ NamedTuple,
8
+ )
9
+
10
+ from dataclasses import field
3
11
  from port_ocean.core.models import Entity
4
12
 
5
13
  RAW_ITEM = dict[Any, Any]
@@ -30,6 +38,7 @@ class EntitySelectorDiff(NamedTuple):
30
38
  class CalculationResult(NamedTuple):
31
39
  entity_selector_diff: EntitySelectorDiff
32
40
  errors: list[Exception]
41
+ misonfigured_entity_keys: dict[str, str] = field(default_factory=dict)
33
42
 
34
43
 
35
44
  class IntegrationEventsCallbacks(TypedDict):
@@ -57,7 +57,6 @@ def run(
57
57
  # Override config with arguments
58
58
  if initialize_port_resources is not None:
59
59
  app.config.initialize_port_resources = initialize_port_resources
60
-
61
60
  initialize_defaults(app.integration.AppConfigHandlerClass.CONFIG_CLASS, app.config)
62
61
 
63
62
  uvicorn.run(app, host="0.0.0.0", port=application_settings.port)
@@ -269,3 +269,37 @@ class TestJQEntityProcessor:
269
269
  assert len(result.entity_selector_diff.passed) == 1
270
270
  assert result.entity_selector_diff.passed[0].properties.get("foo") == "bar"
271
271
  assert not result.errors
272
+
273
+ async def test_parse_items_wrong_mapping(
274
+ self, mocked_processor: JQEntityProcessor
275
+ ) -> None:
276
+ mapping = Mock()
277
+ mapping.port.entity.mappings.dict.return_value = {
278
+ "title": ".foo",
279
+ "identifier": ".ark",
280
+ "blueprint": ".baz",
281
+ "properties": {
282
+ "description": ".bazbar",
283
+ "url": ".foobar",
284
+ "defaultBranch": ".bar.baz",
285
+ },
286
+ }
287
+ mapping.port.items_to_parse = None
288
+ mapping.selector.query = "true"
289
+ raw_results = [
290
+ {
291
+ "foo": "bar",
292
+ "baz": "bazbar",
293
+ "bar": {"foobar": "barfoo", "baz": "barbaz"},
294
+ },
295
+ {"foo": "bar", "baz": "bazbar", "bar": {"foobar": "foobar"}},
296
+ ]
297
+ result = await mocked_processor._parse_items(mapping, raw_results)
298
+ assert len(result.misonfigured_entity_keys) > 0
299
+ assert len(result.misonfigured_entity_keys) == 4
300
+ assert result.misonfigured_entity_keys == {
301
+ "identifier": ".ark",
302
+ "description": ".bazbar",
303
+ "url": ".foobar",
304
+ "defaultBranch": ".bar.baz",
305
+ }
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.16.0"
3
+ version = "0.17.0"
4
4
  description = "Port Ocean is a CLI tool for managing your Port projects."
5
5
  readme = "README.md"
6
6
  homepage = "https://app.getport.io"
File without changes
File without changes