validio-sdk 7.2.0__tar.gz → 7.4.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 (55) hide show
  1. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/PKG-INFO +1 -1
  2. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/pyproject.toml +1 -1
  3. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/_api/api.py +31 -0
  4. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_diffable.py +2 -2
  5. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_serde.py +2 -2
  6. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_server_resources.py +47 -0
  7. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/channels.py +3 -3
  8. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/credentials.py +237 -12
  9. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/sources.py +72 -0
  10. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test__diff.py +48 -0
  11. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/validators.py +1 -1
  12. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/LICENSE +0 -0
  13. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/README_PUBLIC.md +0 -0
  14. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/__init__.py +0 -0
  15. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/_api/__init__.py +0 -0
  16. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/client/__init__.py +0 -0
  17. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/client/client.py +0 -0
  18. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/__init__.py +0 -0
  19. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/_import.py +0 -0
  20. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/_progress.py +0 -0
  21. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/apply.py +0 -0
  22. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/plan.py +0 -0
  23. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/scaffold.py +0 -0
  24. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/code/settings.py +0 -0
  25. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/config.py +0 -0
  26. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/dbt.py +0 -0
  27. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/exception.py +0 -0
  28. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/metadata.py +0 -0
  29. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/py.typed +0 -0
  30. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/__init__.py +0 -0
  31. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_diff.py +0 -0
  32. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_diff_util.py +0 -0
  33. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_errors.py +0 -0
  34. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_resource.py +0 -0
  35. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_resource_graph.py +0 -0
  36. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_update_namespace.py +0 -0
  37. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/_util.py +0 -0
  38. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/enums.py +0 -0
  39. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/filters.py +0 -0
  40. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/notification_rules.py +0 -0
  41. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/replacement.py +0 -0
  42. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/segmentations.py +0 -0
  43. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tags.py +0 -0
  44. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/__init__.py +0 -0
  45. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/assets/example_manifest.json +0 -0
  46. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/assets/expected_trimmed_manifest.json +0 -0
  47. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test__dbt.py +0 -0
  48. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test__plan.py +0 -0
  49. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test__resource.py +0 -0
  50. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test__sql_validation.py +0 -0
  51. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/tests/test_import.py +0 -0
  52. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/thresholds.py +0 -0
  53. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/resource/windows.py +0 -0
  54. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/scalars.py +0 -0
  55. {validio_sdk-7.2.0 → validio_sdk-7.4.0}/validio_sdk/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: validio-sdk
3
- Version: 7.2.0
3
+ Version: 7.4.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 = "7.2.0"
6
+ version = "7.4.0"
7
7
  description = "SDK to interact with the Validio platform"
8
8
  authors = ["Validio <support@validio.io>"]
9
9
  license = "Apache-2.0"
@@ -553,6 +553,13 @@ async def get_sources(
553
553
  schedule
554
554
  }
555
555
  }
556
+ ... on TeradataSource {
557
+ config {
558
+ database
559
+ table
560
+ schedule
561
+ }
562
+ }
556
563
  }
557
564
  """
558
565
 
@@ -962,6 +969,12 @@ async def get_credentials(
962
969
  baseUrl
963
970
  }
964
971
  }
972
+ ... on AnthropicCredential {
973
+ config {
974
+ model
975
+ optBaseUrl: baseUrl
976
+ }
977
+ }
965
978
  ... on AwsCredential {
966
979
  config {
967
980
  accessKey
@@ -1080,6 +1093,8 @@ async def get_credentials(
1080
1093
  user
1081
1094
  }
1082
1095
  }
1096
+ tlsEnabled
1097
+ tlsCaCertificates
1083
1098
  }
1084
1099
  enableCatalog
1085
1100
  }
@@ -1166,6 +1181,21 @@ async def get_credentials(
1166
1181
  }
1167
1182
  enableCatalog
1168
1183
  }
1184
+ ... on TeradataCredential {
1185
+ config {
1186
+ host
1187
+ sslMode
1188
+ httpsPort
1189
+ tdmstPort
1190
+ auth {
1191
+ __typename
1192
+ ... on TeradataCredentialUserPassword {
1193
+ user
1194
+ }
1195
+ }
1196
+ }
1197
+ enableCatalog
1198
+ }
1169
1199
  }
1170
1200
  """
1171
1201
 
@@ -1214,6 +1244,7 @@ async def get_credentials(
1214
1244
  ("powerBiAuth", "auth"),
1215
1245
  ("databaseRequired", "database"),
1216
1246
  ("optUser", "user"),
1247
+ ("optBaseUrl", "baseUrl"),
1217
1248
  ],
1218
1249
  )
1219
1250
  result[i] = credential
@@ -1,7 +1,7 @@
1
1
  import inspect
2
2
  from abc import ABC, abstractmethod
3
3
  from collections.abc import Iterator
4
- from typing import TYPE_CHECKING, Any, Optional
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from camel_converter import to_camel
7
7
 
@@ -53,7 +53,7 @@ class Diffable(ABC):
53
53
  @abstractmethod
54
54
  def _nested_objects(
55
55
  self,
56
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
56
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
57
57
  """Returns any nested objects contained within this object.
58
58
 
59
59
  Nested objects will be diff-ed recursively.
@@ -389,9 +389,9 @@ def _import_value_repr(
389
389
  # single long line.
390
390
  if "\n" not in value:
391
391
  value_repr = repr(value)
392
- elif "'''" not in value:
392
+ elif not any(["'''" in value, value.startswith("'"), value.endswith("'")]):
393
393
  value_repr = f"'''{value}'''"
394
- elif '"""' not in value:
394
+ elif not any(['"""' in value, value.startswith('"'), value.endswith('"')]):
395
395
  value_repr = f'"""{value}"""'
396
396
  else:
397
397
  value_repr = repr(value)
@@ -38,6 +38,9 @@ from validio_sdk.resource.channels import (
38
38
  WebhookChannel,
39
39
  )
40
40
  from validio_sdk.resource.credentials import (
41
+ AnthropicCredential,
42
+ AnthropicCredentialApiKey,
43
+ AnthropicCredentialAuth,
41
44
  AtlanCredential,
42
45
  AwsAthenaCredential,
43
46
  AwsCredential,
@@ -74,6 +77,9 @@ from validio_sdk.resource.credentials import (
74
77
  SnowflakeCredentialUserPassword,
75
78
  TableauConnectedAppCredential,
76
79
  TableauPersonalAccessTokenCredential,
80
+ TeradataCredential,
81
+ TeradataCredentialAuth,
82
+ TeradataCredentialUserPassword,
77
83
  )
78
84
  from validio_sdk.resource.filters import Filter, SqlFilter
79
85
  from validio_sdk.resource.notification_rules import Conditions
@@ -205,6 +211,19 @@ async def load_credentials(
205
211
  display_name=display_name,
206
212
  __internal__=g,
207
213
  )
214
+ case "AnthropicCredential":
215
+ anthropic_auth: AnthropicCredentialAuth = AnthropicCredentialApiKey(
216
+ api_key="UNSET",
217
+ )
218
+
219
+ credential = AnthropicCredential(
220
+ name=name,
221
+ model=c["config"]["model"],
222
+ base_url=c["config"]["baseUrl"],
223
+ auth=anthropic_auth,
224
+ display_name=display_name,
225
+ __internal__=g,
226
+ )
208
227
  case "AtlanCredential":
209
228
  credential = AtlanCredential(
210
229
  name=name,
@@ -408,6 +427,8 @@ async def load_credentials(
408
427
  port=c["config"]["port"],
409
428
  database=c["config"]["database"],
410
429
  auth=oracle_auth,
430
+ tls_enabled=c["config"]["tlsEnabled"],
431
+ tls_ca_certificates=c["config"]["tlsCaCertificates"],
411
432
  display_name=display_name,
412
433
  enable_catalog=c["enableCatalog"],
413
434
  __internal__=g,
@@ -491,6 +512,32 @@ async def load_credentials(
491
512
  enable_catalog=c["enableCatalog"],
492
513
  __internal__=g,
493
514
  )
515
+ case "TeradataCredential":
516
+ auth_type = c["config"]["auth"]["__typename"]
517
+
518
+ if auth_type == "TeradataCredentialUserPassword":
519
+ teradata_auth: TeradataCredentialAuth = (
520
+ TeradataCredentialUserPassword(
521
+ user=c["config"]["auth"]["user"],
522
+ password="UNSET",
523
+ )
524
+ )
525
+ else:
526
+ raise ValidioBugError(
527
+ f"Unknown Teradata auth type on {name}: '{auth_type}'"
528
+ )
529
+
530
+ credential = TeradataCredential(
531
+ name=name,
532
+ host=c["config"]["host"],
533
+ auth=teradata_auth,
534
+ ssl_mode=c["config"]["sslMode"],
535
+ https_port=c["config"]["httpsPort"],
536
+ tdmst_port=c["config"]["tdmstPort"],
537
+ display_name=display_name,
538
+ enable_catalog=c["enableCatalog"],
539
+ __internal__=g,
540
+ )
494
541
  case _:
495
542
  raise ValidioError(
496
543
  f"unsupported credential '{name}' of type '{type(c)}'"
@@ -1,7 +1,7 @@
1
1
  """Notification Channels."""
2
2
 
3
3
  from enum import Enum
4
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
4
+ from typing import TYPE_CHECKING, Any, Union, cast
5
5
 
6
6
  from camel_converter import to_camel
7
7
 
@@ -387,7 +387,7 @@ class EmailChannelAuthSmtpUserPassword(ApiSecretChangeNestedResource):
387
387
 
388
388
  def _nested_objects(
389
389
  self,
390
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
390
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
391
391
  return {}
392
392
 
393
393
 
@@ -456,5 +456,5 @@ class EmailChannel(Channel):
456
456
 
457
457
  def _nested_objects(
458
458
  self,
459
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
459
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
460
460
  return {"auth": self.auth}
@@ -21,6 +21,7 @@ from validio_sdk.resource._serde import (
21
21
  _api_update_input_params,
22
22
  _encode_resource,
23
23
  _import_resource_params,
24
+ _maybe_cast,
24
25
  decode_nested_objects,
25
26
  get_children_node,
26
27
  get_config_node,
@@ -48,6 +49,15 @@ class ClickHouseProtocol(str, Enum):
48
49
  NATIVE_TLS = "NATIVE_TLS"
49
50
 
50
51
 
52
+ class TeradataSSLMode(str, Enum):
53
+ """SSL encryption mode when connecting to Teradata."""
54
+
55
+ PREFER = "PREFER"
56
+ ALLOW = "ALLOW"
57
+ DISABLE = "DISABLE"
58
+ REQUIRE = "REQUIRE"
59
+
60
+
51
61
  class Credential(Resource):
52
62
  """
53
63
  Base class for a credential resource.
@@ -236,6 +246,100 @@ class Credential(Resource):
236
246
  return test_input
237
247
 
238
248
 
249
+ class AnthropicCredentialApiKey(ApiSecretChangeNestedResource):
250
+ """Anthropic credential using API key for authentication."""
251
+
252
+ def __init__(
253
+ self,
254
+ *,
255
+ api_key: str,
256
+ ):
257
+ """
258
+ Constructor.
259
+
260
+ :param api_key: API key to use for connecting to Anthropic.
261
+ """
262
+ self.api_key = api_key
263
+
264
+ def _api_variant_name(self) -> str:
265
+ return "apiKey"
266
+
267
+ def _immutable_fields(self) -> set[str]:
268
+ return set({})
269
+
270
+ def _mutable_fields(self) -> set[str]:
271
+ return set({})
272
+
273
+ def _secret_fields(self) -> set[str]:
274
+ return {"api_key"}
275
+
276
+ def _nested_objects(
277
+ self,
278
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
279
+ return {}
280
+
281
+
282
+ AnthropicCredentialAuth = Union[AnthropicCredentialApiKey]
283
+
284
+
285
+ class AnthropicCredential(Credential):
286
+ """Credential to connect to Anthropic."""
287
+
288
+ def __init__(
289
+ self,
290
+ *,
291
+ name: str,
292
+ model: str,
293
+ auth: AnthropicCredentialAuth,
294
+ base_url: str | None = None,
295
+ display_name: str | None = None,
296
+ ignore_changes: bool = False,
297
+ __internal__: ResourceGraph | None = None,
298
+ ):
299
+ """
300
+ Constructor.
301
+
302
+ :param name: Unique resource name assigned to the credential
303
+ :param model: Name of the model to use. E.g. claude-sonnet-4-5
304
+ :param auth: Auth method to use for connecting to Anthropic.
305
+ :param base_url: Base URL of Anthropic API. E.g. https://api.anthropic.com/v1.
306
+ If not provided, defaults to the standard Anthropic API endpoint.
307
+ :param display_name: Human-readable name for the credential. This name is
308
+ visible in the UI and does not need to be unique.
309
+ :param ignore_changes: If set to true, changes to the resource will be ignored.
310
+ """
311
+ super().__init__(
312
+ name=name,
313
+ display_name=display_name,
314
+ ignore_changes=ignore_changes,
315
+ __internal__=__internal__,
316
+ )
317
+
318
+ self.model = model
319
+ self.auth = auth
320
+ self.base_url = base_url
321
+
322
+ def _immutable_fields(self) -> set[str]:
323
+ return set({})
324
+
325
+ def _mutable_fields(self) -> set[str]:
326
+ return {
327
+ *super()._mutable_fields(),
328
+ *{
329
+ "model",
330
+ "base_url",
331
+ },
332
+ }
333
+
334
+ def _secret_fields(self) -> set[str]:
335
+ return set()
336
+
337
+ def _nested_objects(
338
+ self,
339
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
340
+ return {"auth": self.auth}
341
+
342
+
239
343
  class AtlanCredential(Credential):
240
344
  """(BETA) An Atlan credential resource."""
241
345
 
@@ -647,7 +751,7 @@ class SnowflakeCredentialKeyPair(ApiSecretChangeNestedResource):
647
751
 
648
752
  def _nested_objects(
649
753
  self,
650
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
754
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
651
755
  return {}
652
756
 
653
757
 
@@ -683,7 +787,7 @@ class SnowflakeCredentialUserPassword(ApiSecretChangeNestedResource):
683
787
 
684
788
  def _nested_objects(
685
789
  self,
686
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
790
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
687
791
  return {}
688
792
 
689
793
 
@@ -748,7 +852,7 @@ class SnowflakeCredential(Credential):
748
852
 
749
853
  def _nested_objects(
750
854
  self,
751
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
855
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
752
856
  return {"auth": self.auth}
753
857
 
754
858
 
@@ -1337,7 +1441,7 @@ class MsSqlServerCredentialUserPassword(ApiSecretChangeNestedResource):
1337
1441
 
1338
1442
  def _nested_objects(
1339
1443
  self,
1340
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1444
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1341
1445
  return {}
1342
1446
 
1343
1447
 
@@ -1372,7 +1476,7 @@ class MsSqlServerCredentialEntraId(ApiSecretChangeNestedResource):
1372
1476
 
1373
1477
  def _nested_objects(
1374
1478
  self,
1375
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1479
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1376
1480
  return {}
1377
1481
 
1378
1482
 
@@ -1442,7 +1546,7 @@ class MsSqlServerCredential(Credential):
1442
1546
 
1443
1547
  def _nested_objects(
1444
1548
  self,
1445
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1549
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1446
1550
  return {"auth": self.auth}
1447
1551
 
1448
1552
 
@@ -1475,7 +1579,7 @@ class OmniCredentialApiKey(ApiSecretChangeNestedResource):
1475
1579
 
1476
1580
  def _nested_objects(
1477
1581
  self,
1478
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1582
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1479
1583
  return {}
1480
1584
 
1481
1585
 
@@ -1536,7 +1640,7 @@ class OmniCredential(Credential):
1536
1640
 
1537
1641
  def _nested_objects(
1538
1642
  self,
1539
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1643
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1540
1644
  return {"auth": self.auth}
1541
1645
 
1542
1646
 
@@ -1571,7 +1675,7 @@ class OracleCredentialUserPassword(ApiSecretChangeNestedResource):
1571
1675
 
1572
1676
  def _nested_objects(
1573
1677
  self,
1574
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1678
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1575
1679
  return {}
1576
1680
 
1577
1681
 
@@ -1588,6 +1692,8 @@ class OracleCredential(Credential):
1588
1692
  port: int,
1589
1693
  database: str,
1590
1694
  auth: OracleCredentialAuth,
1695
+ tls_enabled: bool | None = None,
1696
+ tls_ca_certificates: str | None = None,
1591
1697
  enable_catalog: bool = False,
1592
1698
  display_name: str | None = None,
1593
1699
  ignore_changes: bool = False,
@@ -1601,6 +1707,10 @@ class OracleCredential(Credential):
1601
1707
  :param port: Port number of the server.
1602
1708
  :param auth: Credentials to use for authentication.
1603
1709
  :param database: Name of the database to connect to
1710
+ :param tls_enabled: If set to true, connections will be
1711
+ established using the TLS protocol.
1712
+ :param tls_ca_certificates: PEM certificate bundle used to
1713
+ verify the server TLS certificate.
1604
1714
  :param enable_catalog: If set to true, this credential will
1605
1715
  be used to fetch catalog information.
1606
1716
  :param display_name: Human-readable name for the credential. This name is
@@ -1618,6 +1728,8 @@ class OracleCredential(Credential):
1618
1728
  self.port = port
1619
1729
  self.database = database
1620
1730
  self.auth = auth
1731
+ self.tls_enabled = tls_enabled
1732
+ self.tls_ca_certificates = tls_ca_certificates
1621
1733
  self.enable_catalog = enable_catalog
1622
1734
 
1623
1735
  def _immutable_fields(self) -> set[str]:
@@ -1630,6 +1742,8 @@ class OracleCredential(Credential):
1630
1742
  "host",
1631
1743
  "port",
1632
1744
  "database",
1745
+ "tls_enabled",
1746
+ "tls_ca_certificates",
1633
1747
  "enable_catalog",
1634
1748
  },
1635
1749
  }
@@ -1639,7 +1753,7 @@ class OracleCredential(Credential):
1639
1753
 
1640
1754
  def _nested_objects(
1641
1755
  self,
1642
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1756
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1643
1757
  return {"auth": self.auth}
1644
1758
 
1645
1759
 
@@ -1871,7 +1985,7 @@ class MsPowerBiCredentialAuthEntraId(ApiSecretChangeNestedResource):
1871
1985
 
1872
1986
  def _nested_objects(
1873
1987
  self,
1874
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
1988
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1875
1989
  return {}
1876
1990
 
1877
1991
 
@@ -1922,7 +2036,7 @@ class MsPowerBiCredential(Credential):
1922
2036
 
1923
2037
  def _nested_objects(
1924
2038
  self,
1925
- ) -> dict[str, Optional["Diffable | list[Diffable]"] | None]:
2039
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
1926
2040
  return {"auth": self.auth}
1927
2041
 
1928
2042
 
@@ -1978,6 +2092,115 @@ class SigmaCredential(Credential):
1978
2092
  return {"client_secret"}
1979
2093
 
1980
2094
 
2095
+ class TeradataCredentialUserPassword(ApiSecretChangeNestedResource):
2096
+ """Teradata credential via user-password auth."""
2097
+
2098
+ def __init__(
2099
+ self,
2100
+ user: str,
2101
+ password: str,
2102
+ ):
2103
+ """
2104
+ Constructor.
2105
+
2106
+ :param user: Username.
2107
+ :param password: Password.
2108
+ """
2109
+ self.user = user
2110
+ self.password = password
2111
+
2112
+ def _api_variant_name(self) -> str:
2113
+ return "userPassword"
2114
+
2115
+ def _immutable_fields(self) -> set[str]:
2116
+ return set({})
2117
+
2118
+ def _mutable_fields(self) -> set[str]:
2119
+ return {"user"}
2120
+
2121
+ def _secret_fields(self) -> set[str]:
2122
+ return {"password"}
2123
+
2124
+ def _nested_objects(
2125
+ self,
2126
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
2127
+ return {}
2128
+
2129
+
2130
+ TeradataCredentialAuth = Union[TeradataCredentialUserPassword]
2131
+
2132
+
2133
+ class TeradataCredential(Credential):
2134
+ """Credential to connect to a Teradata warehouse."""
2135
+
2136
+ def __init__(
2137
+ self,
2138
+ name: str,
2139
+ host: str,
2140
+ auth: TeradataCredentialAuth,
2141
+ ssl_mode: TeradataSSLMode,
2142
+ https_port: int | None = None,
2143
+ tdmst_port: int | None = None,
2144
+ enable_catalog: bool = False,
2145
+ display_name: str | None = None,
2146
+ ignore_changes: bool = False,
2147
+ __internal__: ResourceGraph | None = None,
2148
+ ):
2149
+ """
2150
+ Constructor.
2151
+
2152
+ :param name: Unique resource name assigned to the credential
2153
+ :param host: Host address of the server.
2154
+ :param auth: Credentials to use for authentication.
2155
+ :param ssl_mode: SSL encryption mode when connecting to Teradata.
2156
+ :param https_port: Port number to use to access the Teradata server
2157
+ via SSL connection (default 443).
2158
+ :param tdmst_port: Port number to use to access the Teradata server
2159
+ via non-SSL connection (default 1025).
2160
+ :param enable_catalog: If set to true, this credential will
2161
+ be used to fetch catalog information.
2162
+ :param display_name: Human-readable name for the credential. This name is
2163
+ visible in the UI and does not need to be unique.
2164
+ :param ignore_changes: If set to true, changes to the resource will be ignored.
2165
+ """
2166
+ super().__init__(
2167
+ name=name,
2168
+ display_name=display_name,
2169
+ ignore_changes=ignore_changes,
2170
+ __internal__=__internal__,
2171
+ )
2172
+
2173
+ self.host = host
2174
+ self.auth = auth
2175
+ self.ssl_mode = _maybe_cast(ssl_mode, TeradataSSLMode)
2176
+ self.https_port = https_port
2177
+ self.tdmst_port = tdmst_port
2178
+ self.enable_catalog = enable_catalog
2179
+
2180
+ def _immutable_fields(self) -> set[str]:
2181
+ return set({})
2182
+
2183
+ def _mutable_fields(self) -> set[str]:
2184
+ return {
2185
+ *super()._mutable_fields(),
2186
+ *{
2187
+ "host",
2188
+ "enable_catalog",
2189
+ "ssl_mode",
2190
+ "https_port",
2191
+ "tdmst_port",
2192
+ },
2193
+ }
2194
+
2195
+ def _secret_fields(self) -> set[str]:
2196
+ return set()
2197
+
2198
+ def _nested_objects(
2199
+ self,
2200
+ ) -> dict[str, "Diffable | list[Diffable] | None"]:
2201
+ return {"auth": self.auth}
2202
+
2203
+
1981
2204
  WarehouseCredential = Union[
1982
2205
  AzureSynapseCredential,
1983
2206
  AwsAthenaCredential,
@@ -1986,5 +2209,7 @@ WarehouseCredential = Union[
1986
2209
  DatabricksCredential,
1987
2210
  GcpCredential,
1988
2211
  PostgreSqlCredential,
2212
+ OracleCredential,
1989
2213
  SnowflakeCredential,
2214
+ TeradataCredential,
1990
2215
  ]
@@ -39,6 +39,7 @@ from validio_sdk.resource.credentials import (
39
39
  OracleCredential,
40
40
  PostgreSqlCredential,
41
41
  SnowflakeCredential,
42
+ TeradataCredential,
42
43
  WarehouseCredential,
43
44
  )
44
45
  from validio_sdk.resource.enums import IncidentGroupPriority
@@ -1444,6 +1445,77 @@ class OracleSource(Source):
1444
1445
  }
1445
1446
 
1446
1447
 
1448
+ class TeradataSource(Source):
1449
+ """A Teradata source configuration."""
1450
+
1451
+ def __init__(
1452
+ self,
1453
+ name: str,
1454
+ credential: TeradataCredential,
1455
+ database: str,
1456
+ table: str,
1457
+ schedule: str | None,
1458
+ display_name: str | None = None,
1459
+ owner: str | None = None,
1460
+ ignore_changes: bool = False,
1461
+ jtd_schema: JsonTypeDefinition | None = None,
1462
+ description: str | None = None,
1463
+ tags: list[Tag] | None = None,
1464
+ priority: IncidentGroupPriority | None = None,
1465
+ ):
1466
+ """
1467
+ Constructor.
1468
+
1469
+ :param database: Name of the database to connect to.
1470
+ :param table: Name of table to monitor.
1471
+ :param schedule: A 5-digit cron expression specifying how when the source
1472
+ polls for new data. Example: '0 0 * * *' to poll daily at midnight.
1473
+ :param display_name: Human-readable name for the source. This name is
1474
+ visible in the UI and does not need to be unique.
1475
+ :param owner: User email address of the source owner.
1476
+ :param ignore_changes: If set to true, changes to the resource will be ignored.
1477
+ :param description: Description of the resource.
1478
+ :param tags: Tags to add to the resource
1479
+ :param priority: Priority to assign to incidents created by this source.
1480
+ """
1481
+ super().__init__(
1482
+ name=name,
1483
+ credential=credential,
1484
+ display_name=display_name,
1485
+ owner=owner,
1486
+ ignore_changes=ignore_changes,
1487
+ jtd_schema=jtd_schema,
1488
+ description=description,
1489
+ tags=tags,
1490
+ priority=priority,
1491
+ )
1492
+
1493
+ self.database = database
1494
+ self.table = table
1495
+ self.schedule = schedule
1496
+
1497
+ def _immutable_fields(self) -> set[str]:
1498
+ return {
1499
+ *super()._immutable_fields(),
1500
+ *{
1501
+ "database",
1502
+ "table",
1503
+ },
1504
+ }
1505
+
1506
+ def _mutable_fields(self) -> set[str]:
1507
+ return {
1508
+ *super()._mutable_fields(),
1509
+ *{"schedule"},
1510
+ }
1511
+
1512
+ def _api_infer_schema_input(self) -> dict[str, object] | None:
1513
+ return {
1514
+ "database": self.database,
1515
+ "table": self.table,
1516
+ }
1517
+
1518
+
1447
1519
  # Streaming
1448
1520
 
1449
1521
 
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
 
3
+ from validio_sdk.code._import import ImportContext
3
4
  from validio_sdk.resource._diff import (
4
5
  CascadeReplacementReason,
5
6
  DiffContext,
@@ -11,6 +12,7 @@ from validio_sdk.resource._diff import (
11
12
  _diff_resource_graph,
12
13
  )
13
14
  from validio_sdk.resource._resource import ResourceGraph
15
+ from validio_sdk.resource._serde import _import_value_repr
14
16
  from validio_sdk.resource.channels import Channel, SlackChannel, WebhookChannel
15
17
  from validio_sdk.resource.credentials import AwsCredential, Credential, DemoCredential
16
18
  from validio_sdk.resource.filters import (
@@ -1774,3 +1776,49 @@ def test_enum_filter_values_should_ignore_order() -> None:
1774
1776
 
1775
1777
  _add_namespace(namespace, server_ctx)
1776
1778
  assert expected == _diff_resource_graph(namespace, manifest_ctx, server_ctx)
1779
+
1780
+
1781
+ @pytest.mark.parametrize(
1782
+ ("input", "expected"),
1783
+ [
1784
+ (
1785
+ "this string has no special characters",
1786
+ "'this string has no special characters'",
1787
+ ),
1788
+ (
1789
+ "this string contains '",
1790
+ '"this string contains \'"',
1791
+ ),
1792
+ (
1793
+ "this\nmultiline\nshould be\ntripple quoted",
1794
+ "'''this\nmultiline\nshould be\ntripple quoted'''",
1795
+ ),
1796
+ (
1797
+ "multiline with '''\nshould be\ndouble quotes",
1798
+ '"""multiline with \'\'\'\nshould be\ndouble quotes"""',
1799
+ ),
1800
+ (
1801
+ "multiline trailing\nshould be\ndouble quotes'",
1802
+ '"""multiline trailing\nshould be\ndouble quotes\'"""',
1803
+ ),
1804
+ (
1805
+ "'multiline leading\nshould be\ndouble quotes",
1806
+ '"""\'multiline leading\nshould be\ndouble quotes"""',
1807
+ ),
1808
+ (
1809
+ 'multiline with """\nshould be\nsingle quotes',
1810
+ "'''multiline with \"\"\"\nshould be\nsingle quotes'''",
1811
+ ),
1812
+ (
1813
+ 'multiline trailing\nshould be\nsingle quotes"',
1814
+ "'''multiline trailing\nshould be\nsingle quotes\"'''",
1815
+ ),
1816
+ (
1817
+ '"multiline leading\nshould be\nsingle quotes',
1818
+ "'''\"multiline leading\nshould be\nsingle quotes'''",
1819
+ ),
1820
+ ],
1821
+ )
1822
+ def test_import_value_repr(input: str, expected: str) -> None:
1823
+ got = _import_value_repr(input, 0, ImportContext())
1824
+ assert got == expected
@@ -416,7 +416,7 @@ class Validator(Resource, ABC):
416
416
  def _mutable_fields(self) -> set[str]:
417
417
  return {
418
418
  *super()._mutable_fields(),
419
- *{"description", "filter_name", "owner", "priority"},
419
+ *{"description", "filter_name", "owner", "priority", "tag_names"},
420
420
  }
421
421
 
422
422
  def _ignored_fields(self) -> set[str]:
File without changes