validio-sdk 5.3.0__tar.gz → 6.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/PKG-INFO +1 -1
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/pyproject.toml +1 -1
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/_api/api.py +78 -8
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/_import.py +2 -1
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/plan.py +1 -5
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_diff.py +46 -37
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_resource.py +2 -1
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_server_resources.py +12 -8
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/channels.py +17 -94
- validio_sdk-6.0.0/validio_sdk/resource/enums.py +12 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/filters.py +26 -103
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/sources.py +115 -3
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test__diff.py +3 -2
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test__plan.py +0 -54
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test__resource.py +19 -9
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test_import.py +17 -4
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/thresholds.py +37 -10
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/validators.py +139 -129
- validio_sdk-5.3.0/validio_sdk/resource/tests/test__channel.py +0 -137
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/LICENSE +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/README_PUBLIC.md +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/_api/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/client/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/client/client.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/_progress.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/apply.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/scaffold.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/code/settings.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/config.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/dbt.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/exception.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/metadata.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/py.typed +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_diff_util.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_diffable.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_errors.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_resource_graph.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_serde.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_update_namespace.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/_util.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/credentials.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/notification_rules.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/replacement.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/segmentations.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tags.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/__init__.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/assets/example_manifest.json +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/assets/expected_trimmed_manifest.json +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test__dbt.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/tests/test__sql_validation.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/resource/windows.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/scalars.py +0 -0
- {validio_sdk-5.3.0 → validio_sdk-6.0.0}/validio_sdk/util.py +0 -0
|
@@ -3,7 +3,7 @@ name = "validio-sdk"
|
|
|
3
3
|
# This version does not represent the released version or any tag. For each
|
|
4
4
|
# release we automatically bump this before building and publishing so this
|
|
5
5
|
# should be kept at 0.0.1dev1
|
|
6
|
-
version = "
|
|
6
|
+
version = "6.0.0"
|
|
7
7
|
description = "SDK to interact with the Validio platform"
|
|
8
8
|
authors = ["Validio <support@validio.io>"]
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, Callable
|
|
5
5
|
|
|
6
6
|
from aiohttp.client_exceptions import ClientConnectorError
|
|
7
7
|
from gql import gql
|
|
8
|
+
from gql.transport.aiohttp import AIOHTTPTransport
|
|
8
9
|
from gql.transport.exceptions import TransportQueryError, TransportServerError
|
|
9
10
|
from graphql import DocumentNode, FieldNode, utilities
|
|
10
11
|
|
|
@@ -22,6 +23,7 @@ from validio_sdk.exception import (
|
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
NUM_CONCURRENT_CONNECTIONS = 5
|
|
26
|
+
DEFAULT_TIMEOUT_SECONDS = 600 # 10 min
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class APIClient:
|
|
@@ -31,9 +33,38 @@ class APIClient:
|
|
|
31
33
|
# We use a somewhat higher timeout that the default 10 seconds to
|
|
32
34
|
# account for longer running operations.
|
|
33
35
|
self.client.client.execute_timeout = int(
|
|
34
|
-
os.environ.get("VALIDIO_TIMEOUT_SECONDS",
|
|
36
|
+
os.environ.get("VALIDIO_TIMEOUT_SECONDS", DEFAULT_TIMEOUT_SECONDS)
|
|
35
37
|
)
|
|
36
38
|
|
|
39
|
+
def set_timeout(self, timeout: int | None) -> Callable:
|
|
40
|
+
original_execute_timeout = self.client.client.execute_timeout
|
|
41
|
+
original_transport_timeout = None
|
|
42
|
+
|
|
43
|
+
if isinstance(self.client.client.transport, AIOHTTPTransport):
|
|
44
|
+
original_transport_timeout = self.client.client.transport.timeout
|
|
45
|
+
|
|
46
|
+
# When creating a session we're connecting to the destination so we have
|
|
47
|
+
# to do this on the client level and can't defer it to the session.
|
|
48
|
+
#
|
|
49
|
+
# This timeout only affects request timeout and not connect timeout when
|
|
50
|
+
# trying to connect to the destination.
|
|
51
|
+
self.client.client.execute_timeout = timeout
|
|
52
|
+
|
|
53
|
+
# This timeout affects connection timeouts and when we should give up
|
|
54
|
+
# when trying to connect to the destination.
|
|
55
|
+
if isinstance(self.client.client.transport, AIOHTTPTransport):
|
|
56
|
+
self.client.client.transport.timeout = timeout
|
|
57
|
+
|
|
58
|
+
# To not have to return and handle multiple values we just return a
|
|
59
|
+
# function that can be used to restore them.
|
|
60
|
+
def reset_values() -> None:
|
|
61
|
+
self.client.client.execute_timeout = original_execute_timeout
|
|
62
|
+
|
|
63
|
+
if isinstance(self.client.client.transport, AIOHTTPTransport):
|
|
64
|
+
self.client.client.transport.timeout = original_transport_timeout
|
|
65
|
+
|
|
66
|
+
return reset_values
|
|
67
|
+
|
|
37
68
|
async def execute(self, query: str, **kwargs: Any) -> Any:
|
|
38
69
|
async with self.client as session:
|
|
39
70
|
return await execute(session, query, **kwargs)
|
|
@@ -68,8 +99,12 @@ class APIClient:
|
|
|
68
99
|
return await infer_schema(session, class_name, variable_values, no_args)
|
|
69
100
|
|
|
70
101
|
async def test_connection(self) -> None:
|
|
102
|
+
reset_timeout_values = self.set_timeout(30)
|
|
103
|
+
|
|
71
104
|
async with self.client as session:
|
|
72
|
-
|
|
105
|
+
await test_connection(session)
|
|
106
|
+
|
|
107
|
+
reset_timeout_values()
|
|
73
108
|
|
|
74
109
|
async def test_credential(self, variable_values: dict[str, Any]) -> None:
|
|
75
110
|
async with self.client as session:
|
|
@@ -289,6 +324,13 @@ class APIClient:
|
|
|
289
324
|
async with self.client as session:
|
|
290
325
|
return await get_users(session, user_id)
|
|
291
326
|
|
|
327
|
+
async def users_by_emails(
|
|
328
|
+
self,
|
|
329
|
+
emails: list[str],
|
|
330
|
+
) -> dict[str, str]:
|
|
331
|
+
async with self.client as session:
|
|
332
|
+
return await users_by_emails(session, emails)
|
|
333
|
+
|
|
292
334
|
async def get_tags(
|
|
293
335
|
self,
|
|
294
336
|
) -> list[dict[str, Any]] | dict[str, Any]:
|
|
@@ -367,6 +409,10 @@ async def get_sources(
|
|
|
367
409
|
key
|
|
368
410
|
value
|
|
369
411
|
}
|
|
412
|
+
priority
|
|
413
|
+
owner {
|
|
414
|
+
email
|
|
415
|
+
}
|
|
370
416
|
... on GcpBigQuerySource {
|
|
371
417
|
config {
|
|
372
418
|
project
|
|
@@ -1194,7 +1240,6 @@ async def get_channels(
|
|
|
1194
1240
|
}
|
|
1195
1241
|
... on SlackChannel {
|
|
1196
1242
|
config {
|
|
1197
|
-
slackWebhookUrl: webhookUrl
|
|
1198
1243
|
applicationLinkUrl
|
|
1199
1244
|
slackChannelId
|
|
1200
1245
|
interactiveMessageEnabled
|
|
@@ -1207,7 +1252,6 @@ async def get_channels(
|
|
|
1207
1252
|
}
|
|
1208
1253
|
... on MsTeamsChannel {
|
|
1209
1254
|
config {
|
|
1210
|
-
msTeamsWebhookUrl: webhookUrl
|
|
1211
1255
|
applicationLinkUrl
|
|
1212
1256
|
msTeamsChannelId
|
|
1213
1257
|
msTeamsInteractiveMessageEnabled: interactiveMessageEnabled
|
|
@@ -1276,8 +1320,6 @@ async def get_channels(
|
|
|
1276
1320
|
_replace_aliases(
|
|
1277
1321
|
ftr["config"],
|
|
1278
1322
|
[
|
|
1279
|
-
("slackWebhookUrl", "webhookUrl"),
|
|
1280
|
-
("msTeamsWebhookUrl", "webhookUrl"),
|
|
1281
1323
|
("msTeamsInteractiveMessageEnabled", "interactiveMessageEnabled"),
|
|
1282
1324
|
("emailInteractiveMessageEnabled", "interactiveMessageEnabled"),
|
|
1283
1325
|
],
|
|
@@ -1739,7 +1781,6 @@ async def get_validators(
|
|
|
1739
1781
|
sensitivity
|
|
1740
1782
|
decisionBoundsType
|
|
1741
1783
|
adaptionRate
|
|
1742
|
-
algorithm
|
|
1743
1784
|
}}
|
|
1744
1785
|
... on DifferenceThreshold {{
|
|
1745
1786
|
differenceOperator: operator
|
|
@@ -1760,6 +1801,9 @@ async def get_validators(
|
|
|
1760
1801
|
namespace {{
|
|
1761
1802
|
id
|
|
1762
1803
|
}}
|
|
1804
|
+
owner {{
|
|
1805
|
+
email
|
|
1806
|
+
}}
|
|
1763
1807
|
sourceConfig {{
|
|
1764
1808
|
filter
|
|
1765
1809
|
segmentation {{
|
|
@@ -1784,6 +1828,7 @@ async def get_validators(
|
|
|
1784
1828
|
key
|
|
1785
1829
|
value
|
|
1786
1830
|
}}
|
|
1831
|
+
priority
|
|
1787
1832
|
... on NumericValidator {{
|
|
1788
1833
|
config {{
|
|
1789
1834
|
sourceField
|
|
@@ -2422,6 +2467,31 @@ async def get_users(
|
|
|
2422
2467
|
return await execute(session, query, variable_values=variable_values)
|
|
2423
2468
|
|
|
2424
2469
|
|
|
2470
|
+
async def users_by_emails(
|
|
2471
|
+
session: Session,
|
|
2472
|
+
emails: list[str],
|
|
2473
|
+
) -> dict[str, str]:
|
|
2474
|
+
if not emails:
|
|
2475
|
+
return {}
|
|
2476
|
+
|
|
2477
|
+
query = """
|
|
2478
|
+
query UserByEmails($emails: [String!]!) {
|
|
2479
|
+
usersByEmails(emails: $emails) {
|
|
2480
|
+
id
|
|
2481
|
+
email
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
"""
|
|
2485
|
+
|
|
2486
|
+
result = await execute(
|
|
2487
|
+
session,
|
|
2488
|
+
query,
|
|
2489
|
+
variable_values={"emails": emails},
|
|
2490
|
+
)
|
|
2491
|
+
|
|
2492
|
+
return {x["email"]: x["id"] for x in result}
|
|
2493
|
+
|
|
2494
|
+
|
|
2425
2495
|
async def get_tags(
|
|
2426
2496
|
session: Session,
|
|
2427
2497
|
) -> list[dict[str, Any]] | dict[str, Any]:
|
|
@@ -69,6 +69,7 @@ class ImportContext:
|
|
|
69
69
|
imports = (
|
|
70
70
|
[
|
|
71
71
|
"from validio_sdk import *",
|
|
72
|
+
"from validio_sdk.resource.enums import *",
|
|
72
73
|
"from validio_sdk.resource.thresholds import *",
|
|
73
74
|
]
|
|
74
75
|
+ [
|
|
@@ -277,7 +278,7 @@ async def _import(
|
|
|
277
278
|
("tags", resource_tags(r), None),
|
|
278
279
|
]
|
|
279
280
|
|
|
280
|
-
if v.
|
|
281
|
+
if v.filter_name:
|
|
281
282
|
filter_ = import_ctx.get_variable(Filter, v.filter_name)
|
|
282
283
|
inits.append(("filter", filter_, None))
|
|
283
284
|
|
|
@@ -24,7 +24,7 @@ from validio_sdk.resource._resource import (
|
|
|
24
24
|
)
|
|
25
25
|
from validio_sdk.resource._server_resources import load_resources
|
|
26
26
|
from validio_sdk.resource._util import SourceSchemaReinference
|
|
27
|
-
from validio_sdk.resource.channels import
|
|
27
|
+
from validio_sdk.resource.channels import WebhookChannel
|
|
28
28
|
from validio_sdk.resource.tags import Tag
|
|
29
29
|
|
|
30
30
|
|
|
@@ -286,10 +286,6 @@ def _create_resource_diff_object(
|
|
|
286
286
|
if not show_secrets and hasattr(r, "_secret_fields"):
|
|
287
287
|
secret_fields = r._secret_fields()
|
|
288
288
|
|
|
289
|
-
# Workaround for Channel having a special secret field
|
|
290
|
-
if isinstance(r, Channel):
|
|
291
|
-
secret_fields.update(r._deprecated_secret_fields())
|
|
292
|
-
|
|
293
289
|
if secret_fields:
|
|
294
290
|
for field in secret_fields:
|
|
295
291
|
# We want to avoid overriding webhook_url if it is None already
|
|
@@ -12,7 +12,11 @@ from typing import (
|
|
|
12
12
|
from camel_converter import to_snake
|
|
13
13
|
from gql.transport.exceptions import TransportQueryError
|
|
14
14
|
|
|
15
|
-
from validio_sdk._api.api import
|
|
15
|
+
from validio_sdk._api.api import (
|
|
16
|
+
secrets_changed_by_field,
|
|
17
|
+
split_to_n_chunks,
|
|
18
|
+
users_by_emails,
|
|
19
|
+
)
|
|
16
20
|
from validio_sdk.client import Session
|
|
17
21
|
from validio_sdk.exception import ValidioBugError, ValidioError
|
|
18
22
|
from validio_sdk.resource._diffable import (
|
|
@@ -26,7 +30,6 @@ from validio_sdk.resource._errors import (
|
|
|
26
30
|
)
|
|
27
31
|
from validio_sdk.resource._resource import CREATE_ONLY_RESOURCES, DiffContext, Resource
|
|
28
32
|
from validio_sdk.resource._util import SourceSchemaReinference, _sanitize_error
|
|
29
|
-
from validio_sdk.resource.channels import Channel
|
|
30
33
|
from validio_sdk.resource.replacement import (
|
|
31
34
|
CascadeReplacementReason,
|
|
32
35
|
ImmutableFieldReplacementReason,
|
|
@@ -36,7 +39,7 @@ from validio_sdk.resource.replacement import (
|
|
|
36
39
|
from validio_sdk.resource.segmentations import Segmentation
|
|
37
40
|
from validio_sdk.resource.sources import Source
|
|
38
41
|
from validio_sdk.resource.tags import Tag
|
|
39
|
-
from validio_sdk.resource.validators import
|
|
42
|
+
from validio_sdk.resource.validators import Validator
|
|
40
43
|
from validio_sdk.resource.windows import TumblingWindow
|
|
41
44
|
|
|
42
45
|
if TYPE_CHECKING:
|
|
@@ -190,6 +193,8 @@ async def diff_resource_graph(
|
|
|
190
193
|
show_secrets=show_secrets,
|
|
191
194
|
)
|
|
192
195
|
|
|
196
|
+
await create_email_lookup_table(manifest_ctx, session)
|
|
197
|
+
|
|
193
198
|
_compute_replacements(
|
|
194
199
|
manifest_ctx=manifest_ctx,
|
|
195
200
|
server_ctx=server_ctx,
|
|
@@ -254,8 +259,7 @@ def _visit_resource_to_replace(
|
|
|
254
259
|
to_delete: dict[str, Resource] = getattr(graph_diff.to_delete, resource_type)
|
|
255
260
|
|
|
256
261
|
# Remove the resource from the update list.
|
|
257
|
-
|
|
258
|
-
del to_update[resource_name]
|
|
262
|
+
to_update.pop(resource_name, None)
|
|
259
263
|
|
|
260
264
|
# Flag the resource to be created.
|
|
261
265
|
manifest_resources: dict[str, Resource] = getattr(manifest_ctx, resource_type)
|
|
@@ -566,23 +570,6 @@ def diff(
|
|
|
566
570
|
if isinstance(manifest_object, Resource) and manifest_object.ignore_changes:
|
|
567
571
|
return None
|
|
568
572
|
|
|
569
|
-
# NOTE: Backwards compatibility while we transition to Filter resources.
|
|
570
|
-
# If we're about to diff a validator where the manifest validator still uses
|
|
571
|
-
# the JSONFilterExpression mode, then force the server object to work
|
|
572
|
-
# in the JSONFilterExpression mode so that we can compare the same fields.
|
|
573
|
-
# Same for validator reference.
|
|
574
|
-
if (
|
|
575
|
-
isinstance(manifest_object, (Reference, Validator))
|
|
576
|
-
and
|
|
577
|
-
# NOTE: the server resource is always the Validator. Even if
|
|
578
|
-
# the server object is potentially the nested 'Reference'.
|
|
579
|
-
# This is mostly for type-checking.
|
|
580
|
-
isinstance(server_resource, Validator)
|
|
581
|
-
and isinstance(manifest_resource, Validator)
|
|
582
|
-
) and not manifest_object._filter_resource_mode():
|
|
583
|
-
manifest_resource._force_filter_expression_mode()
|
|
584
|
-
server_resource._force_filter_expression_mode()
|
|
585
|
-
|
|
586
573
|
if isinstance(server_resource, Segmentation) and isinstance(
|
|
587
574
|
manifest_resource, Segmentation
|
|
588
575
|
):
|
|
@@ -642,20 +629,6 @@ def diff(
|
|
|
642
629
|
server = getattr(server_object, field)
|
|
643
630
|
|
|
644
631
|
if manifest != server:
|
|
645
|
-
# This is a temporary fix to support marking deprecated secret fields as
|
|
646
|
-
# changed on a `Channel`. If the field is both in `_mutable_fields`
|
|
647
|
-
# and also in `_deprecated_secret_fields` we will flag the field in
|
|
648
|
-
# `secret_fields_changed` if there has been an update. This ensures
|
|
649
|
-
# that the value is redacted by default when displaying the config.
|
|
650
|
-
secret_fields_changed = None
|
|
651
|
-
if (
|
|
652
|
-
isinstance(manifest_object, Channel)
|
|
653
|
-
and field in manifest_object._deprecated_secret_fields()
|
|
654
|
-
):
|
|
655
|
-
secret_fields_changed = {
|
|
656
|
-
field: True,
|
|
657
|
-
}
|
|
658
|
-
|
|
659
632
|
# Whenever we find there's a change to make, grab all fields on both
|
|
660
633
|
# resource versions so that we can present them as a diff.
|
|
661
634
|
# Regardless of at what depth we find a change, the diff we present
|
|
@@ -664,7 +637,6 @@ def diff(
|
|
|
664
637
|
return ResourceUpdate(
|
|
665
638
|
manifest_resource,
|
|
666
639
|
server_resource,
|
|
667
|
-
secret_fields_changed=secret_fields_changed,
|
|
668
640
|
)
|
|
669
641
|
|
|
670
642
|
# Some fields require the resource to be re-created if the type changes
|
|
@@ -773,6 +745,43 @@ async def enrich_resource_graph(
|
|
|
773
745
|
)
|
|
774
746
|
|
|
775
747
|
|
|
748
|
+
async def create_email_lookup_table(
|
|
749
|
+
manifest_ctx: DiffContext,
|
|
750
|
+
session: Session,
|
|
751
|
+
) -> None:
|
|
752
|
+
emails_to_lookup = set()
|
|
753
|
+
|
|
754
|
+
# Only sources and validators can have owners.
|
|
755
|
+
for field in ["sources", "validators"]:
|
|
756
|
+
manifest_resources = getattr(manifest_ctx, field)
|
|
757
|
+
|
|
758
|
+
# Add all owner email addresses from the manifest (create and update) to
|
|
759
|
+
# the lookup set for lookup.
|
|
760
|
+
for resource in manifest_resources.values():
|
|
761
|
+
if not isinstance(resource, (Source, Validator)):
|
|
762
|
+
continue
|
|
763
|
+
|
|
764
|
+
if resource.owner is not None:
|
|
765
|
+
emails_to_lookup.add(resource.owner)
|
|
766
|
+
|
|
767
|
+
if not emails_to_lookup:
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
user_email_ids = await users_by_emails(session, list(emails_to_lookup))
|
|
771
|
+
|
|
772
|
+
if len(emails_to_lookup) != len(user_email_ids):
|
|
773
|
+
missing_emails = [x for x in emails_to_lookup if x not in user_email_ids]
|
|
774
|
+
owner_or_owners = "owner" if len(missing_emails) == 1 else "owners"
|
|
775
|
+
address_or_addresses = "address" if len(missing_emails) == 1 else "addresses"
|
|
776
|
+
|
|
777
|
+
raise ValidioError(
|
|
778
|
+
f"Invalid {owner_or_owners}, unknown email {address_or_addresses}: "
|
|
779
|
+
f"{', '.join(missing_emails)}"
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
manifest_ctx.user_email_ids = user_email_ids
|
|
783
|
+
|
|
784
|
+
|
|
776
785
|
async def check_for_source_schema_changes(
|
|
777
786
|
manifest_ctx: DiffContext,
|
|
778
787
|
server_ctx: DiffContext,
|
|
@@ -68,13 +68,14 @@ class DiffContext:
|
|
|
68
68
|
pending_validators_raw: dict[str, tuple[type, dict[str, Any]]] = dataclasses.field(
|
|
69
69
|
default_factory=dict
|
|
70
70
|
)
|
|
71
|
+
user_email_ids: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
71
72
|
|
|
72
73
|
@staticmethod
|
|
73
74
|
def fields() -> list[str]:
|
|
74
75
|
return [
|
|
75
76
|
f
|
|
76
77
|
for f in list(inspect.signature(DiffContext).parameters)
|
|
77
|
-
if f
|
|
78
|
+
if f not in ["pending_validators_raw", "user_email_ids"]
|
|
78
79
|
]
|
|
79
80
|
|
|
80
81
|
@staticmethod
|
|
@@ -498,7 +498,6 @@ async def load_channels(
|
|
|
498
498
|
|
|
499
499
|
cfg = ch["config"]
|
|
500
500
|
application_link_url = cfg["applicationLinkUrl"]
|
|
501
|
-
unset_or_none = None if cfg.get("webhookUrl") else "UNSET"
|
|
502
501
|
interactive_message_enabled = cfg.get("interactiveMessageEnabled", None)
|
|
503
502
|
display_name = ch["name"]
|
|
504
503
|
# The 'secret' parts of a channel are left unset since they are not
|
|
@@ -509,10 +508,9 @@ async def load_channels(
|
|
|
509
508
|
name=name,
|
|
510
509
|
application_link_url=application_link_url,
|
|
511
510
|
ms_teams_channel_id=cfg["msTeamsChannelId"],
|
|
512
|
-
client_id=
|
|
513
|
-
client_secret=
|
|
511
|
+
client_id="UNSET",
|
|
512
|
+
client_secret="UNSET",
|
|
514
513
|
interactive_message_enabled=interactive_message_enabled,
|
|
515
|
-
webhook_url=cfg["webhookUrl"],
|
|
516
514
|
display_name=display_name,
|
|
517
515
|
__internal__=g,
|
|
518
516
|
)
|
|
@@ -521,11 +519,10 @@ async def load_channels(
|
|
|
521
519
|
name=name,
|
|
522
520
|
application_link_url=application_link_url,
|
|
523
521
|
slack_channel_id=cfg["slackChannelId"],
|
|
524
|
-
token=
|
|
525
|
-
signing_secret=
|
|
526
|
-
app_token=
|
|
522
|
+
token="UNSET",
|
|
523
|
+
signing_secret="UNSET",
|
|
524
|
+
app_token="UNSET",
|
|
527
525
|
interactive_message_enabled=interactive_message_enabled,
|
|
528
|
-
webhook_url=cfg["webhookUrl"],
|
|
529
526
|
display_name=display_name,
|
|
530
527
|
__internal__=g,
|
|
531
528
|
)
|
|
@@ -606,6 +603,7 @@ async def load_sources(
|
|
|
606
603
|
for s in server_sources:
|
|
607
604
|
name = s["resourceName"]
|
|
608
605
|
source_config = s.get("config", {})
|
|
606
|
+
owner = s.get("owner") or {}
|
|
609
607
|
|
|
610
608
|
cls = eval(f"validio_sdk.resource.sources.{s['__typename']}")
|
|
611
609
|
source = cls(
|
|
@@ -613,11 +611,13 @@ async def load_sources(
|
|
|
613
611
|
**{to_snake(k): v for k, v in source_config.items()},
|
|
614
612
|
"name": name,
|
|
615
613
|
"display_name": s["name"],
|
|
614
|
+
"owner": owner.get("email"),
|
|
616
615
|
"credential": must_find_credential(
|
|
617
616
|
ctx, s["credential"]["resourceName"]
|
|
618
617
|
),
|
|
619
618
|
"jtd_schema": s["jtdSchema"],
|
|
620
619
|
"description": s["description"],
|
|
620
|
+
"priority": s["priority"],
|
|
621
621
|
"tags": [
|
|
622
622
|
must_find_tag(ctx, Tag._unique_name(t["key"], t["value"]))
|
|
623
623
|
for t in s["tags"]
|
|
@@ -765,7 +765,9 @@ async def load_validators(
|
|
|
765
765
|
name = v["resourceName"]
|
|
766
766
|
display_name = v["name"]
|
|
767
767
|
description = v["description"]
|
|
768
|
+
priority = v["priority"]
|
|
768
769
|
config = v["config"]
|
|
770
|
+
owner = v.get("owner") or {}
|
|
769
771
|
|
|
770
772
|
window = must_find_window(ctx, v["sourceConfig"]["window"]["resourceName"])
|
|
771
773
|
segmentation = must_find_segmentation(
|
|
@@ -808,7 +810,9 @@ async def load_validators(
|
|
|
808
810
|
"window": window,
|
|
809
811
|
"segmentation": segmentation,
|
|
810
812
|
"display_name": display_name,
|
|
813
|
+
"owner": owner.get("email"),
|
|
811
814
|
"description": description,
|
|
815
|
+
"priority": priority,
|
|
812
816
|
"tags": [
|
|
813
817
|
must_find_tag(ctx, Tag._unique_name(t["key"], t["value"]))
|
|
814
818
|
for t in v["tags"]
|