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.
Files changed (57) hide show
  1. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/PKG-INFO +1 -1
  2. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/pyproject.toml +1 -1
  3. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/_api/api.py +43 -33
  4. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diff.py +20 -0
  5. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_server_resources.py +45 -1
  6. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/channels.py +22 -4
  7. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/credentials.py +12 -0
  8. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/sources.py +80 -316
  9. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__diff.py +10 -10
  10. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__plan.py +21 -10
  11. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__resource.py +7 -2
  12. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test_import.py +82 -1
  13. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/thresholds.py +16 -1
  14. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/validators.py +2 -9
  15. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/windows.py +0 -38
  16. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/LICENSE +0 -0
  17. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/README_PUBLIC.md +0 -0
  18. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/__init__.py +0 -0
  19. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/_api/__init__.py +0 -0
  20. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/client/__init__.py +0 -0
  21. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/client/client.py +0 -0
  22. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/__init__.py +0 -0
  23. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/_import.py +0 -0
  24. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/_progress.py +0 -0
  25. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/apply.py +0 -0
  26. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/plan.py +0 -0
  27. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/scaffold.py +0 -0
  28. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/code/settings.py +0 -0
  29. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/config.py +0 -0
  30. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/dbt.py +0 -0
  31. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/exception.py +0 -0
  32. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/metadata.py +0 -0
  33. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/py.typed +0 -0
  34. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/__init__.py +0 -0
  35. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diff_util.py +0 -0
  36. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_diffable.py +0 -0
  37. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_errors.py +0 -0
  38. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_field_selector.py +0 -0
  39. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_resource.py +0 -0
  40. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_resource_graph.py +0 -0
  41. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_serde.py +0 -0
  42. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_update_namespace.py +0 -0
  43. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/_util.py +0 -0
  44. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/filters.py +0 -0
  45. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/notification_rules.py +0 -0
  46. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/replacement.py +0 -0
  47. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/segmentations.py +0 -0
  48. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tags.py +0 -0
  49. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/__init__.py +0 -0
  50. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/assets/example_manifest.json +0 -0
  51. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/assets/expected_trimmed_manifest.json +0 -0
  52. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__channel.py +0 -0
  53. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__dbt.py +0 -0
  54. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__field_selector.py +0 -0
  55. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/resource/tests/test__sql_validation.py +0 -0
  56. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/scalars.py +0 -0
  57. {validio_sdk-0.28.0 → validio_sdk-5.2.0}/validio_sdk/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: validio-sdk
3
- Version: 0.28.0
3
+ Version: 5.2.0
4
4
  Summary: SDK to interact with the Validio platform
5
5
  Home-page: https://validio.io/
6
6
  License: Apache-2.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 = "0.28.0"
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 or token or signing_secret or interactive_message_enabled
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, signing_secret and"
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 set({}) if self.webhook_url else {"signing_secret", "token"}
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
+ ]