port-ocean 0.5.22__tar.gz → 0.5.24.dev1__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 (128) hide show
  1. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/PKG-INFO +1 -1
  2. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +1 -1
  3. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/mixins/entities.py +1 -1
  4. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/defaults/initialize.py +24 -40
  5. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/__init__.py +1 -2
  6. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entity_processor/base.py +6 -17
  7. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +55 -44
  8. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/sync_raw.py +92 -58
  9. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/models.py +14 -0
  10. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/ocean_types.py +5 -1
  11. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/utils.py +22 -2
  12. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/port_defaults.py +1 -1
  13. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/queue_utils.py +10 -3
  14. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/pyproject.toml +1 -1
  15. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/LICENSE.md +0 -0
  16. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/README.md +0 -0
  17. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/__init__.py +0 -0
  18. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/bootstrap.py +0 -0
  19. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/__init__.py +0 -0
  20. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cli.py +0 -0
  21. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/__init__.py +0 -0
  22. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  23. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/defaults/clean.py +0 -0
  24. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/defaults/dock.py +0 -0
  25. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/defaults/group.py +0 -0
  26. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/list_integrations.py +0 -0
  27. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/main.py +0 -0
  28. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/new.py +0 -0
  29. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/pull.py +0 -0
  30. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/sail.py +0 -0
  31. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/commands/version.py +0 -0
  32. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  33. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  34. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  35. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  36. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  37. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  38. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  39. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  40. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  41. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -0
  42. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  43. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  44. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  45. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  46. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  47. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  48. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  49. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  50. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/cli/utils.py +0 -0
  51. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/__init__.py +0 -0
  52. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/__init__.py +0 -0
  53. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/authentication.py +0 -0
  54. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/client.py +0 -0
  55. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/mixins/__init__.py +0 -0
  56. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  57. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/mixins/integrations.py +0 -0
  58. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/mixins/migrations.py +0 -0
  59. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/retry_transport.py +0 -0
  60. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/types.py +0 -0
  61. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/clients/port/utils.py +0 -0
  62. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/config/__init__.py +0 -0
  63. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/config/base.py +0 -0
  64. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/config/dynamic.py +0 -0
  65. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/config/settings.py +0 -0
  66. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/consumers/__init__.py +0 -0
  67. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/consumers/kafka_consumer.py +0 -0
  68. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/context/__init__.py +0 -0
  69. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/context/event.py +0 -0
  70. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/context/ocean.py +0 -0
  71. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/context/resource.py +0 -0
  72. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/__init__.py +0 -0
  73. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/defaults/__init__.py +0 -0
  74. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/defaults/clean.py +0 -0
  75. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/defaults/common.py +0 -0
  76. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/__init__.py +0 -0
  77. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/base.py +0 -0
  78. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/factory.py +0 -0
  79. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/http.py +0 -0
  80. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/kafka.py +0 -0
  81. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/once.py +0 -0
  82. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/event_listener/polling.py +0 -0
  83. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/base.py +0 -0
  84. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  85. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  86. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  87. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  88. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  89. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  90. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  91. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  92. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  93. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  94. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  95. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/__init__.py +0 -0
  96. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/base.py +0 -0
  97. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  98. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/events.py +0 -0
  99. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/handler.py +0 -0
  100. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/sync.py +0 -0
  101. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/core/integrations/mixins/utils.py +0 -0
  102. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/__init__.py +0 -0
  103. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/api.py +0 -0
  104. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/base.py +0 -0
  105. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/clients.py +0 -0
  106. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/context.py +0 -0
  107. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/core.py +0 -0
  108. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/exceptions/utils.py +0 -0
  109. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/helpers/__init__.py +0 -0
  110. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/helpers/async_client.py +0 -0
  111. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/helpers/retry.py +0 -0
  112. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/log/__init__.py +0 -0
  113. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/log/handlers.py +0 -0
  114. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/log/logger_setup.py +0 -0
  115. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/log/sensetive.py +0 -0
  116. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/middlewares.py +0 -0
  117. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/ocean.py +0 -0
  118. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/py.typed +0 -0
  119. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/run.py +0 -0
  120. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/sonar-project.properties +0 -0
  121. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/__init__.py +0 -0
  122. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/async_http.py +0 -0
  123. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/async_iterators.py +0 -0
  124. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/cache.py +0 -0
  125. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/misc.py +0 -0
  126. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/repeat.py +0 -0
  127. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/utils/signal.py +0 -0
  128. {port_ocean-0.5.22 → port_ocean-0.5.24.dev1}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.5.22
3
+ Version: 0.5.24.dev1
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
@@ -1,4 +1,4 @@
1
- FROM python:3.11-slim-buster
1
+ FROM python:3.11-slim-bookworm
2
2
 
3
3
  ENV LIBRDKAFKA_VERSION 1.9.2
4
4
 
@@ -32,7 +32,7 @@ class EntityClientMixin:
32
32
  ) -> None:
33
33
  validation_only = request_options["validation_only"]
34
34
  async with self.semaphore:
35
- logger.info(
35
+ logger.debug(
36
36
  f"{'Validating' if validation_only else 'Upserting'} entity: {entity.identifier} of blueprint: {entity.blueprint}"
37
37
  )
38
38
  headers = await self.auth.headers(user_agent_type)
@@ -2,8 +2,8 @@ import asyncio
2
2
  from typing import Type, Any
3
3
 
4
4
  import httpx
5
- from starlette import status
6
5
  from loguru import logger
6
+ from starlette import status
7
7
 
8
8
  from port_ocean.clients.port.client import PortClient
9
9
  from port_ocean.clients.port.types import UserAgentType
@@ -12,6 +12,7 @@ from port_ocean.context.ocean import ocean
12
12
  from port_ocean.core.defaults.common import Defaults, get_port_integration_defaults
13
13
  from port_ocean.core.handlers.port_app_config.models import PortAppConfig
14
14
  from port_ocean.core.models import Blueprint
15
+ from port_ocean.core.utils import gather_and_split_errors_from_results
15
16
  from port_ocean.exceptions.port_defaults import (
16
17
  AbortDefaultCreationError,
17
18
  )
@@ -65,43 +66,30 @@ async def _create_resources(
65
66
  defaults.blueprints
66
67
  )
67
68
 
68
- blueprints_results = await asyncio.gather(
69
- *(
69
+ blueprints_results, _ = await gather_and_split_errors_from_results(
70
+ [
70
71
  port_client.get_blueprint(blueprint["identifier"], should_log=False)
71
72
  for blueprint in creation_stage
72
- ),
73
- return_exceptions=True,
73
+ ],
74
+ lambda item: isinstance(item, Blueprint),
74
75
  )
75
76
 
76
- existing_blueprints = [
77
- result.identifier
78
- for result in blueprints_results
79
- if not isinstance(result, httpx.HTTPStatusError)
80
- and isinstance(result, Blueprint)
81
- ]
82
-
83
- if existing_blueprints:
77
+ if blueprints_results:
84
78
  logger.info(
85
- f"Blueprints already exist: {existing_blueprints}. Skipping integration default creation..."
79
+ f"Blueprints already exist: {[result.identifier for result in blueprints_results]}. Skipping integration default creation..."
86
80
  )
87
81
  return
88
82
 
89
- create_results = await asyncio.gather(
90
- *(
83
+ created_blueprints, errors = await gather_and_split_errors_from_results(
84
+ (
91
85
  port_client.create_blueprint(
92
86
  blueprint, user_agent_type=UserAgentType.exporter
93
87
  )
94
88
  for blueprint in creation_stage
95
- ),
96
- return_exceptions=True,
89
+ )
97
90
  )
98
91
 
99
- errors = [result for result in create_results if isinstance(result, Exception)]
100
- created_blueprints = [
101
- result["identifier"]
102
- for result in create_results
103
- if not isinstance(result, BaseException)
104
- ]
92
+ created_blueprints_identifiers = [bp["identifier"] for bp in created_blueprints]
105
93
 
106
94
  if errors:
107
95
  for error in errors:
@@ -110,8 +98,8 @@ async def _create_resources(
110
98
  f"Failed to create resources: {error.response.text}. Rolling back changes..."
111
99
  )
112
100
 
113
- raise AbortDefaultCreationError(created_blueprints, errors)
114
- created_pages = []
101
+ raise AbortDefaultCreationError(created_blueprints_identifiers, errors)
102
+ created_pages_identifiers = []
115
103
  try:
116
104
  for patch_stage in blueprint_patches:
117
105
  await asyncio.gather(
@@ -141,19 +129,11 @@ async def _create_resources(
141
129
  )
142
130
  )
143
131
 
144
- create_pages_result = await asyncio.gather(
145
- *(port_client.create_page(page) for page in defaults.pages),
146
- return_exceptions=True,
132
+ created_pages, pages_errors = await gather_and_split_errors_from_results(
133
+ (port_client.create_page(page) for page in defaults.pages)
147
134
  )
148
-
149
- created_pages = [
150
- result.get("identifier", "")
151
- for result in create_pages_result
152
- if not isinstance(result, BaseException)
153
- ]
154
-
155
- pages_errors = [
156
- result for result in create_pages_result if isinstance(result, Exception)
135
+ created_pages_identifiers = [
136
+ page.get("identifier", "") for page in created_pages
157
137
  ]
158
138
 
159
139
  if pages_errors:
@@ -164,7 +144,9 @@ async def _create_resources(
164
144
  )
165
145
 
166
146
  raise AbortDefaultCreationError(
167
- created_blueprints, pages_errors, created_pages
147
+ created_blueprints_identifiers,
148
+ pages_errors,
149
+ created_pages_identifiers,
168
150
  )
169
151
 
170
152
  await port_client.create_integration(
@@ -176,7 +158,9 @@ async def _create_resources(
176
158
  logger.error(
177
159
  f"Failed to create resources: {err.response.text}. Rolling back changes..."
178
160
  )
179
- raise AbortDefaultCreationError(created_blueprints, [err], created_pages)
161
+ raise AbortDefaultCreationError(
162
+ created_blueprints_identifiers, [err], created_pages
163
+ )
180
164
 
181
165
 
182
166
  async def _initialize_defaults(
@@ -4,7 +4,7 @@ from .entities_state_applier.base import (
4
4
  from .entities_state_applier.port.applier import (
5
5
  HttpEntitiesStateApplier,
6
6
  )
7
- from .entity_processor.base import BaseEntityProcessor, EntityPortDiff
7
+ from .entity_processor.base import BaseEntityProcessor
8
8
  from .entity_processor.jq_entity_processor import (
9
9
  JQEntityProcessor,
10
10
  )
@@ -12,7 +12,6 @@ from .port_app_config.api import APIPortAppConfig
12
12
  from .port_app_config.base import BasePortAppConfig
13
13
 
14
14
  __all__ = [
15
- "EntityPortDiff",
16
15
  "BaseEntityProcessor",
17
16
  "JQEntityProcessor",
18
17
  "BasePortAppConfig",
@@ -1,30 +1,16 @@
1
1
  from abc import abstractmethod
2
- from dataclasses import dataclass, field
3
2
 
4
3
  from loguru import logger
5
4
 
6
5
  from port_ocean.core.handlers.base import BaseHandler
7
6
  from port_ocean.core.handlers.port_app_config.models import ResourceConfig
8
- from port_ocean.core.models import Entity
9
7
  from port_ocean.core.ocean_types import (
10
8
  RAW_ITEM,
9
+ CalculationResult,
11
10
  EntitySelectorDiff,
12
11
  )
13
12
 
14
13
 
15
- @dataclass
16
- class EntityPortDiff:
17
- """Represents the differences between entities for porting.
18
-
19
- This class holds the lists of deleted, modified, and created entities as part
20
- of the porting process.
21
- """
22
-
23
- deleted: list[Entity] = field(default_factory=list)
24
- modified: list[Entity] = field(default_factory=list)
25
- created: list[Entity] = field(default_factory=list)
26
-
27
-
28
14
  class BaseEntityProcessor(BaseHandler):
29
15
  """Abstract base class for processing and parsing entities.
30
16
 
@@ -41,7 +27,7 @@ class BaseEntityProcessor(BaseHandler):
41
27
  raw_data: list[RAW_ITEM],
42
28
  parse_all: bool = False,
43
29
  send_raw_data_examples_amount: int = 0,
44
- ) -> EntitySelectorDiff:
30
+ ) -> CalculationResult:
45
31
  pass
46
32
 
47
33
  async def parse_items(
@@ -50,7 +36,7 @@ class BaseEntityProcessor(BaseHandler):
50
36
  raw_data: list[RAW_ITEM],
51
37
  parse_all: bool = False,
52
38
  send_raw_data_examples_amount: int = 0,
53
- ) -> EntitySelectorDiff:
39
+ ) -> CalculationResult:
54
40
  """Public method to parse raw entity data and map it to an EntityDiff.
55
41
 
56
42
  Args:
@@ -63,6 +49,9 @@ class BaseEntityProcessor(BaseHandler):
63
49
  EntityDiff: The parsed entity differences.
64
50
  """
65
51
  with logger.contextualize(kind=mapping.kind):
52
+ if not raw_data:
53
+ return CalculationResult(EntitySelectorDiff([], []), [])
54
+
66
55
  return await self._parse_items(
67
56
  mapping, raw_data, parse_all, send_raw_data_examples_amount
68
57
  )
@@ -14,7 +14,9 @@ from port_ocean.core.models import Entity
14
14
  from port_ocean.core.ocean_types import (
15
15
  RAW_ITEM,
16
16
  EntitySelectorDiff,
17
+ CalculationResult,
17
18
  )
19
+ from port_ocean.core.utils import gather_and_split_errors_from_results, zip_and_sum
18
20
  from port_ocean.exceptions.core import EntityProcessorException
19
21
  from port_ocean.utils.queue_utils import process_in_queue
20
22
 
@@ -111,32 +113,34 @@ class JQEntityProcessor(BaseEntityProcessor):
111
113
  items_to_parse: str | None,
112
114
  selector_query: str,
113
115
  parse_all: bool = False,
114
- ) -> list[MappedEntity]:
116
+ ) -> tuple[list[MappedEntity], list[Exception]]:
117
+ raw_data = [data.copy()]
115
118
  if items_to_parse:
116
119
  items = await self._search(data, items_to_parse)
117
- if isinstance(items, list):
118
- return await asyncio.gather(
119
- *[
120
- self._get_mapped_entity(
121
- {"item": item, **data},
122
- raw_entity_mappings,
123
- selector_query,
124
- parse_all,
125
- )
126
- for item in items
127
- ]
120
+ if not isinstance(items, list):
121
+ logger.warning(
122
+ f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
123
+ f" Skipping..."
128
124
  )
129
- logger.warning(
130
- f"Failed to parse items for JQ expression {items_to_parse}, Expected list but got {type(items)}."
131
- f" Skipping..."
132
- )
133
- else:
134
- return [
135
- await self._get_mapped_entity(
136
- data, raw_entity_mappings, selector_query, parse_all
125
+ return [], []
126
+ raw_data = [{"item": item, **data} for item in items]
127
+
128
+ entities, errors = await gather_and_split_errors_from_results(
129
+ [
130
+ self._get_mapped_entity(
131
+ raw,
132
+ raw_entity_mappings,
133
+ selector_query,
134
+ parse_all,
137
135
  )
136
+ for raw in raw_data
138
137
  ]
139
- return [MappedEntity()]
138
+ )
139
+ if errors:
140
+ logger.error(
141
+ f"Failed to calculate entities with {len(errors)} errors. errors: {errors}"
142
+ )
143
+ return entities, errors
140
144
 
141
145
  @staticmethod
142
146
  async def _send_examples(data: list[dict[str, Any]], kind: str) -> None:
@@ -157,37 +161,44 @@ class JQEntityProcessor(BaseEntityProcessor):
157
161
  raw_results: list[RAW_ITEM],
158
162
  parse_all: bool = False,
159
163
  send_raw_data_examples_amount: int = 0,
160
- ) -> EntitySelectorDiff:
164
+ ) -> CalculationResult:
161
165
  raw_entity_mappings: dict[str, Any] = mapping.port.entity.mappings.dict(
162
166
  exclude_unset=True
163
167
  )
164
-
165
- calculated_entities_results = await process_in_queue(
166
- raw_results,
167
- self._calculate_entity,
168
- raw_entity_mappings,
169
- mapping.port.items_to_parse,
170
- mapping.selector.query,
171
- parse_all,
168
+ logger.info(f"Parsing {len(raw_results)} raw results into entities")
169
+ calculated_entities_results, errors = zip_and_sum(
170
+ await process_in_queue(
171
+ raw_results,
172
+ self._calculate_entity,
173
+ raw_entity_mappings,
174
+ mapping.port.items_to_parse,
175
+ mapping.selector.query,
176
+ parse_all,
177
+ )
178
+ )
179
+ logger.debug(
180
+ f"Finished parsing raw results into entities with {len(errors)} errors. errors: {errors}"
172
181
  )
173
182
 
174
183
  passed_entities = []
175
184
  failed_entities = []
176
185
  examples_to_send: list[dict[str, Any]] = []
177
- for entities_results in calculated_entities_results:
178
- for result in entities_results:
179
- if result.entity.get("identifier") and result.entity.get("blueprint"):
180
- parsed_entity = Entity.parse_obj(result.entity)
181
- if result.did_entity_pass_selector:
182
- passed_entities.append(parsed_entity)
183
- if (
184
- len(examples_to_send) < send_raw_data_examples_amount
185
- and result.raw_data is not None
186
- ):
187
- examples_to_send.append(result.raw_data)
188
- else:
189
- failed_entities.append(parsed_entity)
186
+ for result in calculated_entities_results:
187
+ if result.entity.get("identifier") and result.entity.get("blueprint"):
188
+ parsed_entity = Entity.parse_obj(result.entity)
189
+ if result.did_entity_pass_selector:
190
+ passed_entities.append(parsed_entity)
191
+ if (
192
+ len(examples_to_send) < send_raw_data_examples_amount
193
+ and result.raw_data is not None
194
+ ):
195
+ examples_to_send.append(result.raw_data)
196
+ else:
197
+ failed_entities.append(parsed_entity)
190
198
 
191
199
  await self._send_examples(examples_to_send, mapping.kind)
192
200
 
193
- return EntitySelectorDiff(passed=passed_entities, failed=failed_entities)
201
+ return CalculationResult(
202
+ EntitySelectorDiff(passed=passed_entities, failed=failed_entities),
203
+ errors,
204
+ )
@@ -3,6 +3,7 @@ import inspect
3
3
  import typing
4
4
  from typing import Callable, Awaitable, Any
5
5
 
6
+ import httpx
6
7
  from loguru import logger
7
8
 
8
9
  from port_ocean.clients.port.types import UserAgentType
@@ -24,12 +25,11 @@ from port_ocean.core.ocean_types import (
24
25
  RawEntityDiff,
25
26
  ASYNC_GENERATOR_RESYNC_TYPE,
26
27
  RAW_ITEM,
27
- EntitySelectorDiff,
28
+ CalculationResult,
28
29
  )
29
- from port_ocean.core.utils import zip_and_sum
30
+ from port_ocean.core.utils import zip_and_sum, gather_and_split_errors_from_results
30
31
  from port_ocean.exceptions.core import OceanAbortException
31
32
 
32
-
33
33
  SEND_RAW_DATA_EXAMPLES_AMOUNT = 5
34
34
 
35
35
 
@@ -101,21 +101,13 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
101
101
  logger.info(
102
102
  f"Found {len(tasks) + len(results)} resync tasks for {resource_config.kind}"
103
103
  )
104
-
105
- results_with_error = await asyncio.gather(*tasks, return_exceptions=True)
104
+ successful_results, errors = await gather_and_split_errors_from_results(tasks)
106
105
  results.extend(
107
106
  sum(
108
- [
109
- result
110
- for result in results_with_error
111
- if not isinstance(result, Exception)
112
- ],
107
+ successful_results,
113
108
  [],
114
109
  )
115
110
  )
116
- errors = [
117
- result for result in results_with_error if isinstance(result, Exception)
118
- ]
119
111
 
120
112
  logger.info(
121
113
  f"Triggered {len(tasks)} tasks for {resource_config.kind}, failed: {len(errors)}"
@@ -128,7 +120,7 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
128
120
  raw_diff: list[tuple[ResourceConfig, list[RAW_ITEM]]],
129
121
  parse_all: bool = False,
130
122
  send_raw_data_examples_amount: int = 0,
131
- ) -> list[EntitySelectorDiff]:
123
+ ) -> list[CalculationResult]:
132
124
  return await asyncio.gather(
133
125
  *(
134
126
  self.entity_processor.parse_items(
@@ -145,12 +137,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
145
137
  user_agent_type: UserAgentType,
146
138
  parse_all: bool = False,
147
139
  send_raw_data_examples_amount: int = 0,
148
- ) -> EntitySelectorDiff:
140
+ ) -> CalculationResult:
149
141
  objects_diff = await self._calculate_raw(
150
142
  [(resource, results)], parse_all, send_raw_data_examples_amount
151
143
  )
152
144
  await self.entities_state_applier.upsert(
153
- objects_diff[0].passed, user_agent_type
145
+ objects_diff[0].entity_selector_diff.passed, user_agent_type
154
146
  )
155
147
 
156
148
  return objects_diff[0]
@@ -160,13 +152,15 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
160
152
  resource: ResourceConfig,
161
153
  results: list[RAW_ITEM],
162
154
  user_agent_type: UserAgentType,
163
- ) -> list[Entity]:
155
+ ) -> tuple[list[Entity], list[Exception]]:
164
156
  objects_diff = await self._calculate_raw([(resource, results)])
157
+ entities_selector_diff, errors = objects_diff[0]
165
158
 
166
- entities_after: list[Entity] = objects_diff[0].passed
167
- await self.entities_state_applier.delete(entities_after, user_agent_type)
159
+ await self.entities_state_applier.delete(
160
+ entities_selector_diff.passed, user_agent_type
161
+ )
168
162
  logger.info("Finished unregistering change")
169
- return entities_after
163
+ return entities_selector_diff.passed, errors
170
164
 
171
165
  async def _register_in_batches(
172
166
  self, resource_config: ResourceConfig, user_agent_type: UserAgentType
@@ -183,39 +177,38 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
183
177
  send_raw_data_examples_amount = (
184
178
  SEND_RAW_DATA_EXAMPLES_AMOUNT if ocean.config.send_raw_data_examples else 0
185
179
  )
186
- entities = (
187
- await self._register_resource_raw(
188
- resource_config,
189
- raw_results,
190
- user_agent_type,
191
- send_raw_data_examples_amount=send_raw_data_examples_amount,
192
- )
193
- ).passed
180
+ all_entities, register_errors = await self._register_resource_raw(
181
+ resource_config,
182
+ raw_results,
183
+ user_agent_type,
184
+ send_raw_data_examples_amount=send_raw_data_examples_amount,
185
+ )
186
+ errors.extend(register_errors)
187
+ passed_entities = list(all_entities.passed)
194
188
 
195
189
  for generator in async_generators:
196
190
  try:
197
191
  async for items in generator:
198
192
  if send_raw_data_examples_amount > 0:
199
193
  send_raw_data_examples_amount = max(
200
- 0, send_raw_data_examples_amount - len(entities)
194
+ 0, send_raw_data_examples_amount - len(passed_entities)
201
195
  )
202
- entities.extend(
203
- (
204
- await self._register_resource_raw(
205
- resource_config,
206
- items,
207
- user_agent_type,
208
- send_raw_data_examples_amount=send_raw_data_examples_amount,
209
- )
210
- ).passed
196
+
197
+ entities, register_errors = await self._register_resource_raw(
198
+ resource_config,
199
+ items,
200
+ user_agent_type,
201
+ send_raw_data_examples_amount=send_raw_data_examples_amount,
211
202
  )
203
+ errors.extend(register_errors)
204
+ passed_entities.extend(entities.passed)
212
205
  except* OceanAbortException as error:
213
206
  errors.append(error)
214
207
 
215
208
  logger.info(
216
- f"Finished registering change for {len(results)} raw results for kind: {resource_config.kind}. {len(entities)} entities were affected"
209
+ f"Finished registering change for {len(results)} raw results for kind: {resource_config.kind}. {len(passed_entities)} entities were affected"
217
210
  )
218
- return entities, errors
211
+ return passed_entities, errors
219
212
 
220
213
  async def register_raw(
221
214
  self,
@@ -244,16 +237,26 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
244
237
  if not resource_mappings:
245
238
  return []
246
239
 
247
- diffs: list[EntitySelectorDiff] = await asyncio.gather(
248
- *(
249
- self._register_resource_raw(resource, results, user_agent_type, True)
250
- for resource in resource_mappings
240
+ diffs, errors = zip(
241
+ await asyncio.gather(
242
+ *(
243
+ self._register_resource_raw(
244
+ resource, results, user_agent_type, True
245
+ )
246
+ for resource in resource_mappings
247
+ )
251
248
  )
252
249
  )
253
250
 
254
- registered_entities, entities_to_delete = zip_and_sum(
255
- (entities_diff.passed, entities_diff.failed) for entities_diff in diffs
256
- )
251
+ if errors:
252
+ message = f"Failed to register {len(errors)} entities. Skipping delete phase due to incomplete state"
253
+ logger.error(message, exc_info=errors)
254
+ raise ExceptionGroup(
255
+ message,
256
+ errors,
257
+ )
258
+
259
+ registered_entities, entities_to_delete = zip_and_sum(diffs)
257
260
 
258
261
  registered_entities_attributes = {
259
262
  (entity.identifier, entity.blueprint) for entity in registered_entities
@@ -306,13 +309,23 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
306
309
  resource for resource in config.resources if resource.kind == kind
307
310
  ]
308
311
 
309
- return await asyncio.gather(
312
+ entities, errors = await asyncio.gather(
310
313
  *(
311
314
  self._unregister_resource_raw(resource, results, user_agent_type)
312
315
  for resource in resource_mappings
313
316
  )
314
317
  )
315
318
 
319
+ if errors:
320
+ message = f"Failed to unregister all entities with {len(errors)} errors"
321
+ logger.error(message, exc_info=errors)
322
+ raise ExceptionGroup(
323
+ message,
324
+ errors,
325
+ )
326
+
327
+ return entities
328
+
316
329
  async def update_raw_diff(
317
330
  self,
318
331
  kind: str,
@@ -337,14 +350,16 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
337
350
  with logger.contextualize(kind=kind):
338
351
  logger.info(f"Found {len(resource_mappings)} resources for {kind}")
339
352
 
340
- entities_before = await self._calculate_raw(
341
- [
342
- (mapping, raw_desired_state["before"])
343
- for mapping in resource_mappings
344
- ]
353
+ entities_before, _ = zip(
354
+ await self._calculate_raw(
355
+ [
356
+ (mapping, raw_desired_state["before"])
357
+ for mapping in resource_mappings
358
+ ]
359
+ )
345
360
  )
346
361
 
347
- entities_after = await self._calculate_raw(
362
+ entities_after, after_errors = await self._calculate_raw(
348
363
  [(mapping, raw_desired_state["after"]) for mapping in resource_mappings]
349
364
  )
350
365
 
@@ -356,6 +371,11 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
356
371
  (entities_diff.passed for entities_diff in entities_after), []
357
372
  )
358
373
 
374
+ if after_errors:
375
+ message = f"Failed to calculate diff for entities with {len(after_errors)} errors. Skipping delete phase due to incomplete state"
376
+ logger.error(message, exc_info=after_errors)
377
+ entities_before_flatten = []
378
+
359
379
  await self.entities_state_applier.apply_diff(
360
380
  {"before": entities_before_flatten, "after": entities_after_flatten},
361
381
  user_agent_type,
@@ -390,6 +410,16 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
390
410
  use_cache=False
391
411
  )
392
412
 
413
+ try:
414
+ entities_at_port = await ocean.port_client.search_entities(
415
+ user_agent_type
416
+ )
417
+ except httpx.HTTPError as e:
418
+ logger.warning(
419
+ "Failed to fetch the current state of entities at Port. Skipping delete phase due to unknown initial state"
420
+ )
421
+ entities_at_port = []
422
+
393
423
  creation_results: list[tuple[list[Entity], list[Exception]]] = []
394
424
 
395
425
  try:
@@ -407,6 +437,13 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
407
437
  except asyncio.CancelledError as e:
408
438
  logger.warning("Resync aborted successfully")
409
439
  else:
440
+ if not entities_at_port:
441
+ logger.warning(
442
+ "Due to an error before the resync, the previous state of entities at Port is unknown."
443
+ " Skipping delete phase due to unknown initial state."
444
+ )
445
+ return
446
+
410
447
  logger.info("Starting resync diff calculation")
411
448
  flat_created_entities, errors = zip_and_sum(creation_results) or [
412
449
  [],
@@ -424,11 +461,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
424
461
 
425
462
  logger.error(message, exc_info=error_group)
426
463
  else:
427
- entities_at_port = await ocean.port_client.search_entities(
428
- user_agent_type
429
- )
430
464
  logger.info(
431
- f"Running resync diff calculation, number of entities found at Port: {len(entities_at_port)}, number of entities found during sync: {len(flat_created_entities)}"
465
+ f"Running resync diff calculation, number of entities at Port before resync: {len(entities_at_port)}, number of entities created during sync: {len(flat_created_entities)}"
432
466
  )
433
467
  await self.entities_state_applier.delete_diff(
434
468
  {"before": entities_at_port, "after": flat_created_entities},
@@ -1,3 +1,4 @@
1
+ from dataclasses import dataclass, field
1
2
  from typing import Any
2
3
 
3
4
  from pydantic import BaseModel
@@ -34,3 +35,16 @@ class Migration(BaseModel):
34
35
  sourceBlueprint: str
35
36
  mapping: dict[str, Any]
36
37
  status: str
38
+
39
+
40
+ @dataclass
41
+ class EntityPortDiff:
42
+ """Represents the differences between entities for porting.
43
+
44
+ This class holds the lists of deleted, modified, and created entities as part
45
+ of the porting process.
46
+ """
47
+
48
+ deleted: list[Entity] = field(default_factory=list)
49
+ modified: list[Entity] = field(default_factory=list)
50
+ created: list[Entity] = field(default_factory=list)