port-ocean 0.5.10__tar.gz → 0.5.14__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 (129) hide show
  1. {port_ocean-0.5.10 → port_ocean-0.5.14}/PKG-INFO +6 -5
  2. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/authentication.py +3 -1
  3. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/mixins/entities.py +36 -47
  4. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/retry_transport.py +0 -5
  5. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/port/applier.py +13 -75
  6. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entity_processor/base.py +17 -7
  7. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +42 -41
  8. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/sync_raw.py +80 -51
  9. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/ocean_types.py +16 -11
  10. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/helpers/retry.py +49 -4
  11. port_ocean-0.5.14/port_ocean/utils/async_iterators.py +49 -0
  12. {port_ocean-0.5.10 → port_ocean-0.5.14}/pyproject.toml +8 -7
  13. port_ocean-0.5.10/port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -40
  14. {port_ocean-0.5.10 → port_ocean-0.5.14}/LICENSE.md +0 -0
  15. {port_ocean-0.5.10 → port_ocean-0.5.14}/README.md +0 -0
  16. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/__init__.py +0 -0
  17. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/bootstrap.py +0 -0
  18. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/__init__.py +0 -0
  19. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cli.py +0 -0
  20. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/__init__.py +0 -0
  21. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  22. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/defaults/clean.py +0 -0
  23. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/defaults/dock.py +0 -0
  24. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/defaults/group.py +0 -0
  25. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/list_integrations.py +0 -0
  26. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/main.py +0 -0
  27. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/new.py +0 -0
  28. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/pull.py +0 -0
  29. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/sail.py +0 -0
  30. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/commands/version.py +0 -0
  31. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  32. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  33. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  34. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  35. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  36. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  37. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  38. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  39. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  40. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  41. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -0
  42. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  43. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  44. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -0
  45. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  46. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  47. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  48. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  49. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  50. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  51. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/cli/utils.py +0 -0
  52. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/__init__.py +0 -0
  53. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/__init__.py +0 -0
  54. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/client.py +0 -0
  55. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/mixins/__init__.py +0 -0
  56. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  57. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/mixins/integrations.py +0 -0
  58. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/mixins/migrations.py +0 -0
  59. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/types.py +0 -0
  60. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/clients/port/utils.py +0 -0
  61. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/config/__init__.py +0 -0
  62. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/config/base.py +0 -0
  63. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/config/dynamic.py +0 -0
  64. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/config/settings.py +0 -0
  65. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/consumers/__init__.py +0 -0
  66. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/consumers/kafka_consumer.py +0 -0
  67. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/context/__init__.py +0 -0
  68. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/context/event.py +0 -0
  69. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/context/ocean.py +0 -0
  70. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/context/resource.py +0 -0
  71. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/__init__.py +0 -0
  72. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/defaults/__init__.py +0 -0
  73. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/defaults/clean.py +0 -0
  74. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/defaults/common.py +0 -0
  75. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/defaults/initialize.py +0 -0
  76. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/__init__.py +0 -0
  77. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/base.py +0 -0
  78. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/factory.py +0 -0
  79. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/http.py +0 -0
  80. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/kafka.py +0 -0
  81. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/once.py +0 -0
  82. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/event_listener/polling.py +0 -0
  83. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/__init__.py +0 -0
  84. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/base.py +0 -0
  85. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  86. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  87. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  88. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  89. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  90. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  91. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  92. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  93. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  94. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  95. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/__init__.py +0 -0
  96. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/base.py +0 -0
  97. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  98. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/events.py +0 -0
  99. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/handler.py +0 -0
  100. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/sync.py +0 -0
  101. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/integrations/mixins/utils.py +0 -0
  102. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/models.py +0 -0
  103. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/core/utils.py +0 -0
  104. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/__init__.py +0 -0
  105. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/api.py +0 -0
  106. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/base.py +0 -0
  107. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/clients.py +0 -0
  108. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/context.py +0 -0
  109. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/core.py +0 -0
  110. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/port_defaults.py +0 -0
  111. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/exceptions/utils.py +0 -0
  112. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/helpers/__init__.py +0 -0
  113. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/helpers/async_client.py +0 -0
  114. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/log/__init__.py +0 -0
  115. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/log/handlers.py +0 -0
  116. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/log/logger_setup.py +0 -0
  117. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/log/sensetive.py +0 -0
  118. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/middlewares.py +0 -0
  119. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/ocean.py +0 -0
  120. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/py.typed +0 -0
  121. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/run.py +0 -0
  122. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/sonar-project.properties +0 -0
  123. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/__init__.py +0 -0
  124. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/async_http.py +0 -0
  125. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/cache.py +0 -0
  126. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/misc.py +0 -0
  127. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/repeat.py +0 -0
  128. {port_ocean-0.5.10 → port_ocean-0.5.14}/port_ocean/utils/signal.py +0 -0
  129. {port_ocean-0.5.10 → port_ocean-0.5.14}/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.10
3
+ Version: 0.5.14
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
@@ -21,11 +21,12 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Utilities
23
23
  Provides-Extra: cli
24
+ Requires-Dist: aiostream (>=0.5.2,<0.6.0)
24
25
  Requires-Dist: click (>=8.1.3,<9.0.0) ; extra == "cli"
25
26
  Requires-Dist: confluent-kafka (>=2.1.1,<3.0.0)
26
27
  Requires-Dist: cookiecutter (>=2.1.1,<3.0.0) ; extra == "cli"
27
- Requires-Dist: fastapi (>=0.100,<0.110)
28
- Requires-Dist: httpx (>=0.24.1,<0.25.0)
28
+ Requires-Dist: fastapi (>=0.100,<0.111)
29
+ Requires-Dist: httpx (>=0.24.1,<0.28.0)
29
30
  Requires-Dist: jinja2-time (>=0.2.0,<0.3.0) ; extra == "cli"
30
31
  Requires-Dist: loguru (>=0.7.0,<0.8.0)
31
32
  Requires-Dist: pydantic (>=1.10.8,<2.0.0)
@@ -36,8 +37,8 @@ Requires-Dist: pyyaml (>=6.0,<7.0)
36
37
  Requires-Dist: rich (>=13.4.1,<14.0.0) ; extra == "cli"
37
38
  Requires-Dist: six (>=1.16.0,<2.0.0)
38
39
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
39
- Requires-Dist: urllib3 (>=1.26.16,<2.0.0)
40
- Requires-Dist: uvicorn (>=0.22.0,<0.23.0)
40
+ Requires-Dist: urllib3 (>=1.26.16,<3.0.0)
41
+ Requires-Dist: uvicorn (>=0.22,<0.30)
41
42
  Requires-Dist: werkzeug (>=2.3.4,<4.0.0)
42
43
  Project-URL: Repository, https://github.com/port-labs/Port-Ocean
43
44
  Description-Content-Type: text/markdown
@@ -49,7 +49,9 @@ class PortAuthentication:
49
49
 
50
50
  credentials = {"clientId": client_id, "clientSecret": client_secret}
51
51
  response = await self.client.post(
52
- f"{self.api_url}/auth/access_token", json=credentials
52
+ f"{self.api_url}/auth/access_token",
53
+ json=credentials,
54
+ extensions={"retryable": True},
53
55
  )
54
56
  handle_status_code(response)
55
57
  return TokenResponse(**response.json())
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from typing import Any
2
3
  from urllib.parse import quote_plus
3
4
 
4
5
  import httpx
@@ -133,23 +134,10 @@ class EntityClientMixin:
133
134
  return_exceptions=True,
134
135
  )
135
136
 
136
- async def validate_entity_exist(self, identifier: str, blueprint: str) -> None:
137
- logger.info(f"Validating entity {identifier} of blueprint {blueprint} exists")
138
-
139
- response = await self.client.get(
140
- f"{self.auth.api_url}/blueprints/{blueprint}/entities/{identifier}",
141
- headers=await self.auth.headers(),
142
- )
143
- if response.is_error:
144
- logger.error(
145
- f"Error validating "
146
- f"entity: {identifier} of "
147
- f"blueprint: {blueprint}"
148
- )
149
- handle_status_code(response)
150
-
151
- async def search_entities(self, user_agent_type: UserAgentType) -> list[Entity]:
152
- query = {
137
+ async def search_entities(
138
+ self, user_agent_type: UserAgentType, query: dict[Any, Any] | None = None
139
+ ) -> list[Entity]:
140
+ default_query = {
153
141
  "combinator": "and",
154
142
  "rules": [
155
143
  {
@@ -165,6 +153,11 @@ class EntityClientMixin:
165
153
  ],
166
154
  }
167
155
 
156
+ if query is None:
157
+ query = default_query
158
+ elif query.get("rules"):
159
+ query["rules"].append(default_query)
160
+
168
161
  logger.info(f"Searching entities with query {query}")
169
162
  response = await self.client.post(
170
163
  f"{self.auth.api_url}/entities/search",
@@ -174,43 +167,39 @@ class EntityClientMixin:
174
167
  "exclude_calculated_properties": "true",
175
168
  "include": ["blueprint", "identifier"],
176
169
  },
170
+ extensions={"retryable": True},
171
+ timeout=30,
177
172
  )
178
173
  handle_status_code(response)
179
174
  return [Entity.parse_obj(result) for result in response.json()["entities"]]
180
175
 
181
- async def search_dependent_entities(self, entity: Entity) -> list[Entity]:
182
- body = {
183
- "combinator": "and",
184
- "rules": [
176
+ async def search_batch_entities(
177
+ self, user_agent_type: UserAgentType, entities_to_search: list[Entity]
178
+ ) -> list[Entity]:
179
+ search_rules = []
180
+ for entity in entities_to_search:
181
+ search_rules.append(
185
182
  {
186
- "operator": "relatedTo",
187
- "blueprint": entity.blueprint,
188
- "value": entity.identifier,
189
- "direction": "downstream",
183
+ "combinator": "and",
184
+ "rules": [
185
+ {
186
+ "property": "$identifier",
187
+ "operator": "=",
188
+ "value": entity.identifier,
189
+ },
190
+ {
191
+ "property": "$blueprint",
192
+ "operator": "=",
193
+ "value": entity.blueprint,
194
+ },
195
+ ],
190
196
  }
191
- ],
192
- }
193
-
194
- logger.info(f"Searching dependent entity with body {body}")
195
- response = await self.client.post(
196
- f"{self.auth.api_url}/entities/search",
197
- headers=await self.auth.headers(),
198
- json=body,
199
- )
200
- handle_status_code(response)
201
-
202
- return [Entity.parse_obj(result) for result in response.json()["entities"]]
197
+ )
203
198
 
204
- async def validate_entity_payload(
205
- self, entity: Entity, merge: bool, create_missing_related_entities: bool
206
- ) -> None:
207
- logger.info(f"Validating entity {entity.identifier}")
208
- await self.upsert_entity(
209
- entity,
199
+ return await self.search_entities(
200
+ user_agent_type,
210
201
  {
211
- "merge": merge,
212
- "create_missing_related_entities": create_missing_related_entities,
213
- "delete_dependent_entities": False,
214
- "validation_only": True,
202
+ "combinator": "and",
203
+ "rules": [{"combinator": "or", "rules": search_rules}],
215
204
  },
216
205
  )
@@ -15,11 +15,6 @@ class TokenRetryTransport(RetryTransport):
15
15
  super().__init__(**kwargs)
16
16
  self.port_client = port_client
17
17
 
18
- def _is_retryable_method(self, request: httpx.Request) -> bool:
19
- return super()._is_retryable_method(request) or request.url.path.endswith(
20
- "/auth/access_token"
21
- )
22
-
23
18
  async def _handle_unauthorized(self, response: httpx.Response) -> None:
24
19
  token = await self.port_client.auth.token
25
20
  response.headers["Authorization"] = f"Bearer {token}"
@@ -1,6 +1,3 @@
1
- import asyncio
2
- from itertools import chain
3
-
4
1
  from loguru import logger
5
2
 
6
3
  from port_ocean.clients.port.types import UserAgentType
@@ -14,14 +11,9 @@ from port_ocean.core.handlers.entities_state_applier.port.get_related_entities i
14
11
  from port_ocean.core.handlers.entities_state_applier.port.order_by_entities_dependencies import (
15
12
  order_by_entities_dependencies,
16
13
  )
17
- from port_ocean.core.handlers.entities_state_applier.port.validate_entity_relations import (
18
- validate_entity_relations,
19
- )
20
- from port_ocean.core.handlers.entity_processor.base import EntityPortDiff
21
14
  from port_ocean.core.models import Entity
22
15
  from port_ocean.core.ocean_types import EntityDiff
23
- from port_ocean.core.utils import is_same_entity, get_unique, get_port_diff
24
- from port_ocean.exceptions.core import RelationValidationException
16
+ from port_ocean.core.utils import is_same_entity, get_port_diff
25
17
 
26
18
 
27
19
  class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
@@ -32,63 +24,17 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
32
24
  through HTTP requests.
33
25
  """
34
26
 
35
- async def _validate_delete_dependent_entities(self, entities: list[Entity]) -> None:
36
- logger.info("Validated deleted entities")
37
- if not event.port_app_config.delete_dependent_entities:
38
- dependent_entities = await asyncio.gather(
39
- *(
40
- self.context.port_client.search_dependent_entities(entity)
41
- for entity in entities
42
- )
43
- )
44
- new_dependent_entities = get_unique(
45
- [
46
- entity
47
- for entity in chain.from_iterable(dependent_entities)
48
- if not any(is_same_entity(item, entity) for item in entities)
49
- ]
50
- )
51
-
52
- if new_dependent_entities:
53
- raise RelationValidationException(
54
- f"Must enable delete_dependent_entities flag or delete all dependent entities: "
55
- f" {[(dep.blueprint, dep.identifier) for dep in new_dependent_entities]}"
56
- )
57
-
58
- async def _validate_entity_diff(self, diff: EntityPortDiff) -> None:
59
- config = event.port_app_config
60
- await self._validate_delete_dependent_entities(diff.deleted)
61
- modified_or_created_entities = diff.modified + diff.created
62
-
63
- if modified_or_created_entities and not config.create_missing_related_entities:
64
- logger.info("Validating modified or created entities")
65
-
66
- await asyncio.gather(
67
- *(
68
- self.context.port_client.validate_entity_payload(
69
- entity,
70
- config.enable_merge_entity,
71
- create_missing_related_entities=config.create_missing_related_entities,
72
- )
73
- for entity in modified_or_created_entities
74
- )
75
- )
76
-
77
- if not event.port_app_config.delete_dependent_entities:
78
- logger.info("Validating no relation blocks the operation")
79
- await validate_entity_relations(diff, self.context.port_client)
80
-
81
- async def _delete_diff(
27
+ async def _safe_delete(
82
28
  self,
83
29
  entities_to_delete: list[Entity],
84
- created_entities: list[Entity],
30
+ entities_to_protect: list[Entity],
85
31
  user_agent_type: UserAgentType,
86
32
  ) -> None:
87
33
  if not entities_to_delete:
88
34
  return
89
35
 
90
36
  related_entities = await get_related_entities(
91
- created_entities, self.context.port_client
37
+ entities_to_protect, self.context.port_client
92
38
  )
93
39
 
94
40
  allowed_entities_to_delete = []
@@ -98,7 +44,8 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
98
44
  is_same_entity(entity, entity_to_delete) for entity in related_entities
99
45
  )
100
46
  is_part_of_created = any(
101
- is_same_entity(entity, entity_to_delete) for entity in created_entities
47
+ is_same_entity(entity, entity_to_delete)
48
+ for entity in entities_to_protect
102
49
  )
103
50
  if is_part_of_related:
104
51
  if event.port_app_config.create_missing_related_entities:
@@ -119,21 +66,14 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
119
66
  user_agent_type: UserAgentType,
120
67
  ) -> None:
121
68
  diff = get_port_diff(entities["before"], entities["after"])
69
+ kept_entities = diff.created + diff.modified
122
70
 
123
71
  logger.info(
124
72
  f"Updating entity diff (created: {len(diff.created)}, deleted: {len(diff.deleted)}, modified: {len(diff.modified)})"
125
73
  )
126
- await self._validate_entity_diff(diff)
74
+ await self.upsert(kept_entities, user_agent_type)
127
75
 
128
- logger.info("Upserting new entities")
129
- await self.upsert(diff.created, user_agent_type)
130
- logger.info("Upserting modified entities")
131
- await self.upsert(diff.modified, user_agent_type)
132
-
133
- logger.info("Deleting diff entities")
134
- await self._delete_diff(
135
- diff.deleted, diff.created + diff.modified, user_agent_type
136
- )
76
+ await self._safe_delete(diff.deleted, kept_entities, user_agent_type)
137
77
 
138
78
  async def delete_diff(
139
79
  self,
@@ -145,15 +85,13 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
145
85
  if not diff.deleted:
146
86
  return
147
87
 
88
+ kept_entities = diff.created + diff.modified
89
+
148
90
  logger.info(
149
- f"Updating entity diff (created: {len(diff.created)}, deleted: {len(diff.deleted)}, modified: {len(diff.modified)})"
91
+ f"Determining entities to delete ({len(diff.deleted)}/{len(kept_entities)})"
150
92
  )
151
- await self._validate_entity_diff(diff)
152
93
 
153
- logger.info("Deleting diff entities")
154
- await self._delete_diff(
155
- diff.deleted, diff.created + diff.modified, user_agent_type
156
- )
94
+ await self._safe_delete(diff.deleted, kept_entities, user_agent_type)
157
95
 
158
96
  async def upsert(
159
97
  self, entities: list[Entity], user_agent_type: UserAgentType
@@ -6,7 +6,10 @@ from loguru import logger
6
6
  from port_ocean.core.handlers.base import BaseHandler
7
7
  from port_ocean.core.handlers.port_app_config.models import ResourceConfig
8
8
  from port_ocean.core.models import Entity
9
- from port_ocean.core.ocean_types import RawEntityDiff, EntityDiff
9
+ from port_ocean.core.ocean_types import (
10
+ RAW_ITEM,
11
+ EntitySelectorDiff,
12
+ )
10
13
 
11
14
 
12
15
  @dataclass
@@ -33,21 +36,28 @@ class BaseEntityProcessor(BaseHandler):
33
36
 
34
37
  @abstractmethod
35
38
  async def _parse_items(
36
- self, mapping: ResourceConfig, raw_data: RawEntityDiff
37
- ) -> EntityDiff:
39
+ self,
40
+ mapping: ResourceConfig,
41
+ raw_data: list[RAW_ITEM],
42
+ parse_all: bool = False,
43
+ ) -> EntitySelectorDiff:
38
44
  pass
39
45
 
40
46
  async def parse_items(
41
- self, mapping: ResourceConfig, raw_data: RawEntityDiff
42
- ) -> EntityDiff:
47
+ self,
48
+ mapping: ResourceConfig,
49
+ raw_data: list[RAW_ITEM],
50
+ parse_all: bool = False,
51
+ ) -> EntitySelectorDiff:
43
52
  """Public method to parse raw entity data and map it to an EntityDiff.
44
53
 
45
54
  Args:
46
55
  mapping (ResourceConfig): The configuration for entity mapping.
47
- raw_data (RawEntityDiff): The raw data to be parsed.
56
+ raw_data (list[RawEntity]): The raw data to be parsed.
57
+ parse_all (bool): Whether to parse all data or just data that passed the selector.
48
58
 
49
59
  Returns:
50
60
  EntityDiff: The parsed entity differences.
51
61
  """
52
62
  with logger.contextualize(kind=mapping.kind):
53
- return await self._parse_items(mapping, raw_data)
63
+ return await self._parse_items(mapping, raw_data, parse_all)
@@ -9,7 +9,10 @@ import pyjq as jq # type: ignore
9
9
  from port_ocean.core.handlers.entity_processor.base import BaseEntityProcessor
10
10
  from port_ocean.core.handlers.port_app_config.models import ResourceConfig
11
11
  from port_ocean.core.models import Entity
12
- from port_ocean.core.ocean_types import RawEntityDiff, EntityDiff
12
+ from port_ocean.core.ocean_types import (
13
+ RAW_ITEM,
14
+ EntitySelectorDiff,
15
+ )
13
16
  from port_ocean.exceptions.core import EntityProcessorException
14
17
 
15
18
 
@@ -68,16 +71,19 @@ class JQEntityProcessor(BaseEntityProcessor):
68
71
 
69
72
  return result
70
73
 
71
- async def _get_entity_if_passed_selector(
74
+ async def _get_mapped_entity(
72
75
  self,
73
76
  data: dict[str, Any],
74
77
  raw_entity_mappings: dict[str, Any],
75
78
  selector_query: str,
76
- ) -> dict[str, Any]:
79
+ parse_all: bool = False,
80
+ ) -> tuple[dict[str, Any], bool]:
77
81
  should_run = await self._search_as_bool(data, selector_query)
78
- if should_run:
79
- return await self._search_as_object(data, raw_entity_mappings)
80
- return {}
82
+ if parse_all or should_run:
83
+ mapped_entity = await self._search_as_object(data, raw_entity_mappings)
84
+ return mapped_entity, should_run
85
+
86
+ return {}, False
81
87
 
82
88
  async def _calculate_entity(
83
89
  self,
@@ -85,16 +91,18 @@ class JQEntityProcessor(BaseEntityProcessor):
85
91
  raw_entity_mappings: dict[str, Any],
86
92
  items_to_parse: str | None,
87
93
  selector_query: str,
88
- ) -> list[dict[str, Any]]:
94
+ parse_all: bool = False,
95
+ ) -> list[tuple[dict[str, Any], bool]]:
89
96
  if items_to_parse:
90
97
  items = await self._search(data, items_to_parse)
91
98
  if isinstance(items, list):
92
99
  return await asyncio.gather(
93
100
  *[
94
- self._get_entity_if_passed_selector(
101
+ self._get_mapped_entity(
95
102
  {"item": item, **data},
96
103
  raw_entity_mappings,
97
104
  selector_query,
105
+ parse_all,
98
106
  )
99
107
  for item in items
100
108
  ]
@@ -105,51 +113,44 @@ class JQEntityProcessor(BaseEntityProcessor):
105
113
  )
106
114
  else:
107
115
  return [
108
- await self._get_entity_if_passed_selector(
109
- data, raw_entity_mappings, selector_query
116
+ await self._get_mapped_entity(
117
+ data, raw_entity_mappings, selector_query, parse_all
110
118
  )
111
119
  ]
112
- return [{}]
120
+ return [({}, False)]
113
121
 
114
- async def _calculate_entities(
115
- self, mapping: ResourceConfig, raw_data: list[dict[str, Any]]
116
- ) -> list[Entity]:
122
+ async def _parse_items(
123
+ self,
124
+ mapping: ResourceConfig,
125
+ raw_results: list[RAW_ITEM],
126
+ parse_all: bool = False,
127
+ ) -> EntitySelectorDiff:
117
128
  raw_entity_mappings: dict[str, Any] = mapping.port.entity.mappings.dict(
118
129
  exclude_unset=True
119
130
  )
120
- entities_tasks = [
131
+ calculate_entities_tasks = [
121
132
  asyncio.create_task(
122
133
  self._calculate_entity(
123
134
  data,
124
135
  raw_entity_mappings,
125
136
  mapping.port.items_to_parse,
126
137
  mapping.selector.query,
138
+ parse_all,
127
139
  )
128
140
  )
129
- for data in raw_data
130
- ]
131
- entities = await asyncio.gather(*entities_tasks)
132
-
133
- return [
134
- Entity.parse_obj(entity_data)
135
- for flatten in entities
136
- for entity_data in filter(
137
- lambda entity: entity.get("identifier") and entity.get("blueprint"),
138
- flatten,
139
- )
141
+ for data in raw_results
140
142
  ]
141
-
142
- async def _parse_items(
143
- self, mapping: ResourceConfig, raw_results: RawEntityDiff
144
- ) -> EntityDiff:
145
- entities_before: list[Entity] = await self._calculate_entities(
146
- mapping, raw_results["before"]
147
- )
148
- entities_after: list[Entity] = await self._calculate_entities(
149
- mapping, raw_results["after"]
150
- )
151
-
152
- return {
153
- "before": entities_before,
154
- "after": entities_after,
155
- }
143
+ calculate_entities_results = await asyncio.gather(*calculate_entities_tasks)
144
+
145
+ passed_entities = []
146
+ failed_entities = []
147
+ for entities_results in calculate_entities_results:
148
+ for entity, did_entity_pass_selector in entities_results:
149
+ if entity.get("identifier") and entity.get("blueprint"):
150
+ parsed_entity = Entity.parse_obj(entity)
151
+ if did_entity_pass_selector:
152
+ passed_entities.append(parsed_entity)
153
+ else:
154
+ failed_entities.append(parsed_entity)
155
+
156
+ return EntitySelectorDiff(passed=passed_entities, failed=failed_entities)