port-ocean 0.15.2__tar.gz → 0.16.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.15.2 → port_ocean-0.16.0}/PKG-INFO +1 -1
  2. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/new.py +4 -7
  3. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/cookiecutter.json +3 -0
  4. port_ocean-0.16.0/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +6 -0
  5. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/entities.py +33 -4
  6. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/event.py +11 -0
  7. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/initialize.py +1 -1
  8. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +9 -17
  9. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +5 -2
  10. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +4 -1
  11. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/sync_raw.py +19 -2
  12. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/utils.py +1 -1
  13. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/models.py +4 -0
  14. port_ocean-0.16.0/port_ocean/core/utils/entity_topological_sorter.py +90 -0
  15. port_ocean-0.16.0/port_ocean/debug_cli.py +5 -0
  16. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/run.py +1 -1
  17. port_ocean-0.16.0/port_ocean/tests/core/handlers/mixins/test_sync_raw.py +400 -0
  18. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/test_utils.py +1 -1
  19. port_ocean-0.16.0/port_ocean/tests/core/utils/test_entity_topological_sorter.py +99 -0
  20. {port_ocean-0.15.2 → port_ocean-0.16.0}/pyproject.toml +1 -1
  21. port_ocean-0.15.2/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -2
  22. {port_ocean-0.15.2 → port_ocean-0.16.0}/LICENSE.md +0 -0
  23. {port_ocean-0.15.2 → port_ocean-0.16.0}/README.md +0 -0
  24. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.Deb +0 -0
  25. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.alpine +0 -0
  26. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.base.builder +0 -0
  27. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.base.runner +0 -0
  28. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Dockerfile.dockerignore +0 -0
  29. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/Makefile +0 -0
  30. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/grpcio.sh +0 -0
  31. {port_ocean-0.15.2 → port_ocean-0.16.0}/integrations/_infra/init.sh +0 -0
  32. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/__init__.py +0 -0
  33. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/bootstrap.py +0 -0
  34. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/__init__.py +0 -0
  35. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cli.py +0 -0
  36. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/__init__.py +0 -0
  37. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  38. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
  39. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
  40. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/defaults/group.py +0 -0
  41. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/list_integrations.py +0 -0
  42. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/main.py +0 -0
  43. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/pull.py +0 -0
  44. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/sail.py +0 -0
  45. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/commands/version.py +0 -0
  46. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  47. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  48. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  49. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  50. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  51. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  52. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  53. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  54. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  55. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  56. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  57. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  58. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  59. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  60. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  61. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  62. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  63. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  64. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  65. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/cli/utils.py +0 -0
  66. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/__init__.py +0 -0
  67. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/__init__.py +0 -0
  68. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/authentication.py +0 -0
  69. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/client.py +0 -0
  70. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
  71. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  72. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
  73. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
  74. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/retry_transport.py +0 -0
  75. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/types.py +0 -0
  76. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/clients/port/utils.py +0 -0
  77. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/__init__.py +0 -0
  78. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/base.py +0 -0
  79. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/dynamic.py +0 -0
  80. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/config/settings.py +0 -0
  81. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/consumers/__init__.py +0 -0
  82. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/consumers/kafka_consumer.py +0 -0
  83. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/__init__.py +0 -0
  84. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/ocean.py +0 -0
  85. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/context/resource.py +0 -0
  86. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/__init__.py +0 -0
  87. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/__init__.py +0 -0
  88. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/clean.py +0 -0
  89. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/defaults/common.py +0 -0
  90. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/__init__.py +0 -0
  91. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/base.py +0 -0
  92. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/factory.py +0 -0
  93. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/http.py +0 -0
  94. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/kafka.py +0 -0
  95. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/once.py +0 -0
  96. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/event_listener/polling.py +0 -0
  97. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/__init__.py +0 -0
  98. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/base.py +0 -0
  99. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  100. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  101. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  102. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  103. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  104. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  105. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  106. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  107. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  108. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  109. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  110. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  111. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/__init__.py +0 -0
  112. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/base.py +0 -0
  113. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  114. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/events.py +0 -0
  115. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
  116. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
  117. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/core/ocean_types.py +0 -0
  118. {port_ocean-0.15.2/port_ocean/core → port_ocean-0.16.0/port_ocean/core/utils}/utils.py +0 -0
  119. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/__init__.py +0 -0
  120. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/api.py +0 -0
  121. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/base.py +0 -0
  122. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/clients.py +0 -0
  123. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/context.py +0 -0
  124. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/core.py +0 -0
  125. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/port_defaults.py +0 -0
  126. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/exceptions/utils.py +0 -0
  127. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/__init__.py +0 -0
  128. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/async_client.py +0 -0
  129. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/helpers/retry.py +0 -0
  130. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/__init__.py +0 -0
  131. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/handlers.py +0 -0
  132. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/logger_setup.py +0 -0
  133. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/log/sensetive.py +0 -0
  134. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/middlewares.py +0 -0
  135. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/ocean.py +0 -0
  136. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/py.typed +0 -0
  137. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/sonar-project.properties +0 -0
  138. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/__init__.py +0 -0
  139. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  140. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/conftest.py +0 -0
  141. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/defaults/test_common.py +0 -0
  142. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +0 -0
  143. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/__init__.py +0 -0
  144. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/fake_port_api.py +0 -0
  145. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/fixtures.py +0 -0
  146. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/integration.py +0 -0
  147. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
  148. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/port_client.py +0 -0
  149. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/helpers/smoke_test.py +0 -0
  150. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/log/test_handlers.py +0 -0
  151. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/test_smoke.py +0 -0
  152. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/utils/test_async_iterators.py +0 -0
  153. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/tests/utils/test_cache.py +0 -0
  154. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/__init__.py +0 -0
  155. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/async_http.py +0 -0
  156. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/async_iterators.py +0 -0
  157. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/cache.py +0 -0
  158. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/misc.py +0 -0
  159. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/queue_utils.py +0 -0
  160. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/repeat.py +0 -0
  161. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/signal.py +0 -0
  162. {port_ocean-0.15.2 → port_ocean-0.16.0}/port_ocean/utils/time.py +0 -0
  163. {port_ocean-0.15.2 → port_ocean-0.16.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.15.2
3
+ Version: 0.16.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
@@ -77,18 +77,15 @@ def new(path: str, is_private_integration: bool) -> None:
77
77
  )
78
78
  console.print("Here are your next steps:\n", style="bold")
79
79
  console.print(
80
- "⚓️ Install necessary packages: Run [bold][blue]make install[/blue][/bold] to install all required packages for your project.\n"
81
- f"▶️ [bold][blue]cd {path}/{name} && make install && . .venv/bin/activate[/blue][/bold]\n"
80
+ f"⚓️ Install necessary packages: Run [bold][blue]cd {path}/{name} && make install && . .venv/bin/activate[/blue][/bold] to install all required packages for your project."
82
81
  )
83
82
  console.print(
84
- f"⚓️ Copy example env file: Run [bold][blue]cp {path}/{name}.env.example {path}/{name}/.env [/blue][/bold] and set your port credentials in the created file.\n"
83
+ "⚓️ Copy example env file: Run [bold][blue]cp .env.example .env [/blue][/bold] and update your integration's configuration in the .env file."
85
84
  )
86
85
  console.print(
87
- "⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue] <path_to_integration>[/bold] to run the project using Ocean.\n"
88
- f"▶️ [bold][blue]ocean sail {path}/{name}[/blue][/bold] \n"
86
+ "⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue][/bold] to run the project using Ocean."
89
87
  )
90
88
  if not final_private_integration:
91
89
  console.print(
92
- "⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run[/blue][/bold] to launch your project using Make. \n"
93
- f"▶️ [bold][blue]make run {path}/{name}[/blue][/bold]"
90
+ f"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run {path}/{name}[/blue][/bold] to launch your project using Make."
94
91
  )
@@ -6,6 +6,9 @@
6
6
  "email": "Your address email <you@example.com>",
7
7
  "release_date": "{% now 'local' %}",
8
8
  "is_private_integration": true,
9
+ "port_client_id": "you can find it using: https://docs.getport.io/build-your-software-catalog/custom-integration/api/#find-your-port-credentials",
10
+ "port_client_secret": "you can find it using: https://docs.getport.io/build-your-software-catalog/custom-integration/api/#find-your-port-credentials",
11
+ "is_us_region": false,
9
12
  "_extensions": [
10
13
  "jinja2_time.TimeExtension",
11
14
  "extensions.VersionExtension"
@@ -0,0 +1,6 @@
1
+ OCEAN__PORT__CLIENT_ID={{ cookiecutter.port_client_id }}
2
+ OCEAN__PORT__CLIENT_SECRET={{ cookiecutter.port_client_secret }}
3
+ OCEAN__INTEGRATION__IDENTIFIER={{ cookiecutter.integration_slug }}
4
+ OCEAN__PORT__BASE_URL={% if cookiecutter.is_us_region %}https://api.us.getport.io{% else %}https://api.getport.io{% endif %}
5
+ OCEAN__EVENT_LISTENER__TYPE=POLLING
6
+ OCEAN__INITIALIZE_PORT_RESOURCES=true
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any
2
+ from typing import Any, Literal
3
3
  from urllib.parse import quote_plus
4
4
 
5
5
  import httpx
@@ -11,7 +11,8 @@ from port_ocean.clients.port.utils import (
11
11
  handle_status_code,
12
12
  PORT_HTTP_MAX_CONNECTIONS_LIMIT,
13
13
  )
14
- from port_ocean.core.models import Entity
14
+ from port_ocean.core.models import Entity, PortAPIErrorMessage
15
+ from starlette import status
15
16
 
16
17
 
17
18
  class EntityClientMixin:
@@ -29,7 +30,27 @@ class EntityClientMixin:
29
30
  request_options: RequestOptions,
30
31
  user_agent_type: UserAgentType | None = None,
31
32
  should_raise: bool = True,
32
- ) -> Entity | None:
33
+ ) -> Entity | None | Literal[False]:
34
+ """
35
+ This function upserts an entity into Port.
36
+
37
+ Usage:
38
+ ```python
39
+ upsertedEntity = await self.context.port_client.upsert_entity(
40
+ entity,
41
+ event.port_app_config.get_port_request_options(),
42
+ user_agent_type,
43
+ should_raise=False,
44
+ )
45
+ ```
46
+ :param entity: An Entity to be upserted
47
+ :param request_options: A dictionary specifying how to upsert the entity
48
+ :param user_agent_type: a UserAgentType specifying who is preforming the action
49
+ :param should_raise: A boolean specifying whether the error should be raised or handled silently
50
+ :return: [Entity] if the upsert occured successfully
51
+ :return: [None] will be returned if entity is using search identifier
52
+ :return: [False] will be returned if upsert failed because of unmet dependency
53
+ """
33
54
  validation_only = request_options["validation_only"]
34
55
  async with self.semaphore:
35
56
  logger.debug(
@@ -50,13 +71,21 @@ class EntityClientMixin:
50
71
  },
51
72
  extensions={"retryable": True},
52
73
  )
53
-
54
74
  if response.is_error:
55
75
  logger.error(
56
76
  f"Error {'Validating' if validation_only else 'Upserting'} "
57
77
  f"entity: {entity.identifier} of "
58
78
  f"blueprint: {entity.blueprint}"
59
79
  )
80
+ result = response.json()
81
+
82
+ if (
83
+ response.status_code == status.HTTP_404_NOT_FOUND
84
+ and not result.get("ok")
85
+ and result.get("error") == PortAPIErrorMessage.NOT_FOUND.value
86
+ ):
87
+ # Return false to differentiate from `result_entity.is_using_search_identifier`
88
+ return False
60
89
  handle_status_code(response, should_raise)
61
90
  result = response.json()
62
91
 
@@ -14,6 +14,7 @@ from typing import (
14
14
  from uuid import uuid4
15
15
 
16
16
  from loguru import logger
17
+ from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
17
18
  from pydispatch import dispatcher # type: ignore
18
19
  from werkzeug.local import LocalStack, LocalProxy
19
20
 
@@ -24,6 +25,7 @@ from port_ocean.exceptions.context import (
24
25
  )
25
26
  from port_ocean.utils.misc import get_time
26
27
 
28
+
27
29
  if TYPE_CHECKING:
28
30
  from port_ocean.core.handlers.port_app_config.models import (
29
31
  ResourceConfig,
@@ -50,6 +52,9 @@ class EventContext:
50
52
  _parent_event: Optional["EventContext"] = None
51
53
  _event_id: str = field(default_factory=lambda: str(uuid4()))
52
54
  _on_abort_callbacks: list[AbortCallbackFunction] = field(default_factory=list)
55
+ entity_topological_sorter: EntityTopologicalSorter = field(
56
+ default_factory=EntityTopologicalSorter
57
+ )
53
58
 
54
59
  def on_abort(self, func: AbortCallbackFunction) -> None:
55
60
  self._on_abort_callbacks.append(func)
@@ -129,6 +134,11 @@ async def event_context(
129
134
  ) -> AsyncIterator[EventContext]:
130
135
  parent = parent_override or _event_context_stack.top
131
136
  parent_attributes = parent.attributes if parent else {}
137
+ entity_topological_sorter = (
138
+ parent.entity_topological_sorter
139
+ if parent and parent.entity_topological_sorter
140
+ else EntityTopologicalSorter()
141
+ )
132
142
 
133
143
  attributes = {**parent_attributes, **(attributes or {})}
134
144
  new_event = EventContext(
@@ -138,6 +148,7 @@ async def event_context(
138
148
  _parent_event=parent,
139
149
  # inherit port app config from parent event, so it can be used in nested events
140
150
  _port_app_config=parent.port_app_config if parent else None,
151
+ entity_topological_sorter=entity_topological_sorter,
141
152
  )
142
153
  _event_context_stack.push(new_event)
143
154
 
@@ -14,7 +14,7 @@ from port_ocean.core.defaults.common import (
14
14
  )
15
15
  from port_ocean.core.handlers.port_app_config.models import PortAppConfig
16
16
  from port_ocean.core.models import Blueprint
17
- from port_ocean.core.utils import gather_and_split_errors_from_results
17
+ from port_ocean.core.utils.utils import gather_and_split_errors_from_results
18
18
  from port_ocean.exceptions.port_defaults import (
19
19
  AbortDefaultCreationError,
20
20
  )
@@ -8,12 +8,11 @@ from port_ocean.core.handlers.entities_state_applier.base import (
8
8
  from port_ocean.core.handlers.entities_state_applier.port.get_related_entities import (
9
9
  get_related_entities,
10
10
  )
11
- from port_ocean.core.handlers.entities_state_applier.port.order_by_entities_dependencies import (
12
- order_by_entities_dependencies,
13
- )
11
+
14
12
  from port_ocean.core.models import Entity
15
13
  from port_ocean.core.ocean_types import EntityDiff
16
- from port_ocean.core.utils import is_same_entity, get_port_diff
14
+ from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
15
+ from port_ocean.core.utils.utils import is_same_entity, get_port_diff
17
16
 
18
17
 
19
18
  class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
@@ -106,19 +105,7 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
106
105
  should_raise=False,
107
106
  )
108
107
  else:
109
- entities_with_search_identifier: list[Entity] = []
110
- entities_without_search_identifier: list[Entity] = []
111
108
  for entity in entities:
112
- if entity.is_using_search_identifier:
113
- entities_with_search_identifier.append(entity)
114
- else:
115
- entities_without_search_identifier.append(entity)
116
-
117
- ordered_created_entities = reversed(
118
- entities_with_search_identifier
119
- + order_by_entities_dependencies(entities_without_search_identifier)
120
- )
121
- for entity in ordered_created_entities:
122
109
  upsertedEntity = await self.context.port_client.upsert_entity(
123
110
  entity,
124
111
  event.port_app_config.get_port_request_options(),
@@ -127,6 +114,9 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
127
114
  )
128
115
  if upsertedEntity:
129
116
  modified_entities.append(upsertedEntity)
117
+ # condition to false to differentiate from `result_entity.is_using_search_identifier`
118
+ if upsertedEntity is False:
119
+ event.entity_topological_sorter.register_entity(entity)
130
120
  return modified_entities
131
121
 
132
122
  async def delete(
@@ -141,7 +131,9 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
141
131
  should_raise=False,
142
132
  )
143
133
  else:
144
- ordered_deleted_entities = order_by_entities_dependencies(entities)
134
+ ordered_deleted_entities = (
135
+ EntityTopologicalSorter.order_by_entities_dependencies(entities)
136
+ )
145
137
 
146
138
  for entity in ordered_deleted_entities:
147
139
  await self.context.port_client.delete_entity(
@@ -14,7 +14,6 @@ def node(entity: Entity) -> Node:
14
14
  def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
15
15
  nodes: dict[Node, Set[Node]] = {}
16
16
  entities_map = {}
17
-
18
17
  for entity in entities:
19
18
  nodes[node(entity)] = set()
20
19
  entities_map[node(entity)] = entity
@@ -33,7 +32,11 @@ def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
33
32
  ]
34
33
 
35
34
  for related_entity in related_entities:
36
- nodes[node(entity)].add(node(related_entity))
35
+ if (
36
+ entity.blueprint is not related_entity.blueprint
37
+ or entity.identifier is not related_entity.identifier
38
+ ):
39
+ nodes[node(entity)].add(node(related_entity))
37
40
 
38
41
  sort_op = TopologicalSorter(nodes)
39
42
  try:
@@ -16,7 +16,10 @@ from port_ocean.core.ocean_types import (
16
16
  EntitySelectorDiff,
17
17
  CalculationResult,
18
18
  )
19
- from port_ocean.core.utils import gather_and_split_errors_from_results, zip_and_sum
19
+ from port_ocean.core.utils.utils import (
20
+ gather_and_split_errors_from_results,
21
+ zip_and_sum,
22
+ )
20
23
  from port_ocean.exceptions.core import EntityProcessorException
21
24
  from port_ocean.utils.queue_utils import process_in_queue
22
25
 
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from graphlib import CycleError
2
3
  import inspect
3
4
  import typing
4
5
  from typing import Callable, Awaitable, Any
@@ -27,7 +28,7 @@ from port_ocean.core.ocean_types import (
27
28
  RAW_ITEM,
28
29
  CalculationResult,
29
30
  )
30
- from port_ocean.core.utils import zip_and_sum, gather_and_split_errors_from_results
31
+ from port_ocean.core.utils.utils import zip_and_sum, gather_and_split_errors_from_results
31
32
  from port_ocean.exceptions.core import OceanAbortException
32
33
 
33
34
  SEND_RAW_DATA_EXAMPLES_AMOUNT = 5
@@ -396,7 +397,20 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
396
397
  {"before": entities_before_flatten, "after": entities_after_flatten},
397
398
  user_agent_type,
398
399
  )
399
-
400
+ async def sort_and_upsert_failed_entities(self,user_agent_type: UserAgentType)->None:
401
+ try:
402
+ if not event.entity_topological_sorter.should_execute():
403
+ return None
404
+ logger.info(f"Executings topological sort of {event.entity_topological_sorter.get_entities_count()} entities failed to upsert.",failed_toupsert_entities_count=event.entity_topological_sorter.get_entities_count())
405
+
406
+ for entity in event.entity_topological_sorter.get_entities():
407
+ await self.entities_state_applier.context.port_client.upsert_entity(entity,event.port_app_config.get_port_request_options(),user_agent_type,should_raise=False)
408
+
409
+ except OceanAbortException as ocean_abort:
410
+ logger.info(f"Failed topological sort of failed to upsert entites - trying to upsert unordered {event.entity_topological_sorter.get_entities_count()} entities.",failed_topological_sort_entities_count=event.entity_topological_sorter.get_entities_count() )
411
+ if isinstance(ocean_abort.__cause__,CycleError):
412
+ for entity in event.entity_topological_sorter.get_entities(False):
413
+ await self.entities_state_applier.context.port_client.upsert_entity(entity,event.port_app_config.get_port_request_options(),user_agent_type,should_raise=False)
400
414
  async def sync_raw_all(
401
415
  self,
402
416
  _: dict[Any, Any] | None = None,
@@ -426,6 +440,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
426
440
  use_cache=False
427
441
  )
428
442
  logger.info(f"Resync will use the following mappings: {app_config.dict()}")
443
+
429
444
  try:
430
445
  did_fetched_current_state = True
431
446
  entities_at_port = await ocean.port_client.search_entities(
@@ -455,6 +470,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
455
470
  event.on_abort(lambda: task.cancel())
456
471
 
457
472
  creation_results.append(await task)
473
+
474
+ await self.sort_and_upsert_failed_entities(user_agent_type)
458
475
  except asyncio.CancelledError as e:
459
476
  logger.warning("Resync aborted successfully, skipping delete phase. This leads to an incomplete state")
460
477
  raise
@@ -9,7 +9,7 @@ from port_ocean.core.ocean_types import (
9
9
  RESYNC_EVENT_LISTENER,
10
10
  RESYNC_RESULT,
11
11
  )
12
- from port_ocean.core.utils import validate_result
12
+ from port_ocean.core.utils.utils import validate_result
13
13
  from port_ocean.exceptions.core import (
14
14
  RawObjectValidationException,
15
15
  OceanAbortException,
@@ -27,6 +27,10 @@ class Runtime(Enum):
27
27
  ) or installation_type == self.value
28
28
 
29
29
 
30
+ class PortAPIErrorMessage(Enum):
31
+ NOT_FOUND = "not_found"
32
+
33
+
30
34
  class Entity(BaseModel):
31
35
  identifier: Any
32
36
  blueprint: Any
@@ -0,0 +1,90 @@
1
+ from typing import Any, Generator
2
+ from port_ocean.context import event
3
+ from port_ocean.core.models import Entity
4
+
5
+ from loguru import logger
6
+
7
+ from graphlib import TopologicalSorter, CycleError
8
+ from typing import Set
9
+
10
+ from port_ocean.exceptions.core import OceanAbortException
11
+
12
+ Node = tuple[str, str]
13
+
14
+
15
+ class EntityTopologicalSorter:
16
+ def __init__(self) -> None:
17
+ self.entities: list[Entity] = []
18
+
19
+ def register_entity(
20
+ self,
21
+ entity: Entity,
22
+ ) -> None:
23
+ logger.debug(
24
+ f"Will retry upserting entity - {entity.identifier} at the end of resync"
25
+ )
26
+ self.entities.append(entity)
27
+
28
+ def should_execute(self) -> int:
29
+ return not event.event.port_app_config.create_missing_related_entities
30
+
31
+ def get_entities_count(self) -> int:
32
+ return len(self.entities)
33
+
34
+ def get_entities(self, sorted: bool = True) -> Generator[Entity, Any, None]:
35
+ if not sorted:
36
+ for entity in self.entities:
37
+ yield entity
38
+ return
39
+
40
+ sorted_and_mapped = EntityTopologicalSorter.order_by_entities_dependencies(
41
+ self.entities
42
+ )
43
+ for entity in sorted_and_mapped:
44
+ yield entity
45
+
46
+ @staticmethod
47
+ def node(entity: Entity) -> Node:
48
+ return entity.identifier, entity.blueprint
49
+
50
+ @staticmethod
51
+ def order_by_entities_dependencies(entities: list[Entity]) -> list[Entity]:
52
+ nodes: dict[Node, Set[Node]] = {}
53
+ entities_map = {}
54
+ for entity in entities:
55
+ nodes[EntityTopologicalSorter.node(entity)] = set()
56
+ entities_map[EntityTopologicalSorter.node(entity)] = entity
57
+
58
+ for entity in entities:
59
+ relation_target_ids: list[str] = sum(
60
+ [
61
+ identifiers if isinstance(identifiers, list) else [identifiers]
62
+ for identifiers in entity.relations.values()
63
+ if identifiers is not None
64
+ ],
65
+ [],
66
+ )
67
+ related_entities = [
68
+ related
69
+ for related in entities
70
+ if related.identifier in relation_target_ids
71
+ ]
72
+
73
+ for related_entity in related_entities:
74
+ if (
75
+ entity.blueprint is not related_entity.blueprint
76
+ or entity.identifier is not related_entity.identifier
77
+ ):
78
+ nodes[EntityTopologicalSorter.node(entity)].add(
79
+ EntityTopologicalSorter.node(related_entity)
80
+ )
81
+
82
+ sort_op = TopologicalSorter(nodes)
83
+ try:
84
+ return [entities_map[item] for item in sort_op.static_order()]
85
+ except CycleError as ex:
86
+ raise OceanAbortException(
87
+ "Cannot order entities due to cyclic dependencies. \n"
88
+ "If you do want to have cyclic dependencies, please make sure to set the keys"
89
+ " 'createMissingRelatedEntities' and 'deleteDependentEntities' in the integration config in Port."
90
+ ) from ex
@@ -0,0 +1,5 @@
1
+ from port_ocean.cli.commands.main import cli_start
2
+
3
+
4
+ if __name__ == "__main__":
5
+ cli_start()
@@ -9,7 +9,7 @@ from port_ocean.bootstrap import create_default_app
9
9
  from port_ocean.config.dynamic import default_config_factory
10
10
  from port_ocean.config.settings import ApplicationSettings, LogLevelType
11
11
  from port_ocean.core.defaults.initialize import initialize_defaults
12
- from port_ocean.core.utils import validate_integration_runtime
12
+ from port_ocean.core.utils.utils import validate_integration_runtime
13
13
  from port_ocean.log.logger_setup import setup_logger
14
14
  from port_ocean.ocean import Ocean
15
15
  from port_ocean.utils.misc import get_spec_file, load_module