port-ocean 0.5.9__tar.gz → 0.5.13__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.
- {port_ocean-0.5.9 → port_ocean-0.5.13}/PKG-INFO +4 -3
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/authentication.py +3 -1
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/mixins/entities.py +33 -50
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/retry_transport.py +0 -5
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/config/settings.py +9 -2
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/port/applier.py +13 -75
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entity_processor/base.py +17 -7
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +42 -41
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/sync_raw.py +56 -41
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/ocean_types.py +10 -2
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/helpers/retry.py +49 -4
- port_ocean-0.5.13/port_ocean/utils/async_iterators.py +49 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/pyproject.toml +5 -4
- port_ocean-0.5.9/port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -40
- {port_ocean-0.5.9 → port_ocean-0.5.13}/LICENSE.md +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/README.md +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/bootstrap.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cli.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/defaults/__init___.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/defaults/clean.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/defaults/dock.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/defaults/group.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/list_integrations.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/main.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/new.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/pull.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/sail.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/commands/version.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/extensions.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/cli/utils.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/client.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/mixins/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/mixins/blueprints.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/mixins/integrations.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/mixins/migrations.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/types.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/clients/port/utils.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/config/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/config/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/config/dynamic.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/consumers/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/consumers/kafka_consumer.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/context/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/context/event.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/context/ocean.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/context/resource.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/defaults/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/defaults/clean.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/defaults/common.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/defaults/initialize.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/factory.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/http.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/kafka.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/once.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/event_listener/polling.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/port_app_config/api.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/port_app_config/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/handlers/port_app_config/models.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/events.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/handler.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/sync.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/integrations/mixins/utils.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/models.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/core/utils.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/api.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/base.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/clients.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/context.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/core.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/port_defaults.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/exceptions/utils.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/helpers/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/helpers/async_client.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/log/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/log/handlers.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/log/logger_setup.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/log/sensetive.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/middlewares.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/ocean.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/py.typed +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/run.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/sonar-project.properties +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/__init__.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/async_http.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/cache.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/misc.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/repeat.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/port_ocean/utils/signal.py +0 -0
- {port_ocean-0.5.9 → port_ocean-0.5.13}/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.
|
|
3
|
+
Version: 0.5.13
|
|
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
28
|
Requires-Dist: fastapi (>=0.100,<0.110)
|
|
28
|
-
Requires-Dist: httpx (>=0.24.1,<0.
|
|
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)
|
|
@@ -37,7 +38,7 @@ 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
40
|
Requires-Dist: urllib3 (>=1.26.16,<2.0.0)
|
|
40
|
-
Requires-Dist: uvicorn (>=0.22
|
|
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",
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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,33 @@ 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
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"blueprint": entity.blueprint,
|
|
188
|
-
"value": entity.identifier,
|
|
189
|
-
"direction": "downstream",
|
|
190
|
-
}
|
|
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"]]
|
|
203
|
-
|
|
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,
|
|
176
|
+
async def does_integration_has_ownership_over_entity(
|
|
177
|
+
self, entity: Entity, user_agent_type: UserAgentType
|
|
178
|
+
) -> bool:
|
|
179
|
+
logger.info(f"Validating ownership on entity {entity.identifier}")
|
|
180
|
+
found_entities: list[Entity] = await self.search_entities(
|
|
181
|
+
user_agent_type,
|
|
210
182
|
{
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
|
|
214
|
-
|
|
183
|
+
"combinator": "and",
|
|
184
|
+
"rules": [
|
|
185
|
+
{
|
|
186
|
+
"property": "$identifier",
|
|
187
|
+
"operator": "contains",
|
|
188
|
+
"value": entity.identifier,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"property": "$blueprint",
|
|
192
|
+
"operator": "contains",
|
|
193
|
+
"value": entity.blueprint,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
215
196
|
},
|
|
216
197
|
)
|
|
198
|
+
|
|
199
|
+
return len(found_entities) > 0
|
|
@@ -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,7 @@
|
|
|
1
1
|
from typing import Any, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import Extra, AnyHttpUrl, parse_obj_as, validator
|
|
4
|
+
from pydantic.env_settings import InitSettingsSource, EnvSettingsSource, BaseSettings
|
|
4
5
|
from pydantic.fields import Field
|
|
5
6
|
from pydantic.main import BaseModel
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ from port_ocean.core.event_listener import EventListenerSettingsType
|
|
|
10
11
|
LogLevelType = Literal["ERROR", "WARNING", "INFO", "DEBUG", "CRITICAL"]
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class ApplicationSettings(
|
|
14
|
+
class ApplicationSettings(BaseSettings):
|
|
14
15
|
log_level: LogLevelType = "INFO"
|
|
15
16
|
enable_http_logging: bool = True
|
|
16
17
|
port: int = 8000
|
|
@@ -21,7 +22,13 @@ class ApplicationSettings(BaseOceanModel):
|
|
|
21
22
|
env_file_encoding = "utf-8"
|
|
22
23
|
|
|
23
24
|
@classmethod
|
|
24
|
-
def customise_sources(
|
|
25
|
+
def customise_sources( # type: ignore
|
|
26
|
+
cls,
|
|
27
|
+
init_settings: InitSettingsSource,
|
|
28
|
+
env_settings: EnvSettingsSource,
|
|
29
|
+
*_,
|
|
30
|
+
**__,
|
|
31
|
+
):
|
|
25
32
|
return env_settings, init_settings
|
|
26
33
|
|
|
27
34
|
|
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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.
|
|
74
|
+
await self.upsert(kept_entities, user_agent_type)
|
|
127
75
|
|
|
128
|
-
|
|
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"
|
|
91
|
+
f"Determining entities to delete ({len(diff.deleted)}/{len(kept_entities)})"
|
|
150
92
|
)
|
|
151
|
-
await self._validate_entity_diff(diff)
|
|
152
93
|
|
|
153
|
-
|
|
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
|
|
9
|
+
from port_ocean.core.ocean_types import (
|
|
10
|
+
RawEntity,
|
|
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,
|
|
37
|
-
|
|
39
|
+
self,
|
|
40
|
+
mapping: ResourceConfig,
|
|
41
|
+
raw_data: list[RawEntity],
|
|
42
|
+
parse_all: bool = False,
|
|
43
|
+
) -> EntitySelectorDiff:
|
|
38
44
|
pass
|
|
39
45
|
|
|
40
46
|
async def parse_items(
|
|
41
|
-
self,
|
|
42
|
-
|
|
47
|
+
self,
|
|
48
|
+
mapping: ResourceConfig,
|
|
49
|
+
raw_data: list[RawEntity],
|
|
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 (
|
|
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
|
|
12
|
+
from port_ocean.core.ocean_types import (
|
|
13
|
+
RawEntity,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
115
|
-
self,
|
|
116
|
-
|
|
122
|
+
async def _parse_items(
|
|
123
|
+
self,
|
|
124
|
+
mapping: ResourceConfig,
|
|
125
|
+
raw_results: list[RawEntity],
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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 {"passed": passed_entities, "failed": failed_entities}
|