validio-sdk 0.28.0__tar.gz → 5.2.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-0.28.0 → validio_sdk-5.2.0}/PKG-INFO +1 -1
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/pyproject.toml +1 -1
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/_api/api.py +43 -33
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diff.py +20 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_server_resources.py +45 -1
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/channels.py +22 -4
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/credentials.py +12 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/sources.py +80 -316
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__diff.py +10 -10
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__plan.py +21 -10
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__resource.py +7 -2
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test_import.py +82 -1
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/thresholds.py +16 -1
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/validators.py +2 -9
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/windows.py +0 -38
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/LICENSE +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/README_PUBLIC.md +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/_api/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/client/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/client/client.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/_import.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/_progress.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/apply.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/plan.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/scaffold.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/settings.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/config.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/dbt.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/exception.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/metadata.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/py.typed +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diff_util.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diffable.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_errors.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_field_selector.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_resource.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_resource_graph.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_serde.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_update_namespace.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_util.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/filters.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/notification_rules.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/replacement.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/segmentations.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tags.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/__init__.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/assets/example_manifest.json +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/assets/expected_trimmed_manifest.json +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__channel.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__dbt.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__field_selector.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__sql_validation.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/scalars.py +0 -0
- {validio_sdk-0.28.0 → validio_sdk-5.2.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 = "5.2.0"
|
|
7
7
|
description = "SDK to interact with the Validio platform"
|
|
8
8
|
authors = ["Validio <support@validio.io>"]
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -367,20 +367,6 @@ async def get_sources(
|
|
|
367
367
|
key
|
|
368
368
|
value
|
|
369
369
|
}
|
|
370
|
-
... on GcpStorageSource {
|
|
371
|
-
config {
|
|
372
|
-
project
|
|
373
|
-
bucket
|
|
374
|
-
folder
|
|
375
|
-
csv {
|
|
376
|
-
nullMarker
|
|
377
|
-
delimiter
|
|
378
|
-
}
|
|
379
|
-
schedule
|
|
380
|
-
filePattern
|
|
381
|
-
fileFormat
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
370
|
... on GcpBigQuerySource {
|
|
385
371
|
config {
|
|
386
372
|
project
|
|
@@ -432,19 +418,6 @@ async def get_sources(
|
|
|
432
418
|
schedule
|
|
433
419
|
}
|
|
434
420
|
}
|
|
435
|
-
... on AwsS3Source {
|
|
436
|
-
config {
|
|
437
|
-
bucket
|
|
438
|
-
prefix
|
|
439
|
-
csv {
|
|
440
|
-
nullMarker
|
|
441
|
-
delimiter
|
|
442
|
-
}
|
|
443
|
-
schedule
|
|
444
|
-
filePattern
|
|
445
|
-
fileFormat
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
421
|
... on AzureSynapseSource {
|
|
449
422
|
config {
|
|
450
423
|
database
|
|
@@ -502,6 +475,12 @@ async def get_sources(
|
|
|
502
475
|
schedule
|
|
503
476
|
}
|
|
504
477
|
}
|
|
478
|
+
... on SqlSource {
|
|
479
|
+
config {
|
|
480
|
+
sqlQuery
|
|
481
|
+
schedule
|
|
482
|
+
}
|
|
483
|
+
}
|
|
505
484
|
... on KafkaSource {
|
|
506
485
|
config {
|
|
507
486
|
topic
|
|
@@ -605,6 +584,42 @@ async def secrets_changed_by_field(
|
|
|
605
584
|
return await execute(session, query, variable_values=variable_values)
|
|
606
585
|
|
|
607
586
|
|
|
587
|
+
async def sql_source_preview(
|
|
588
|
+
session: Session,
|
|
589
|
+
credential_id: str,
|
|
590
|
+
sql_query: str,
|
|
591
|
+
dry_run: bool,
|
|
592
|
+
source_name: str,
|
|
593
|
+
) -> Any:
|
|
594
|
+
query = """
|
|
595
|
+
query Op($input: SqlSourcePreviewInput!) {
|
|
596
|
+
sqlSourcePreview(input: $input) {
|
|
597
|
+
jtdSchema
|
|
598
|
+
queryError
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
"""
|
|
602
|
+
input_variable_values = {
|
|
603
|
+
"input": {
|
|
604
|
+
"credentialId": credential_id,
|
|
605
|
+
"sqlQuery": sql_query,
|
|
606
|
+
"dryRun": dry_run,
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
result = await execute(
|
|
611
|
+
session=session, query=query, variable_values=input_variable_values
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
query_error = result.get("queryError")
|
|
615
|
+
if query_error is not None:
|
|
616
|
+
raise ValidioError(
|
|
617
|
+
f"""Sql query for source '{source_name}' is not valid: {query_error}"""
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
return result
|
|
621
|
+
|
|
622
|
+
|
|
608
623
|
async def infer_schema(
|
|
609
624
|
session: Session,
|
|
610
625
|
class_name: str,
|
|
@@ -1412,12 +1427,6 @@ async def get_windows(
|
|
|
1412
1427
|
source {
|
|
1413
1428
|
resourceName
|
|
1414
1429
|
}
|
|
1415
|
-
... on FileWindow {
|
|
1416
|
-
dataTimeField
|
|
1417
|
-
config {
|
|
1418
|
-
segmentRetentionPeriodDays
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
1430
|
... on GlobalWindow {
|
|
1422
1431
|
config {
|
|
1423
1432
|
segmentRetentionPeriodDays
|
|
@@ -1677,6 +1686,7 @@ async def get_validators(
|
|
|
1677
1686
|
sensitivity
|
|
1678
1687
|
decisionBoundsType
|
|
1679
1688
|
adaptionRate
|
|
1689
|
+
algorithm
|
|
1680
1690
|
}}
|
|
1681
1691
|
... on DifferenceThreshold {{
|
|
1682
1692
|
differenceOperator: operator
|
|
@@ -907,11 +907,20 @@ async def check_for_source_schema_changes(
|
|
|
907
907
|
|
|
908
908
|
manifest_source = r.manifest
|
|
909
909
|
reinfer = schema_reinference.should_reinfer_schema_for_source(name)
|
|
910
|
+
|
|
910
911
|
if manifest_source.jtd_schema is None:
|
|
911
912
|
if reinfer:
|
|
912
913
|
sources_to_infer.append(manifest_source)
|
|
913
914
|
else:
|
|
914
915
|
manifest_source.jtd_schema = r.server.jtd_schema
|
|
916
|
+
elif not reinfer:
|
|
917
|
+
# Unless we specify to update schemas we let the server schema be
|
|
918
|
+
# source of truth. This is to not end up mutating or changing
|
|
919
|
+
# anything schema related when we only wanted to update something
|
|
920
|
+
# unrelated, e.g. a description.
|
|
921
|
+
# Only if the source is specified to update its schema do we use the
|
|
922
|
+
# manifest as source of truth.
|
|
923
|
+
manifest_source.jtd_schema = r.server.jtd_schema
|
|
915
924
|
|
|
916
925
|
# For unchanged sources, check if there is a schema diff now that all schemas
|
|
917
926
|
# have been resolved. If we find a diff, flag the source as updated.
|
|
@@ -938,11 +947,22 @@ async def check_for_source_schema_changes(
|
|
|
938
947
|
sources=sources_to_infer,
|
|
939
948
|
session=session,
|
|
940
949
|
)
|
|
950
|
+
|
|
941
951
|
for name, server_source in server_ctx.sources.items():
|
|
942
952
|
if name not in unchanged_sources:
|
|
943
953
|
continue
|
|
944
954
|
|
|
945
955
|
manifest_source = unchanged_sources[name]
|
|
956
|
+
|
|
957
|
+
# Unless we are actively looking to update the schema, force the server
|
|
958
|
+
# manifest to be whatever is specified in the manifest, if in manual
|
|
959
|
+
# schema mode.
|
|
960
|
+
if (
|
|
961
|
+
manifest_source.jtd_schema is not None
|
|
962
|
+
and not schema_reinference.should_reinfer_schema_for_source(name)
|
|
963
|
+
):
|
|
964
|
+
manifest_source.jtd_schema = server_source.jtd_schema
|
|
965
|
+
|
|
946
966
|
if (
|
|
947
967
|
manifest_source.jtd_schema is not None
|
|
948
968
|
and manifest_source.jtd_schema != server_source.jtd_schema
|
|
@@ -71,7 +71,7 @@ from validio_sdk.resource.credentials import (
|
|
|
71
71
|
from validio_sdk.resource.filters import Filter, SqlFilter
|
|
72
72
|
from validio_sdk.resource.notification_rules import Conditions
|
|
73
73
|
from validio_sdk.resource.segmentations import Segmentation
|
|
74
|
-
from validio_sdk.resource.sources import AzureSynapseSource
|
|
74
|
+
from validio_sdk.resource.sources import AzureSynapseSource, Source, SqlSource
|
|
75
75
|
from validio_sdk.resource.tags import Tag
|
|
76
76
|
from validio_sdk.resource.thresholds import Threshold
|
|
77
77
|
from validio_sdk.resource.validators import Reference, SqlValidator, Validator
|
|
@@ -468,6 +468,7 @@ async def load_channels(
|
|
|
468
468
|
slack_channel_id=cfg["slackChannelId"],
|
|
469
469
|
token=unset_or_none,
|
|
470
470
|
signing_secret=unset_or_none,
|
|
471
|
+
app_token=unset_or_none,
|
|
471
472
|
interactive_message_enabled=interactive_message_enabled,
|
|
472
473
|
webhook_url=cfg["webhookUrl"],
|
|
473
474
|
display_name=display_name,
|
|
@@ -835,6 +836,7 @@ async def apply_updates_on_server(
|
|
|
835
836
|
show_secrets=show_secrets,
|
|
836
837
|
session=session,
|
|
837
838
|
progress_bar=progress_bar,
|
|
839
|
+
dry_run_sql=dry_run_sql,
|
|
838
840
|
)
|
|
839
841
|
|
|
840
842
|
# Now we should have all source schemas available. We can expand
|
|
@@ -1071,6 +1073,16 @@ async def _maybe_validate_queries(
|
|
|
1071
1073
|
[v for v in resources_list if isinstance(v, SqlFilter)],
|
|
1072
1074
|
session,
|
|
1073
1075
|
)
|
|
1076
|
+
if (
|
|
1077
|
+
len(resources_list) > 0
|
|
1078
|
+
and isinstance(resources_list[0], Source)
|
|
1079
|
+
and dry_run_sql
|
|
1080
|
+
):
|
|
1081
|
+
await validate_source_sql_queries(
|
|
1082
|
+
manifest_ctx,
|
|
1083
|
+
[v for v in resources_list if isinstance(v, SqlSource)],
|
|
1084
|
+
session,
|
|
1085
|
+
)
|
|
1074
1086
|
|
|
1075
1087
|
|
|
1076
1088
|
async def apply_updates(
|
|
@@ -1229,6 +1241,38 @@ async def validate_filter_sql_query(
|
|
|
1229
1241
|
)
|
|
1230
1242
|
|
|
1231
1243
|
|
|
1244
|
+
async def validate_source_sql_queries(
|
|
1245
|
+
manifest_ctx: DiffContext,
|
|
1246
|
+
sources: list[SqlSource],
|
|
1247
|
+
session: Session,
|
|
1248
|
+
) -> None:
|
|
1249
|
+
for s in sources:
|
|
1250
|
+
await validate_source_sql_query(manifest_ctx, s, session)
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
async def validate_source_sql_query(
|
|
1254
|
+
manifest_ctx: DiffContext,
|
|
1255
|
+
source: SqlSource,
|
|
1256
|
+
session: Session,
|
|
1257
|
+
) -> None:
|
|
1258
|
+
credential = manifest_ctx.credentials.get(source.credential_name)
|
|
1259
|
+
if credential is None:
|
|
1260
|
+
return
|
|
1261
|
+
|
|
1262
|
+
if isinstance(credential, AzureSynapseSqlCredential):
|
|
1263
|
+
# Skipping Azure Synapse credentials since dry run is not supported for query
|
|
1264
|
+
# validation
|
|
1265
|
+
return
|
|
1266
|
+
|
|
1267
|
+
await api.sql_source_preview(
|
|
1268
|
+
session=session,
|
|
1269
|
+
credential_id=credential._must_id(),
|
|
1270
|
+
sql_query=source.sql_query,
|
|
1271
|
+
dry_run=True,
|
|
1272
|
+
source_name=source.name,
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
|
|
1232
1276
|
async def test_credential(
|
|
1233
1277
|
namespace: str,
|
|
1234
1278
|
manifest_ctx: DiffContext,
|
|
@@ -191,6 +191,7 @@ class SlackChannel(Channel):
|
|
|
191
191
|
slack_channel_id: str | None = None,
|
|
192
192
|
token: str | None = None,
|
|
193
193
|
signing_secret: str | None = None,
|
|
194
|
+
app_token: str | None = None,
|
|
194
195
|
interactive_message_enabled: bool | None = None,
|
|
195
196
|
webhook_url: str | None = None,
|
|
196
197
|
display_name: str | None = None,
|
|
@@ -204,7 +205,8 @@ class SlackChannel(Channel):
|
|
|
204
205
|
instance, used to send notifications.
|
|
205
206
|
:param slack_channel_id: Slack channel ID to send to.
|
|
206
207
|
:param token: Slack API token.
|
|
207
|
-
:param signing_secret: Slack API signing secret.
|
|
208
|
+
:param signing_secret: Slack API signing secret. Deprecated.
|
|
209
|
+
:param app_token: App level token used to retrieve user events from Slack.
|
|
208
210
|
:param interactive_message_enabled: If interactive notification messages should
|
|
209
211
|
be used.
|
|
210
212
|
:param webhook_url: Webhook URL provided by Slack to the
|
|
@@ -224,14 +226,22 @@ class SlackChannel(Channel):
|
|
|
224
226
|
self.slack_channel_id = slack_channel_id
|
|
225
227
|
self.token = token
|
|
226
228
|
self.signing_secret = signing_secret
|
|
229
|
+
self.app_token = app_token
|
|
227
230
|
self.interactive_message_enabled = interactive_message_enabled
|
|
228
231
|
|
|
232
|
+
if self.signing_secret:
|
|
233
|
+
self.add_field_deprecation("signing_secret", "app_token")
|
|
234
|
+
|
|
229
235
|
if webhook_url and (
|
|
230
|
-
slack_channel_id
|
|
236
|
+
slack_channel_id
|
|
237
|
+
or token
|
|
238
|
+
or signing_secret
|
|
239
|
+
or interactive_message_enabled
|
|
240
|
+
or app_token
|
|
231
241
|
):
|
|
232
242
|
raise ManifestConfigurationError(
|
|
233
243
|
f"invalid configuration for Slack channel {self.name}: either"
|
|
234
|
-
" webhook_url or slack_channel_id, token,
|
|
244
|
+
" webhook_url or slack_channel_id, token, app_token and"
|
|
235
245
|
" interactive_message_enabled can be used."
|
|
236
246
|
)
|
|
237
247
|
|
|
@@ -241,7 +251,15 @@ class SlackChannel(Channel):
|
|
|
241
251
|
def _secret_fields(self) -> set[str]:
|
|
242
252
|
# If the webhook URL is set, we return an empty set so that the caller can
|
|
243
253
|
# avoid calling the secrets changed endpoint as it's not supported
|
|
244
|
-
return
|
|
254
|
+
return (
|
|
255
|
+
set({})
|
|
256
|
+
if self.webhook_url
|
|
257
|
+
else {
|
|
258
|
+
"app_token",
|
|
259
|
+
"signing_secret",
|
|
260
|
+
"token",
|
|
261
|
+
}
|
|
262
|
+
)
|
|
245
263
|
|
|
246
264
|
def _immutable_fields(self) -> set[str]:
|
|
247
265
|
return set({})
|
|
@@ -1641,3 +1641,15 @@ class SigmaCredential(Credential):
|
|
|
1641
1641
|
|
|
1642
1642
|
def _secret_fields(self) -> set[str]:
|
|
1643
1643
|
return {"client_secret"}
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
WarehouseCredential = Union[
|
|
1647
|
+
AzureSynapseCredential,
|
|
1648
|
+
AwsAthenaCredential,
|
|
1649
|
+
AwsRedshiftCredential,
|
|
1650
|
+
ClickHouseCredential,
|
|
1651
|
+
DatabricksCredential,
|
|
1652
|
+
GcpCredential,
|
|
1653
|
+
PostgreSqlCredential,
|
|
1654
|
+
SnowflakeCredential,
|
|
1655
|
+
]
|