port-ocean 0.10.6__tar.gz → 0.10.10__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 (140) hide show
  1. {port_ocean-0.10.6 → port_ocean-0.10.10}/PKG-INFO +1 -1
  2. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/new.py +12 -0
  3. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/mixins/blueprints.py +8 -7
  4. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/mixins/entities.py +9 -2
  5. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/utils.py +1 -1
  6. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/config/settings.py +1 -1
  7. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/defaults/initialize.py +32 -44
  8. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/port/applier.py +17 -8
  9. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/port_app_config/models.py +8 -4
  10. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/sync_raw.py +8 -1
  11. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/models.py +4 -0
  12. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/port_defaults.py +0 -2
  13. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/helpers/retry.py +5 -0
  14. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/middlewares.py +10 -2
  15. port_ocean-0.10.10/port_ocean/tests/clients/port/mixins/test_entities.py +53 -0
  16. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/tests/helpers/__init__.py +7 -2
  17. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/async_iterators.py +1 -1
  18. {port_ocean-0.10.6 → port_ocean-0.10.10}/pyproject.toml +1 -1
  19. port_ocean-0.10.6/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -74
  20. {port_ocean-0.10.6 → port_ocean-0.10.10}/LICENSE.md +0 -0
  21. {port_ocean-0.10.6 → port_ocean-0.10.10}/README.md +0 -0
  22. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/__init__.py +0 -0
  23. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/bootstrap.py +0 -0
  24. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/__init__.py +0 -0
  25. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cli.py +0 -0
  26. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/__init__.py +0 -0
  27. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  28. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/defaults/clean.py +0 -0
  29. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/defaults/dock.py +0 -0
  30. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/defaults/group.py +0 -0
  31. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/list_integrations.py +0 -0
  32. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/main.py +0 -0
  33. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/pull.py +0 -0
  34. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/sail.py +0 -0
  35. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/commands/version.py +0 -0
  36. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  37. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  38. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  39. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  40. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  41. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  42. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  43. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  44. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  45. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  46. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  47. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  48. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  49. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  50. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  51. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  52. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  53. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  54. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  55. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  56. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  57. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  58. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  59. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/cli/utils.py +0 -0
  60. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/__init__.py +0 -0
  61. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/__init__.py +0 -0
  62. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/authentication.py +0 -0
  63. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/client.py +0 -0
  64. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/mixins/__init__.py +0 -0
  65. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/mixins/integrations.py +0 -0
  66. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/mixins/migrations.py +0 -0
  67. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/retry_transport.py +0 -0
  68. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/clients/port/types.py +0 -0
  69. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/config/__init__.py +0 -0
  70. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/config/base.py +0 -0
  71. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/config/dynamic.py +0 -0
  72. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/consumers/__init__.py +0 -0
  73. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/consumers/kafka_consumer.py +0 -0
  74. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/context/__init__.py +0 -0
  75. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/context/event.py +0 -0
  76. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/context/ocean.py +0 -0
  77. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/context/resource.py +0 -0
  78. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/__init__.py +0 -0
  79. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/defaults/__init__.py +0 -0
  80. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/defaults/clean.py +0 -0
  81. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/defaults/common.py +0 -0
  82. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/__init__.py +0 -0
  83. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/base.py +0 -0
  84. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/factory.py +0 -0
  85. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/http.py +0 -0
  86. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/kafka.py +0 -0
  87. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/once.py +0 -0
  88. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/event_listener/polling.py +0 -0
  89. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/__init__.py +0 -0
  90. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/base.py +0 -0
  91. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  92. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  93. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  94. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  95. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  96. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  97. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  98. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  99. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  100. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  101. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  102. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  103. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  104. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/__init__.py +0 -0
  105. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/base.py +0 -0
  106. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  107. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/events.py +0 -0
  108. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/handler.py +0 -0
  109. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/sync.py +0 -0
  110. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/integrations/mixins/utils.py +0 -0
  111. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/ocean_types.py +0 -0
  112. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/core/utils.py +0 -0
  113. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/__init__.py +0 -0
  114. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/api.py +0 -0
  115. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/base.py +0 -0
  116. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/clients.py +0 -0
  117. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/context.py +0 -0
  118. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/core.py +0 -0
  119. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/exceptions/utils.py +0 -0
  120. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/helpers/__init__.py +0 -0
  121. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/helpers/async_client.py +0 -0
  122. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/log/__init__.py +0 -0
  123. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/log/handlers.py +0 -0
  124. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/log/logger_setup.py +0 -0
  125. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/log/sensetive.py +0 -0
  126. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/ocean.py +0 -0
  127. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/py.typed +0 -0
  128. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/run.py +0 -0
  129. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/sonar-project.properties +0 -0
  130. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/tests/__init__.py +0 -0
  131. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/tests/test_sample.py +0 -0
  132. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/__init__.py +0 -0
  133. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/async_http.py +0 -0
  134. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/cache.py +0 -0
  135. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/misc.py +0 -0
  136. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/queue_utils.py +0 -0
  137. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/repeat.py +0 -0
  138. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/signal.py +0 -0
  139. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/utils/time.py +0 -0
  140. {port_ocean-0.10.6 → port_ocean-0.10.10}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.10.6
3
+ Version: 0.10.10
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
@@ -9,6 +9,17 @@ from port_ocean.cli.commands.main import cli_start, print_logo, console
9
9
  from port_ocean.cli.utils import cli_root_path
10
10
 
11
11
 
12
+ def symlink_makefile(result: str, name: str) -> None:
13
+ infra_make_file = os.path.join(
14
+ os.path.dirname(result), "../integrations/_infra/Makefile"
15
+ )
16
+ target_link_make_file = os.path.join(
17
+ os.path.dirname(result), f"../integrations/{name}/Makefile"
18
+ )
19
+
20
+ os.link(infra_make_file, target_link_make_file)
21
+
22
+
12
23
  def add_vscode_configuration(result: str, name: str) -> None:
13
24
  vscode_entry_root_path = "${workspaceFolder}/integrations/" + name
14
25
  new_vscode_entry = {
@@ -66,6 +77,7 @@ def new(path: str, is_private_integration: bool) -> None:
66
77
 
67
78
  if not is_private_integration:
68
79
  add_vscode_configuration(result, name)
80
+ symlink_makefile(result, name)
69
81
 
70
82
  console.print(
71
83
  "\n🌊 Ahoy, Captain! Your project is ready to set sail into the vast ocean of possibilities!",
@@ -64,7 +64,6 @@ class BlueprintClientMixin:
64
64
  f"Deleting blueprint with id: {identifier} with all entities: {delete_entities}"
65
65
  )
66
66
  headers = await self.auth.headers(user_agent_type)
67
- response = None
68
67
 
69
68
  if not delete_entities:
70
69
  response = await self.client.delete(
@@ -82,7 +81,9 @@ class BlueprintClientMixin:
82
81
  handle_status_code(response, should_raise)
83
82
  return response.json().get("migrationId", "")
84
83
 
85
- async def create_action(self, action: dict[str, Any]) -> None:
84
+ async def create_action(
85
+ self, action: dict[str, Any], should_log: bool = True
86
+ ) -> None:
86
87
  logger.info(f"Creating action: {action}")
87
88
  response = await self.client.post(
88
89
  f"{self.auth.api_url}/actions",
@@ -90,12 +91,13 @@ class BlueprintClientMixin:
90
91
  headers=await self.auth.headers(),
91
92
  )
92
93
 
93
- handle_status_code(response)
94
+ handle_status_code(response, should_log=should_log)
94
95
 
95
96
  async def create_scorecard(
96
97
  self,
97
98
  blueprint_identifier: str,
98
99
  scorecard: dict[str, Any],
100
+ should_log: bool = True,
99
101
  ) -> None:
100
102
  logger.info(f"Creating scorecard: {scorecard}")
101
103
  response = await self.client.post(
@@ -104,11 +106,10 @@ class BlueprintClientMixin:
104
106
  headers=await self.auth.headers(),
105
107
  )
106
108
 
107
- handle_status_code(response)
109
+ handle_status_code(response, should_log=should_log)
108
110
 
109
111
  async def create_page(
110
- self,
111
- page: dict[str, Any],
112
+ self, page: dict[str, Any], should_log: bool = True
112
113
  ) -> dict[str, Any]:
113
114
  logger.info(f"Creating page: {page}")
114
115
  response = await self.client.post(
@@ -117,7 +118,7 @@ class BlueprintClientMixin:
117
118
  headers=await self.auth.headers(),
118
119
  )
119
120
 
120
- handle_status_code(response)
121
+ handle_status_code(response, should_log=should_log)
121
122
  return page
122
123
 
123
124
  async def delete_page(
@@ -29,7 +29,7 @@ class EntityClientMixin:
29
29
  request_options: RequestOptions,
30
30
  user_agent_type: UserAgentType | None = None,
31
31
  should_raise: bool = True,
32
- ) -> Entity:
32
+ ) -> Entity | None:
33
33
  validation_only = request_options["validation_only"]
34
34
  async with self.semaphore:
35
35
  logger.debug(
@@ -58,9 +58,16 @@ class EntityClientMixin:
58
58
  )
59
59
  handle_status_code(response, should_raise)
60
60
  result = response.json()
61
+
61
62
  result_entity = (
62
63
  Entity.parse_obj(result["entity"]) if result.get("entity") else entity
63
64
  )
65
+
66
+ # Happens when upsert fails and search identifier is defined.
67
+ # We return None to ignore the entity later in the delete process
68
+ if result_entity.is_using_search_identifier:
69
+ return None
70
+
64
71
  # In order to save memory we'll keep only the identifier, blueprint and relations of the
65
72
  # upserted entity result for later calculations
66
73
  reduced_entity = Entity(
@@ -94,7 +101,7 @@ class EntityClientMixin:
94
101
  )
95
102
  for entity in entities
96
103
  ),
97
- return_exceptions=should_raise,
104
+ return_exceptions=True,
98
105
  )
99
106
  entity_results = [
100
107
  entity for entity in modified_entities_results if isinstance(entity, Entity)
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  # The max_keepalive_connections can't be too high, as it will cause the application to run out of available connections.
19
19
  PORT_HTTP_MAX_CONNECTIONS_LIMIT = 200
20
20
  PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS = 50
21
- PORT_HTTP_TIMEOUT = 10.0
21
+ PORT_HTTP_TIMEOUT = 60.0
22
22
 
23
23
  PORT_HTTPX_TIMEOUT = httpx.Timeout(PORT_HTTP_TIMEOUT)
24
24
  PORT_HTTPX_LIMITS = httpx.Limits(
@@ -67,7 +67,7 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
67
67
  allow_environment_variables_jq_access: bool = True
68
68
  initialize_port_resources: bool = True
69
69
  scheduled_resync_interval: int | None = None
70
- client_timeout: int = 30
70
+ client_timeout: int = 60
71
71
  send_raw_data_examples: bool = True
72
72
  port: PortSettings
73
73
  event_listener: EventListenerSettingsType = Field(
@@ -120,7 +120,7 @@ async def _create_resources(
120
120
  )
121
121
  return
122
122
 
123
- created_blueprints, errors = await gather_and_split_errors_from_results(
123
+ created_blueprints, blueprint_errors = await gather_and_split_errors_from_results(
124
124
  (
125
125
  port_client.create_blueprint(
126
126
  blueprint, user_agent_type=UserAgentType.exporter
@@ -131,15 +131,17 @@ async def _create_resources(
131
131
 
132
132
  created_blueprints_identifiers = [bp["identifier"] for bp in created_blueprints]
133
133
 
134
- if errors:
135
- for error in errors:
134
+ if blueprint_errors:
135
+ for error in blueprint_errors:
136
136
  if isinstance(error, httpx.HTTPStatusError):
137
137
  logger.warning(
138
138
  f"Failed to create resources: {error.response.text}. Rolling back changes..."
139
139
  )
140
140
 
141
- raise AbortDefaultCreationError(created_blueprints_identifiers, errors)
142
- created_pages_identifiers = []
141
+ raise AbortDefaultCreationError(
142
+ created_blueprints_identifiers, blueprint_errors
143
+ )
144
+
143
145
  try:
144
146
  for patch_stage in blueprint_patches:
145
147
  await asyncio.gather(
@@ -153,44 +155,43 @@ async def _create_resources(
153
155
  )
154
156
  )
155
157
 
156
- await asyncio.gather(
157
- *(port_client.create_action(action) for action in defaults.actions)
158
+ except httpx.HTTPStatusError as err:
159
+ logger.error(f"Failed to create resources: {err.response.text}. continuing...")
160
+ raise AbortDefaultCreationError(created_blueprints_identifiers, [err])
161
+ try:
162
+ created_actions, actions_errors = await gather_and_split_errors_from_results(
163
+ (
164
+ port_client.create_action(action, should_log=False)
165
+ for action in defaults.actions
166
+ )
158
167
  )
159
168
 
160
- await asyncio.gather(
161
- *(
162
- port_client.create_scorecard(blueprint_scorecards["blueprint"], action)
163
- for blueprint_scorecards in defaults.scorecards
164
- for action in blueprint_scorecards["data"]
169
+ created_scorecards, scorecards_errors = (
170
+ await gather_and_split_errors_from_results(
171
+ (
172
+ port_client.create_scorecard(
173
+ blueprint_scorecards["blueprint"], action, should_log=False
174
+ )
175
+ for blueprint_scorecards in defaults.scorecards
176
+ for action in blueprint_scorecards["data"]
177
+ )
165
178
  )
166
179
  )
167
180
 
168
181
  created_pages, pages_errors = await gather_and_split_errors_from_results(
169
- (port_client.create_page(page) for page in defaults.pages)
182
+ (port_client.create_page(page, should_log=False) for page in defaults.pages)
170
183
  )
171
- created_pages_identifiers = [
172
- page.get("identifier", "") for page in created_pages
173
- ]
174
184
 
175
- if pages_errors:
176
- for error in pages_errors:
185
+ errors = actions_errors + scorecards_errors + pages_errors
186
+ if errors:
187
+ for error in errors:
177
188
  if isinstance(error, httpx.HTTPStatusError):
178
189
  logger.warning(
179
- f"Failed to create resources: {error.response.text}. Rolling back changes..."
190
+ f"Failed to create resource: {error.response.text}. continuing..."
180
191
  )
181
192
 
182
- raise AbortDefaultCreationError(
183
- created_blueprints_identifiers,
184
- pages_errors,
185
- created_pages_identifiers,
186
- )
187
- except httpx.HTTPStatusError as err:
188
- logger.error(
189
- f"Failed to create resources: {err.response.text}. Rolling back changes..."
190
- )
191
- raise AbortDefaultCreationError(
192
- created_blueprints_identifiers, [err], created_pages_identifiers
193
- )
193
+ except Exception as err:
194
+ logger.error(f"Failed to create resources: {err}. continuing...")
194
195
 
195
196
 
196
197
  async def _initialize_defaults(
@@ -227,19 +228,6 @@ async def _initialize_defaults(
227
228
  for identifier in e.blueprints_to_rollback
228
229
  )
229
230
  )
230
- if e.pages_to_rollback:
231
- logger.warning(
232
- f"Failed to create resources. Rolling back pages : {e.pages_to_rollback}"
233
- )
234
- await asyncio.gather(
235
- *(
236
- port_client.delete_page(
237
- identifier,
238
- )
239
- for identifier in e.pages_to_rollback
240
- )
241
- )
242
-
243
231
  raise ExceptionGroup(str(e), e.errors)
244
232
 
245
233
 
@@ -106,18 +106,27 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
106
106
  should_raise=False,
107
107
  )
108
108
  else:
109
+ entities_with_search_identifier: list[Entity] = []
110
+ entities_without_search_identifier: list[Entity] = []
111
+ 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
+
109
117
  ordered_created_entities = reversed(
110
- order_by_entities_dependencies(entities)
118
+ entities_with_search_identifier
119
+ + order_by_entities_dependencies(entities_without_search_identifier)
111
120
  )
112
121
  for entity in ordered_created_entities:
113
- modified_entities.append(
114
- await self.context.port_client.upsert_entity(
115
- entity,
116
- event.port_app_config.get_port_request_options(),
117
- user_agent_type,
118
- should_raise=False,
119
- )
122
+ upsertedEntity = await self.context.port_client.upsert_entity(
123
+ entity,
124
+ event.port_app_config.get_port_request_options(),
125
+ user_agent_type,
126
+ should_raise=False,
120
127
  )
128
+ if upsertedEntity:
129
+ modified_entities.append(upsertedEntity)
121
130
  return modified_entities
122
131
 
123
132
  async def delete(
@@ -13,18 +13,22 @@ class Rule(BaseModel):
13
13
  value: str
14
14
 
15
15
 
16
- class SearchRelation(BaseModel):
16
+ class IngestSearchQuery(BaseModel):
17
17
  combinator: str
18
- rules: list[Rule | SearchRelation]
18
+ rules: list[Rule | IngestSearchQuery]
19
19
 
20
20
 
21
21
  class EntityMapping(BaseModel):
22
- identifier: str
22
+ identifier: str | IngestSearchQuery
23
23
  title: str | None
24
24
  blueprint: str
25
25
  team: str | None
26
26
  properties: dict[str, str] = Field(default_factory=dict)
27
- relations: dict[str, str | SearchRelation] = Field(default_factory=dict)
27
+ relations: dict[str, str | IngestSearchQuery] = Field(default_factory=dict)
28
+
29
+ @property
30
+ def is_using_search_identifier(self) -> bool:
31
+ return isinstance(self.identifier, dict)
28
32
 
29
33
 
30
34
  class MappingsConfig(BaseModel):
@@ -154,6 +154,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
154
154
  results: list[RAW_ITEM],
155
155
  user_agent_type: UserAgentType,
156
156
  ) -> tuple[list[Entity], list[Exception]]:
157
+ if resource.port.entity.mappings.is_using_search_identifier:
158
+ logger.info(
159
+ f"Skip unregistering resource of kind {resource.kind}, as mapping defined with search identifier"
160
+ )
161
+ return [], []
162
+
157
163
  objects_diff = await self._calculate_raw([(resource, results)])
158
164
  entities_selector_diff, errors = objects_diff[0]
159
165
 
@@ -272,7 +278,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
272
278
  [
273
279
  entity
274
280
  for entity in entities_to_delete
275
- if (entity.identifier, entity.blueprint)
281
+ if not entity.is_using_search_identifier
282
+ and (entity.identifier, entity.blueprint)
276
283
  not in registered_entities_attributes
277
284
  ],
278
285
  )
@@ -19,6 +19,10 @@ class Entity(BaseModel):
19
19
  properties: dict[str, Any] = {}
20
20
  relations: dict[str, Any] = {}
21
21
 
22
+ @property
23
+ def is_using_search_identifier(self) -> bool:
24
+ return isinstance(self.identifier, dict)
25
+
22
26
 
23
27
  class BlueprintRelation(BaseModel):
24
28
  many: bool
@@ -6,10 +6,8 @@ class AbortDefaultCreationError(BaseOceanException):
6
6
  self,
7
7
  blueprints_to_rollback: list[str],
8
8
  errors: list[Exception],
9
- pages_to_rollback: list[str] | None = None,
10
9
  ):
11
10
  self.blueprints_to_rollback = blueprints_to_rollback
12
- self.pages_to_rollback = pages_to_rollback
13
11
  self.errors = errors
14
12
  super().__init__("Aborting defaults creation")
15
13
 
@@ -290,6 +290,11 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
290
290
  if remaining_attempts < 1:
291
291
  self._log_error(request, error)
292
292
  raise
293
+ except httpx.ReadTimeout as e:
294
+ error = e
295
+ if remaining_attempts < 1:
296
+ self._log_error(request, error)
297
+ raise
293
298
  except httpx.TimeoutException as e:
294
299
  error = e
295
300
  if remaining_attempts < 1:
@@ -52,7 +52,14 @@ async def request_handler(
52
52
  request_id = generate_uuid()
53
53
 
54
54
  with logger.contextualize(request_id=request_id):
55
- logger.bind(url=str(request.url), method=request.method).info("Request started")
55
+ log_level = (
56
+ "DEBUG"
57
+ if request.url.path == "/docs" or request.url.path == "/openapi.json"
58
+ else "INFO"
59
+ )
60
+ logger.bind(url=str(request.url), method=request.method).log(
61
+ log_level, f"Request to {request.url.path} started"
62
+ )
56
63
  response = await _handle_silently(call_next, request)
57
64
 
58
65
  end_time = get_time(seconds_precision=False)
@@ -61,5 +68,6 @@ async def request_handler(
61
68
  response.headers["X-Process-Time"] = str(time_elapsed)
62
69
  logger.bind(
63
70
  time_elapsed=time_elapsed, response_status=response.status_code
64
- ).info("Request ended")
71
+ ).log(log_level, f"Request to {request.url.path} ended")
72
+
65
73
  return response
@@ -0,0 +1,53 @@
1
+ from typing import Any
2
+ from unittest.mock import MagicMock
3
+
4
+ import pytest
5
+
6
+ from port_ocean.clients.port.mixins.entities import EntityClientMixin
7
+ from port_ocean.core.models import Entity
8
+ from httpx import ReadTimeout
9
+
10
+
11
+ errored_entity_identifier: str = "a"
12
+ expected_result_entities = [
13
+ Entity(identifier="b", blueprint="b"),
14
+ Entity(identifier="c", blueprint="c"),
15
+ ]
16
+ all_entities = [
17
+ Entity(identifier=errored_entity_identifier, blueprint="a")
18
+ ] + expected_result_entities
19
+
20
+
21
+ async def mock_upsert_entity(entity: Entity, *args: Any, **kwargs: Any) -> Entity:
22
+ if entity.identifier == errored_entity_identifier:
23
+ raise ReadTimeout("")
24
+ else:
25
+ return entity
26
+
27
+
28
+ @pytest.fixture
29
+ async def entity_client(monkeypatch: Any) -> EntityClientMixin:
30
+ # Arrange
31
+ entity_client = EntityClientMixin(auth=MagicMock(), client=MagicMock())
32
+ monkeypatch.setattr(entity_client, "upsert_entity", mock_upsert_entity)
33
+
34
+ return entity_client
35
+
36
+
37
+ async def test_batch_upsert_entities_read_timeout_should_raise_false(
38
+ entity_client: EntityClientMixin,
39
+ ) -> None:
40
+ result_entities = await entity_client.batch_upsert_entities(
41
+ entities=all_entities, request_options=MagicMock(), should_raise=False
42
+ )
43
+
44
+ assert result_entities == expected_result_entities
45
+
46
+
47
+ async def test_batch_upsert_entities_read_timeout_should_raise_true(
48
+ entity_client: EntityClientMixin,
49
+ ) -> None:
50
+ with pytest.raises(ReadTimeout):
51
+ await entity_client.batch_upsert_entities(
52
+ entities=all_entities, request_options=MagicMock(), should_raise=True
53
+ )
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  from inspect import getmembers
3
+ from pathlib import Path
3
4
  from typing import Dict, List, Set, Tuple, Union
4
5
 
5
6
  from yaml import safe_load
@@ -8,13 +9,17 @@ from port_ocean.bootstrap import create_default_app
8
9
  from port_ocean.core.handlers.port_app_config.models import ResourceConfig
9
10
  from port_ocean.core.ocean_types import RESYNC_RESULT
10
11
  from port_ocean.ocean import Ocean
11
- from port_ocean.utils.misc import load_module
12
+ from port_ocean.utils.misc import get_spec_file, load_module
12
13
 
13
14
 
14
15
  def get_integration_ocean_app(integration_path: str) -> Ocean:
16
+ spec_file = get_spec_file(Path(integration_path))
17
+
18
+ config_factory = None if not spec_file else spec_file.get("configurations", [])
19
+
15
20
  default_app = create_default_app(
16
21
  integration_path,
17
- None,
22
+ config_factory,
18
23
  {
19
24
  "port": {
20
25
  "client_id": "bla",
@@ -36,7 +36,7 @@ async def stream_async_iterators_tasks(
36
36
  :return: A stream of results
37
37
  """
38
38
  if not tasks:
39
- raise StopAsyncIteration("No tasks provided")
39
+ return
40
40
 
41
41
  if len(tasks) == 1:
42
42
  async for batch_items in tasks[0]:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.10.6"
3
+ version = "0.10.10"
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"
@@ -1,74 +0,0 @@
1
- ACTIVATE := . .venv/bin/activate
2
-
3
- define run_checks
4
- exit_code=0; \
5
- cd $1; \
6
- poetry check || exit_code=$$?;\
7
- mypy . || exit_code=$$?; \
8
- ruff check . || exit_code=$$?; \
9
- black --check . || exit_code=$$?; \
10
- if [ $$exit_code -eq 1 ]; then \
11
- echo "\033[0;31mOne or more checks failed with exit code $$exit_code\033[0m"; \
12
- else \
13
- echo "\033[0;32mAll checks executed successfully.\033[0m"; \
14
- fi; \
15
- exit $$exit_code
16
- endef
17
-
18
- define install_poetry
19
- if ! command -v poetry &> /dev/null; then \
20
- pip install --upgrade pip; \
21
- pip install poetry; \
22
- else \
23
- echo "Poetry is already installed."; \
24
- fi
25
- endef
26
-
27
- define deactivate_virtualenv
28
- if [ -n "$$VIRTUAL_ENV" ]; then \
29
- unset VIRTUAL_ENV; \
30
- unset PYTHONHOME; \
31
- unset -f pydoc >/dev/null 2>&1; \
32
- OLD_PATH="$$PATH"; \
33
- PATH=$$(echo -n "$$PATH" | awk -v RS=: -v ORS=: '/\/virtualenv\/bin$$/ {next} {print}'); \
34
- export PATH; \
35
- hash -r; \
36
- echo "Deactivated the virtual environment."; \
37
- fi
38
- endef
39
-
40
- .SILENT: install install/prod lint run test clean
41
-
42
- install:
43
- $(call deactivate_virtualenv) && \
44
- $(call install_poetry) && \
45
- poetry install --with dev
46
-
47
- install/prod:
48
- $(call install_poetry) && \
49
- poetry install --without dev --no-root --no-interaction --no-ansi --no-cache
50
-
51
- lint:
52
- $(ACTIVATE) && \
53
- $(call run_checks,.)
54
-
55
- run:
56
- $(ACTIVATE) && ocean sail
57
-
58
- test:
59
- $(ACTIVATE) && poetry run pytest -n auto
60
-
61
- clean:
62
- @find . -name '.venv' -type d -exec rm -rf {} \;
63
- @find . -name '*.pyc' -exec rm -rf {} \;
64
- @find . -name '__pycache__' -exec rm -rf {} \;
65
- @find . -name 'Thumbs.db' -exec rm -rf {} \;
66
- @find . -name '*~' -exec rm -rf {} \;
67
- rm -rf .cache
68
- rm -rf build
69
- rm -rf dist
70
- rm -rf *.egg-info
71
- rm -rf htmlcov
72
- rm -rf .tox/
73
- rm -rf docs/_build
74
- rm -rf dist/
File without changes
File without changes