validio-sdk 7.4.2__tar.gz → 8.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-7.4.2 → validio_sdk-8.0.0}/PKG-INFO +7 -3
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/pyproject.toml +1 -1
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/_api/api.py +101 -8
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/_import.py +6 -2
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/plan.py +38 -41
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_diff.py +86 -46
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_diffable.py +19 -7
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_resource.py +156 -76
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_serde.py +25 -94
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_server_resources.py +166 -11
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_util.py +28 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/channels.py +256 -36
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/credentials.py +336 -107
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/filters.py +38 -7
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/notification_rules.py +33 -113
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/replacement.py +24 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/segmentations.py +22 -8
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/sources.py +112 -112
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tags.py +6 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test__diff.py +297 -8
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test__plan.py +22 -13
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test__resource.py +41 -7
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test__sql_validation.py +0 -4
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test_import.py +0 -1
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/thresholds.py +7 -1
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/validators.py +635 -190
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/windows.py +41 -32
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/util.py +5 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/LICENSE +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/README_PUBLIC.md +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/_api/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/client/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/client/client.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/_progress.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/apply.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/scaffold.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/code/settings.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/config.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/dbt.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/exception.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/metadata.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/py.typed +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_diff_util.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_errors.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_resource_graph.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/_update_namespace.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/enums.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/__init__.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/assets/example_manifest.json +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/assets/expected_trimmed_manifest.json +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/resource/tests/test__dbt.py +0 -0
- {validio_sdk-7.4.2 → validio_sdk-8.0.0}/validio_sdk/scalars.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: validio-sdk
|
|
3
|
-
Version:
|
|
3
|
+
Version: 8.0.0
|
|
4
4
|
Summary: SDK to interact with the Validio platform
|
|
5
|
-
Home-page: https://validio.io/
|
|
6
5
|
License: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
7
|
Author: Validio
|
|
8
8
|
Author-email: support@validio.io
|
|
9
9
|
Requires-Python: >=3.10,<4.0
|
|
@@ -11,11 +11,15 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
17
|
Requires-Dist: camel-converter (>=3.0.0,<4.0.0)
|
|
15
18
|
Requires-Dist: gql[all] (>=3.5.0,<4.0.0)
|
|
16
19
|
Requires-Dist: platformdirs (>=3.5.0,<4.0.0)
|
|
17
20
|
Requires-Dist: rich (==13.9.4)
|
|
18
21
|
Project-URL: Documentation, https://dev.validio.io/sdk-docs
|
|
22
|
+
Project-URL: Homepage, https://validio.io/
|
|
19
23
|
Description-Content-Type: text/markdown
|
|
20
24
|
|
|
21
25
|
# validio-sdk
|
|
@@ -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 = "8.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"
|
|
@@ -258,6 +258,10 @@ class APIClient:
|
|
|
258
258
|
async with self.client as session:
|
|
259
259
|
return await get_namespaces(session, namespace_id)
|
|
260
260
|
|
|
261
|
+
async def get_namespace_role_configs(self) -> list[dict[str, Any]]:
|
|
262
|
+
async with self.client as session:
|
|
263
|
+
return await get_namespace_role_configs(session)
|
|
264
|
+
|
|
261
265
|
async def create_namespace(
|
|
262
266
|
self,
|
|
263
267
|
namespace_id: str,
|
|
@@ -418,7 +422,6 @@ async def get_sources(
|
|
|
418
422
|
project
|
|
419
423
|
dataset
|
|
420
424
|
table
|
|
421
|
-
lookbackDays
|
|
422
425
|
schedule
|
|
423
426
|
billingProject
|
|
424
427
|
}
|
|
@@ -438,7 +441,6 @@ async def get_sources(
|
|
|
438
441
|
catalog
|
|
439
442
|
database
|
|
440
443
|
table
|
|
441
|
-
lookbackDays
|
|
442
444
|
schedule
|
|
443
445
|
}
|
|
444
446
|
}
|
|
@@ -457,7 +459,6 @@ async def get_sources(
|
|
|
457
459
|
database
|
|
458
460
|
schema
|
|
459
461
|
table
|
|
460
|
-
lookbackDays
|
|
461
462
|
schedule
|
|
462
463
|
}
|
|
463
464
|
}
|
|
@@ -466,7 +467,6 @@ async def get_sources(
|
|
|
466
467
|
database
|
|
467
468
|
schema
|
|
468
469
|
table
|
|
469
|
-
lookbackDays
|
|
470
470
|
schedule
|
|
471
471
|
}
|
|
472
472
|
}
|
|
@@ -475,7 +475,6 @@ async def get_sources(
|
|
|
475
475
|
catalog
|
|
476
476
|
schema
|
|
477
477
|
table
|
|
478
|
-
lookbackDays
|
|
479
478
|
schedule
|
|
480
479
|
httpPath
|
|
481
480
|
}
|
|
@@ -515,7 +514,6 @@ async def get_sources(
|
|
|
515
514
|
database
|
|
516
515
|
schema
|
|
517
516
|
table
|
|
518
|
-
lookbackDays
|
|
519
517
|
schedule
|
|
520
518
|
}
|
|
521
519
|
}
|
|
@@ -526,7 +524,6 @@ async def get_sources(
|
|
|
526
524
|
database
|
|
527
525
|
schema
|
|
528
526
|
table
|
|
529
|
-
lookbackDays
|
|
530
527
|
schedule
|
|
531
528
|
}
|
|
532
529
|
}
|
|
@@ -549,7 +546,6 @@ async def get_sources(
|
|
|
549
546
|
config {
|
|
550
547
|
database
|
|
551
548
|
table
|
|
552
|
-
lookbackDays
|
|
553
549
|
schedule
|
|
554
550
|
}
|
|
555
551
|
}
|
|
@@ -975,6 +971,19 @@ async def get_credentials(
|
|
|
975
971
|
optBaseUrl: baseUrl
|
|
976
972
|
}
|
|
977
973
|
}
|
|
974
|
+
... on AwsBedrockCredential {
|
|
975
|
+
config {
|
|
976
|
+
model
|
|
977
|
+
region
|
|
978
|
+
optBaseUrl: baseUrl
|
|
979
|
+
auth {
|
|
980
|
+
__typename
|
|
981
|
+
... on AwsBedrockCredentialAccessKey {
|
|
982
|
+
accessKeyId
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
978
987
|
... on AwsCredential {
|
|
979
988
|
config {
|
|
980
989
|
accessKey
|
|
@@ -1312,6 +1321,29 @@ async def get_channels(
|
|
|
1312
1321
|
}
|
|
1313
1322
|
}
|
|
1314
1323
|
}
|
|
1324
|
+
... on ServiceNowChannel {
|
|
1325
|
+
config {
|
|
1326
|
+
applicationLinkUrl
|
|
1327
|
+
instanceUrl
|
|
1328
|
+
statusMapping {
|
|
1329
|
+
triage
|
|
1330
|
+
investigating
|
|
1331
|
+
resolved
|
|
1332
|
+
serviceNowActive
|
|
1333
|
+
resolvedCloseCode
|
|
1334
|
+
notAnAnomalyCloseCode
|
|
1335
|
+
}
|
|
1336
|
+
serviceNowAuth: auth {
|
|
1337
|
+
__typename
|
|
1338
|
+
... on ServiceNowBasicAuth {
|
|
1339
|
+
username
|
|
1340
|
+
}
|
|
1341
|
+
... on ServiceNowOAuth {
|
|
1342
|
+
clientId
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1315
1347
|
}
|
|
1316
1348
|
"""
|
|
1317
1349
|
|
|
@@ -1360,6 +1392,7 @@ async def get_channels(
|
|
|
1360
1392
|
[
|
|
1361
1393
|
("msTeamsInteractiveMessageEnabled", "interactiveMessageEnabled"),
|
|
1362
1394
|
("emailInteractiveMessageEnabled", "interactiveMessageEnabled"),
|
|
1395
|
+
("serviceNowAuth", "auth"),
|
|
1363
1396
|
],
|
|
1364
1397
|
)
|
|
1365
1398
|
|
|
@@ -1952,6 +1985,50 @@ async def get_validators(
|
|
|
1952
1985
|
}}
|
|
1953
1986
|
{reference_source_config}
|
|
1954
1987
|
}}
|
|
1988
|
+
... on ReferentialIntegrityValidator {{
|
|
1989
|
+
config {{
|
|
1990
|
+
keyFields {{
|
|
1991
|
+
sourceField
|
|
1992
|
+
referenceField
|
|
1993
|
+
}}
|
|
1994
|
+
valueFields {{
|
|
1995
|
+
sourceField
|
|
1996
|
+
referenceField
|
|
1997
|
+
}}
|
|
1998
|
+
referenceSource {{
|
|
1999
|
+
resourceName
|
|
2000
|
+
}}
|
|
2001
|
+
referenceWindow {{
|
|
2002
|
+
resourceName
|
|
2003
|
+
}}
|
|
2004
|
+
referenceFilter {{
|
|
2005
|
+
resourceName
|
|
2006
|
+
}}
|
|
2007
|
+
initializeWithBackfill
|
|
2008
|
+
threshold {{
|
|
2009
|
+
...ThresholdDetails
|
|
2010
|
+
}}
|
|
2011
|
+
}}
|
|
2012
|
+
}}
|
|
2013
|
+
... on ReconciliationValidator {{
|
|
2014
|
+
config {{
|
|
2015
|
+
reconciliationMetric: metric
|
|
2016
|
+
segmentationFieldMapping {{
|
|
2017
|
+
sourceField
|
|
2018
|
+
referenceField
|
|
2019
|
+
}}
|
|
2020
|
+
referenceValidatorLeft {{
|
|
2021
|
+
resourceName
|
|
2022
|
+
}}
|
|
2023
|
+
referenceValidatorRight {{
|
|
2024
|
+
resourceName
|
|
2025
|
+
}}
|
|
2026
|
+
initializeWithBackfill
|
|
2027
|
+
threshold {{
|
|
2028
|
+
...ThresholdDetails
|
|
2029
|
+
}}
|
|
2030
|
+
}}
|
|
2031
|
+
}}
|
|
1955
2032
|
... on SqlValidator {{
|
|
1956
2033
|
config {{
|
|
1957
2034
|
query
|
|
@@ -2041,6 +2118,7 @@ async def get_validators(
|
|
|
2041
2118
|
("categoricalDistributionMetric", "metric"),
|
|
2042
2119
|
("distributionMetric", "metric"),
|
|
2043
2120
|
("numericAnomalyMetric", "metric"),
|
|
2121
|
+
("reconciliationMetric", "metric"),
|
|
2044
2122
|
("relativeTimeMetric", "metric"),
|
|
2045
2123
|
("relativeVolumeMetric", "metric"),
|
|
2046
2124
|
("volumeMetric", "metric"),
|
|
@@ -2257,6 +2335,21 @@ async def get_namespaces(
|
|
|
2257
2335
|
return await execute(session, query, variable_values=variable_values)
|
|
2258
2336
|
|
|
2259
2337
|
|
|
2338
|
+
async def get_namespace_role_configs(
|
|
2339
|
+
session: Session,
|
|
2340
|
+
) -> list[dict[str, Any]]:
|
|
2341
|
+
query = """
|
|
2342
|
+
query {
|
|
2343
|
+
roleConfigs(input: {type: Namespace}) {
|
|
2344
|
+
id
|
|
2345
|
+
name
|
|
2346
|
+
isSystemRole
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
"""
|
|
2350
|
+
return await execute(session, query)
|
|
2351
|
+
|
|
2352
|
+
|
|
2260
2353
|
async def create_namespace(
|
|
2261
2354
|
session: Session,
|
|
2262
2355
|
namespace_id: str,
|
|
@@ -6,7 +6,10 @@ from typing import Any, cast
|
|
|
6
6
|
from validio_sdk.exception import ValidioError
|
|
7
7
|
from validio_sdk.resource._resource import DiffContext, Resource
|
|
8
8
|
from validio_sdk.resource._serde import IGNORE_CHANGES_FIELD_NAME
|
|
9
|
-
from validio_sdk.resource.
|
|
9
|
+
from validio_sdk.resource._util import (
|
|
10
|
+
_CREDENTIALS_WITH_DEPENDENCIES,
|
|
11
|
+
_VALIDATORS_WITH_DEPENDENCIES,
|
|
12
|
+
)
|
|
10
13
|
from validio_sdk.resource.channels import Channel
|
|
11
14
|
from validio_sdk.resource.credentials import Credential
|
|
12
15
|
from validio_sdk.resource.filters import Filter
|
|
@@ -320,7 +323,8 @@ def add_resource_decls(
|
|
|
320
323
|
# others last.
|
|
321
324
|
resources.sort(
|
|
322
325
|
key=lambda p: (
|
|
323
|
-
p[1].__class__.__name__ in
|
|
326
|
+
p[1].__class__.__name__ in _CREDENTIALS_WITH_DEPENDENCIES,
|
|
327
|
+
p[1].__class__.__name__ in _VALIDATORS_WITH_DEPENDENCIES,
|
|
324
328
|
p[0],
|
|
325
329
|
)
|
|
326
330
|
)
|
|
@@ -24,7 +24,6 @@ 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 WebhookChannel
|
|
28
27
|
from validio_sdk.resource.tags import Tag
|
|
29
28
|
|
|
30
29
|
|
|
@@ -228,7 +227,7 @@ def _create_resource_diff_object(
|
|
|
228
227
|
show_secrets: bool,
|
|
229
228
|
rewrites: dict[str, Any] | None = None,
|
|
230
229
|
secret_fields_changed: dict[str, Any] | None = None,
|
|
231
|
-
|
|
230
|
+
manifest: Resource | Diffable | dict | None = None,
|
|
232
231
|
) -> dict[str, object]:
|
|
233
232
|
if rewrites is None:
|
|
234
233
|
rewrites = {}
|
|
@@ -236,13 +235,24 @@ def _create_resource_diff_object(
|
|
|
236
235
|
if secret_fields_changed is None:
|
|
237
236
|
secret_fields_changed = {}
|
|
238
237
|
|
|
238
|
+
is_manifest = manifest is None
|
|
239
|
+
|
|
239
240
|
data = dict(r) if not isinstance(r, dict) else r
|
|
241
|
+
manifest_data = (
|
|
242
|
+
dict(manifest)
|
|
243
|
+
if not isinstance(manifest, dict) and manifest is not None
|
|
244
|
+
else manifest
|
|
245
|
+
)
|
|
240
246
|
|
|
241
247
|
diff_object = {}
|
|
242
248
|
for k, v in data.items():
|
|
243
249
|
if k.startswith("_"):
|
|
244
250
|
continue
|
|
245
251
|
|
|
252
|
+
nested_manifest_data = (
|
|
253
|
+
manifest_data.get(k) if isinstance(manifest_data, dict) else None
|
|
254
|
+
)
|
|
255
|
+
|
|
246
256
|
if k in rewrites:
|
|
247
257
|
diff_object[k] = rewrites[k]
|
|
248
258
|
elif isinstance(v, Resource | Diffable | dict):
|
|
@@ -251,7 +261,7 @@ def _create_resource_diff_object(
|
|
|
251
261
|
v,
|
|
252
262
|
show_secrets,
|
|
253
263
|
secret_fields_changed=ch if not isinstance(ch, bool) else {},
|
|
254
|
-
|
|
264
|
+
manifest=nested_manifest_data,
|
|
255
265
|
)
|
|
256
266
|
elif hasattr(v, "__dict__") and not isinstance(v, Enum):
|
|
257
267
|
ch = secret_fields_changed.get(k)
|
|
@@ -259,7 +269,7 @@ def _create_resource_diff_object(
|
|
|
259
269
|
v.__dict__,
|
|
260
270
|
show_secrets,
|
|
261
271
|
secret_fields_changed=ch if not isinstance(ch, bool) else {},
|
|
262
|
-
|
|
272
|
+
manifest=nested_manifest_data,
|
|
263
273
|
)
|
|
264
274
|
elif isinstance(v, list):
|
|
265
275
|
if len(v) == 0 or not isinstance(v[0], Diffable):
|
|
@@ -272,7 +282,7 @@ def _create_resource_diff_object(
|
|
|
272
282
|
_create_resource_diff_object(
|
|
273
283
|
item.__dict__,
|
|
274
284
|
show_secrets,
|
|
275
|
-
|
|
285
|
+
manifest=None,
|
|
276
286
|
)
|
|
277
287
|
)
|
|
278
288
|
|
|
@@ -280,41 +290,28 @@ def _create_resource_diff_object(
|
|
|
280
290
|
else:
|
|
281
291
|
diff_object[k] = v
|
|
282
292
|
|
|
283
|
-
#
|
|
284
|
-
if not
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
flag_webhook_url_as_changed = True
|
|
307
|
-
|
|
308
|
-
if (
|
|
309
|
-
# Special case handling for webhook URL
|
|
310
|
-
flag_webhook_url_as_changed
|
|
311
|
-
or
|
|
312
|
-
# If the field does not exist in the secrets changed dict then
|
|
313
|
-
# don't flag it as changed
|
|
314
|
-
secret_fields_changed.get(field)
|
|
315
|
-
) and not is_manifest:
|
|
316
|
-
diff_object[field] = "REDACTED-PREVIOUS"
|
|
317
|
-
else:
|
|
318
|
-
diff_object[field] = "REDACTED"
|
|
293
|
+
# Nothing to mask, no secrets
|
|
294
|
+
if not hasattr(r, "_secret_fields"):
|
|
295
|
+
return diff_object
|
|
296
|
+
|
|
297
|
+
for secret_field in r._secret_fields():
|
|
298
|
+
# The field can either be in the map set to False, or absent, to be
|
|
299
|
+
# treated as unchanged.
|
|
300
|
+
field_changed = (
|
|
301
|
+
secret_field in secret_fields_changed
|
|
302
|
+
and secret_fields_changed[secret_field]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if is_manifest:
|
|
306
|
+
if not show_secrets:
|
|
307
|
+
diff_object[secret_field] = "REDACTED"
|
|
308
|
+
elif field_changed:
|
|
309
|
+
diff_object[secret_field] = (
|
|
310
|
+
"UNKNOWN" if show_secrets else "REDACTED-PREVIOUS"
|
|
311
|
+
)
|
|
312
|
+
elif not show_secrets:
|
|
313
|
+
diff_object[secret_field] = "REDACTED"
|
|
314
|
+
elif manifest_data and secret_field in manifest_data:
|
|
315
|
+
diff_object[secret_field] = manifest_data[secret_field]
|
|
319
316
|
|
|
320
317
|
return diff_object
|
|
@@ -38,6 +38,7 @@ from validio_sdk.resource._util import SourceSchemaReinference, _sanitized_error
|
|
|
38
38
|
from validio_sdk.resource.replacement import (
|
|
39
39
|
CascadeReplacementReason,
|
|
40
40
|
ImmutableFieldReplacementReason,
|
|
41
|
+
RecreateOnChangeReplacementReason,
|
|
41
42
|
ReplacementContext,
|
|
42
43
|
ReplacementReason,
|
|
43
44
|
)
|
|
@@ -217,16 +218,20 @@ def _compute_replacements(
|
|
|
217
218
|
graph_diff.replacement_ctx, parent_resource_type
|
|
218
219
|
)
|
|
219
220
|
|
|
220
|
-
to_replace = {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
to_replace: dict[str, ReplacementReason] = {}
|
|
222
|
+
for name, resource_update in to_update.items():
|
|
223
|
+
if resource_update.replacement_field:
|
|
224
|
+
to_replace[name] = ImmutableFieldReplacementReason(
|
|
225
|
+
field_name=resource_update.replacement_field,
|
|
226
|
+
resource_update=resource_update,
|
|
227
|
+
)
|
|
228
|
+
elif resource_update.manifest.recreate_on_change:
|
|
229
|
+
to_replace[name] = RecreateOnChangeReplacementReason(
|
|
230
|
+
resource_update=resource_update,
|
|
231
|
+
)
|
|
225
232
|
|
|
226
|
-
for name,
|
|
227
|
-
replacement_ctx[name] =
|
|
228
|
-
field_name=replacement_field, resource_update=resource_update
|
|
229
|
-
)
|
|
233
|
+
for name, reason in to_replace.items():
|
|
234
|
+
replacement_ctx[name] = reason
|
|
230
235
|
_visit_resource_to_replace(
|
|
231
236
|
manifest_ctx=manifest_ctx,
|
|
232
237
|
server_ctx=server_ctx,
|
|
@@ -594,52 +599,87 @@ def diff(
|
|
|
594
599
|
|
|
595
600
|
# No changes yet. Next, descend into the nested fields. If we find any
|
|
596
601
|
# changes, then mark _this_ resource as changed.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
602
|
+
# For immutable nested objects, changes require the resource to be recreated.
|
|
603
|
+
nested_object_sources = [
|
|
604
|
+
(
|
|
605
|
+
manifest_object._mutable_nested_objects(),
|
|
606
|
+
server_object._mutable_nested_objects(),
|
|
607
|
+
False,
|
|
608
|
+
),
|
|
609
|
+
(
|
|
610
|
+
manifest_object._immutable_nested_objects(),
|
|
611
|
+
server_object._immutable_nested_objects(),
|
|
612
|
+
True,
|
|
613
|
+
),
|
|
614
|
+
]
|
|
615
|
+
for manifest_nested, server_nested, is_immutable in nested_object_sources:
|
|
616
|
+
for field, manifest in manifest_nested.items():
|
|
617
|
+
server = server_nested[field]
|
|
618
|
+
replacement_field = field if is_immutable else None
|
|
608
619
|
|
|
609
|
-
|
|
610
|
-
|
|
620
|
+
# No possible change if both are unset.
|
|
621
|
+
if manifest is None and server is None:
|
|
622
|
+
continue
|
|
611
623
|
|
|
612
|
-
|
|
613
|
-
if
|
|
614
|
-
return ResourceUpdate(
|
|
624
|
+
# If exactly one of them is None, then we definitely have a change.
|
|
625
|
+
if manifest is None or server is None:
|
|
626
|
+
return ResourceUpdate(
|
|
627
|
+
manifest_resource,
|
|
628
|
+
server_resource,
|
|
629
|
+
replacement_field=replacement_field,
|
|
630
|
+
)
|
|
615
631
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
collected_diff = diff(
|
|
619
|
-
manifest[i],
|
|
620
|
-
server[i],
|
|
621
|
-
curr_depth + 1,
|
|
632
|
+
if isinstance(server, list) != isinstance(manifest, list):
|
|
633
|
+
return ResourceUpdate(
|
|
622
634
|
manifest_resource,
|
|
623
635
|
server_resource,
|
|
636
|
+
replacement_field=replacement_field,
|
|
624
637
|
)
|
|
625
638
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
639
|
+
if isinstance(server, list) and isinstance(manifest, list):
|
|
640
|
+
if len(server) != len(manifest):
|
|
641
|
+
return ResourceUpdate(
|
|
642
|
+
manifest_resource,
|
|
643
|
+
server_resource,
|
|
644
|
+
replacement_field=replacement_field,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
for i in range(len(server)):
|
|
648
|
+
# Descend into both objects to diff.
|
|
649
|
+
collected_diff = diff(
|
|
650
|
+
manifest[i],
|
|
651
|
+
server[i],
|
|
652
|
+
curr_depth + 1,
|
|
653
|
+
manifest_resource,
|
|
654
|
+
server_resource,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
if collected_diff:
|
|
658
|
+
# For immutable, flag for replacement. For mutable,
|
|
659
|
+
# return the collected diff directly.
|
|
660
|
+
if is_immutable:
|
|
661
|
+
return ResourceUpdate(
|
|
662
|
+
manifest_resource,
|
|
663
|
+
server_resource,
|
|
664
|
+
replacement_field=replacement_field,
|
|
665
|
+
)
|
|
666
|
+
return collected_diff
|
|
631
667
|
|
|
632
|
-
|
|
668
|
+
continue
|
|
633
669
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
670
|
+
collected_diff = diff(
|
|
671
|
+
manifest, server, curr_depth + 1, manifest_resource, server_resource
|
|
672
|
+
)
|
|
673
|
+
if collected_diff:
|
|
674
|
+
# For immutable, flag for replacement. For mutable,
|
|
675
|
+
# return the collected diff directly.
|
|
676
|
+
if is_immutable:
|
|
677
|
+
return ResourceUpdate(
|
|
678
|
+
manifest_resource,
|
|
679
|
+
server_resource,
|
|
680
|
+
replacement_field=replacement_field,
|
|
681
|
+
)
|
|
682
|
+
return collected_diff
|
|
643
683
|
|
|
644
684
|
# No update to make
|
|
645
685
|
return None
|
|
@@ -41,7 +41,7 @@ class Diffable(ABC):
|
|
|
41
41
|
within a resource be sure to account for how that field should be diffed.
|
|
42
42
|
Every field reachable from a resource, should be returned by exactly one
|
|
43
43
|
of the listed APIs here, with the exception of `_replace_on_type_change_fields`
|
|
44
|
-
which can overlap with `
|
|
44
|
+
which can overlap with `_mutable_nested_objects`.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
47
|
@abstractmethod
|
|
@@ -57,16 +57,27 @@ class Diffable(ABC):
|
|
|
57
57
|
return set({})
|
|
58
58
|
|
|
59
59
|
@abstractmethod
|
|
60
|
-
def
|
|
60
|
+
def _mutable_nested_objects(
|
|
61
61
|
self,
|
|
62
62
|
) -> dict[str, "Diffable | list[Diffable] | None"]:
|
|
63
|
-
"""Returns any nested objects contained within this object.
|
|
63
|
+
"""Returns any mutable nested objects contained within this object.
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Mutable nested objects will be diff-ed recursively.
|
|
66
66
|
...
|
|
67
67
|
:returns dict[field, Optional[object]].
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
+
def _immutable_nested_objects(
|
|
71
|
+
self,
|
|
72
|
+
) -> dict[str, "Diffable | list[Diffable] | None"]:
|
|
73
|
+
"""Returns any immutable nested objects contained within this object.
|
|
74
|
+
|
|
75
|
+
Immutable nested objects cannot be updated after creation.
|
|
76
|
+
...
|
|
77
|
+
:returns dict[field, Optional[object]].
|
|
78
|
+
"""
|
|
79
|
+
return {}
|
|
80
|
+
|
|
70
81
|
def __iter__(self) -> Iterator[Any]:
|
|
71
82
|
yield from self.__dict__.items()
|
|
72
83
|
|
|
@@ -83,7 +94,7 @@ class Diffable(ABC):
|
|
|
83
94
|
if self._secret_fields():
|
|
84
95
|
return True
|
|
85
96
|
|
|
86
|
-
for nested in self.
|
|
97
|
+
for nested in self._mutable_nested_objects().values():
|
|
87
98
|
if isinstance(nested, list):
|
|
88
99
|
for n in nested:
|
|
89
100
|
if n._has_secret_fields(curr_depth=curr_depth + 1):
|
|
@@ -98,7 +109,8 @@ class Diffable(ABC):
|
|
|
98
109
|
return {
|
|
99
110
|
*self._immutable_fields(),
|
|
100
111
|
*self._mutable_fields(),
|
|
101
|
-
*self.
|
|
112
|
+
*self._mutable_nested_objects().keys(),
|
|
113
|
+
*self._immutable_nested_objects().keys(),
|
|
102
114
|
*self._ignored_fields(),
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -239,7 +251,7 @@ class ApiSecretChangeNestedResource(Diffable):
|
|
|
239
251
|
data[NODE_TYPE_FIELD_NAME] = self.__class__.__name__
|
|
240
252
|
return data
|
|
241
253
|
|
|
242
|
-
def
|
|
254
|
+
def _mutable_nested_objects(
|
|
243
255
|
self,
|
|
244
256
|
) -> dict[str, "Diffable | list[Diffable] | None"]:
|
|
245
257
|
return {}
|