snowpark-checkpoints-validators 0.2.0rc1__py3-none-any.whl → 0.2.1__py3-none-any.whl

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 (24) hide show
  1. snowflake/snowpark_checkpoints/__init__.py +44 -0
  2. snowflake/snowpark_checkpoints/__version__.py +16 -0
  3. snowflake/snowpark_checkpoints/checkpoint.py +580 -0
  4. snowflake/snowpark_checkpoints/errors.py +60 -0
  5. snowflake/snowpark_checkpoints/job_context.py +128 -0
  6. snowflake/snowpark_checkpoints/singleton.py +23 -0
  7. snowflake/snowpark_checkpoints/snowpark_sampler.py +124 -0
  8. snowflake/snowpark_checkpoints/spark_migration.py +255 -0
  9. snowflake/snowpark_checkpoints/utils/__init__.py +14 -0
  10. snowflake/snowpark_checkpoints/utils/constants.py +134 -0
  11. snowflake/snowpark_checkpoints/utils/extra_config.py +89 -0
  12. snowflake/snowpark_checkpoints/utils/logging_utils.py +67 -0
  13. snowflake/snowpark_checkpoints/utils/pandera_check_manager.py +399 -0
  14. snowflake/snowpark_checkpoints/utils/supported_types.py +65 -0
  15. snowflake/snowpark_checkpoints/utils/telemetry.py +900 -0
  16. snowflake/snowpark_checkpoints/utils/utils_checks.py +395 -0
  17. snowflake/snowpark_checkpoints/validation_result_metadata.py +155 -0
  18. snowflake/snowpark_checkpoints/validation_results.py +49 -0
  19. snowpark_checkpoints_validators-0.2.1.dist-info/METADATA +323 -0
  20. snowpark_checkpoints_validators-0.2.1.dist-info/RECORD +22 -0
  21. snowpark_checkpoints_validators-0.2.0rc1.dist-info/METADATA +0 -514
  22. snowpark_checkpoints_validators-0.2.0rc1.dist-info/RECORD +0 -4
  23. {snowpark_checkpoints_validators-0.2.0rc1.dist-info → snowpark_checkpoints_validators-0.2.1.dist-info}/WHEEL +0 -0
  24. {snowpark_checkpoints_validators-0.2.0rc1.dist-info → snowpark_checkpoints_validators-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,395 @@
1
+ # Copyright 2025 Snowflake Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import inspect
17
+ import json
18
+ import logging
19
+ import os
20
+ import re
21
+
22
+ from datetime import datetime
23
+ from typing import Any, Optional
24
+
25
+ import numpy as np
26
+
27
+ from pandera import DataFrameSchema
28
+
29
+ from snowflake.snowpark import DataFrame as SnowparkDataFrame
30
+ from snowflake.snowpark_checkpoints.errors import SchemaValidationError
31
+ from snowflake.snowpark_checkpoints.job_context import SnowparkJobContext
32
+ from snowflake.snowpark_checkpoints.snowpark_sampler import (
33
+ SamplingAdapter,
34
+ SamplingStrategy,
35
+ )
36
+ from snowflake.snowpark_checkpoints.utils.constants import (
37
+ CHECKPOINT_JSON_OUTPUT_FILE_FORMAT_NAME,
38
+ CHECKPOINT_TABLE_NAME_FORMAT,
39
+ COLUMNS_KEY,
40
+ DATAFRAME_CUSTOM_DATA_KEY,
41
+ DATAFRAME_EXECUTION_MODE,
42
+ DATAFRAME_PANDERA_SCHEMA_KEY,
43
+ DEFAULT_KEY,
44
+ EXCEPT_HASH_AGG_QUERY,
45
+ FAIL_STATUS,
46
+ PASS_STATUS,
47
+ SNOWPARK_CHECKPOINTS_OUTPUT_DIRECTORY_NAME,
48
+ )
49
+ from snowflake.snowpark_checkpoints.utils.extra_config import (
50
+ get_checkpoint_file,
51
+ )
52
+ from snowflake.snowpark_checkpoints.utils.pandera_check_manager import (
53
+ PanderaCheckManager,
54
+ )
55
+ from snowflake.snowpark_checkpoints.utils.telemetry import STATUS_KEY, report_telemetry
56
+ from snowflake.snowpark_checkpoints.validation_result_metadata import (
57
+ ValidationResultsMetadata,
58
+ )
59
+ from snowflake.snowpark_checkpoints.validation_results import ValidationResult
60
+
61
+
62
+ LOGGER = logging.getLogger(__name__)
63
+
64
+
65
+ def _replace_special_characters(checkpoint_name: str) -> str:
66
+ """Replace special characters in the checkpoint name with underscores.
67
+
68
+ Args:
69
+ checkpoint_name (str): The checkpoint name to process.
70
+
71
+ Returns:
72
+ str: The checkpoint name with special characters replaced by underscores.
73
+
74
+ """
75
+ regex = r"^[a-zA-Z_\s-][a-zA-Z0-9$_\s-]*$"
76
+ if not bool(re.match(regex, checkpoint_name)):
77
+ raise ValueError(
78
+ f"Invalid checkpoint name: {checkpoint_name}",
79
+ "Checkpoint name must contain only alphanumeric characters, hyphens, underscores and dollar signs.",
80
+ )
81
+ return re.sub(r"[\s-]", "_", checkpoint_name)
82
+
83
+
84
+ def _process_sampling(
85
+ df: SnowparkDataFrame,
86
+ pandera_schema: DataFrameSchema,
87
+ job_context: Optional[SnowparkJobContext] = None,
88
+ sample_frac: Optional[float] = 1.0,
89
+ sample_number: Optional[int] = None,
90
+ sampling_strategy: Optional[SamplingStrategy] = SamplingStrategy.RANDOM_SAMPLE,
91
+ ):
92
+ """Process a Snowpark DataFrame by sampling it according to the specified parameters.
93
+
94
+ Adjusts the column casing of the provided Pandera schema to uppercase.
95
+
96
+ Args:
97
+ df (SnowparkDataFrame): The Snowpark DataFrame to be sampled.
98
+ pandera_schema (DataFrameSchema): The Pandera schema to validate the DataFrame.
99
+ job_context (SnowparkJobContext, optional): The job context for the sampling operation.
100
+ Defaults to None.
101
+ sample_frac (Optional[float], optional): The fraction of rows to sample.
102
+ Defaults to 0.1.
103
+ sample_number (Optional[int], optional): The number of rows to sample.
104
+ Defaults to None.
105
+ sampling_strategy (Optional[SamplingStrategy], optional): The strategy to use for sampling.
106
+ Defaults to SamplingStrategy.RANDOM_SAMPLE.
107
+
108
+ Returns:
109
+ Tuple[DataFrameSchema, pd.DataFrame]: A tuple containing the adjusted Pandera schema with uppercase column names
110
+ and the sampled pandas DataFrame.
111
+
112
+ """
113
+ sampler = SamplingAdapter(
114
+ job_context, sample_frac, sample_number, sampling_strategy
115
+ )
116
+ sampler.process_args([df])
117
+
118
+ # fix up the column casing
119
+ pandera_schema_upper = pandera_schema
120
+ new_columns: dict[Any, Any] = {}
121
+
122
+ for col in pandera_schema.columns:
123
+ new_columns[col.upper()] = pandera_schema.columns[col]
124
+
125
+ pandera_schema_upper = pandera_schema_upper.remove_columns(pandera_schema.columns)
126
+ pandera_schema_upper = pandera_schema_upper.add_columns(new_columns)
127
+
128
+ sample_df = sampler.get_sampled_pandas_args()[0]
129
+ sample_df.index = np.ones(sample_df.count().iloc[0])
130
+
131
+ return pandera_schema_upper, sample_df
132
+
133
+
134
+ def _generate_schema(
135
+ checkpoint_name: str, output_path: Optional[str] = None
136
+ ) -> DataFrameSchema:
137
+ """Generate a DataFrameSchema based on the checkpoint name provided.
138
+
139
+ This function reads a JSON file corresponding to the checkpoint name,
140
+ extracts schema information, and constructs a DataFrameSchema object.
141
+ It also adds custom checks for numeric and boolean types if specified
142
+ in the JSON file.
143
+
144
+ Args:
145
+ checkpoint_name (str): The name of the checkpoint used to locate
146
+ the JSON file containing schema information.
147
+ output_path (str): The path to the output directory.
148
+
149
+ DataFrameSchema: A schema object representing the structure and
150
+ constraints of the DataFrame.
151
+ constraints of the DataFrame.
152
+
153
+ """
154
+ LOGGER.info(
155
+ "Generating Pandera DataFrameSchema for checkpoint: '%s'", checkpoint_name
156
+ )
157
+ current_directory_path = output_path if output_path else os.getcwd()
158
+
159
+ output_directory_path = os.path.join(
160
+ current_directory_path, SNOWPARK_CHECKPOINTS_OUTPUT_DIRECTORY_NAME
161
+ )
162
+
163
+ if not os.path.exists(output_directory_path):
164
+ raise ValueError(
165
+ """Output directory snowpark-checkpoints-output does not exist.
166
+ Please run the Snowpark checkpoint collector first."""
167
+ )
168
+
169
+ checkpoint_schema_file_path = os.path.join(
170
+ output_directory_path,
171
+ CHECKPOINT_JSON_OUTPUT_FILE_FORMAT_NAME.format(checkpoint_name),
172
+ )
173
+
174
+ if not os.path.exists(checkpoint_schema_file_path):
175
+ raise ValueError(
176
+ f"Checkpoint {checkpoint_name} JSON file not found. Please run the Snowpark checkpoint collector first."
177
+ )
178
+
179
+ LOGGER.info("Reading schema from file: '%s'", checkpoint_schema_file_path)
180
+ with open(checkpoint_schema_file_path) as schema_file:
181
+ checkpoint_schema_config = json.load(schema_file)
182
+
183
+ if DATAFRAME_PANDERA_SCHEMA_KEY not in checkpoint_schema_config:
184
+ raise ValueError(
185
+ f"Pandera schema not found in the JSON file for checkpoint: {checkpoint_name}"
186
+ )
187
+
188
+ schema_dict = checkpoint_schema_config.get(DATAFRAME_PANDERA_SCHEMA_KEY)
189
+ schema_dict_str = json.dumps(schema_dict)
190
+ schema = DataFrameSchema.from_json(schema_dict_str)
191
+
192
+ if DATAFRAME_CUSTOM_DATA_KEY not in checkpoint_schema_config:
193
+ LOGGER.info(
194
+ "No custom data found in the JSON file for checkpoint: '%s'",
195
+ checkpoint_name,
196
+ )
197
+ return schema
198
+
199
+ custom_data = checkpoint_schema_config.get(DATAFRAME_CUSTOM_DATA_KEY)
200
+
201
+ if COLUMNS_KEY not in custom_data:
202
+ raise ValueError(
203
+ f"Columns not found in the JSON file for checkpoint: {checkpoint_name}"
204
+ )
205
+
206
+ pandera_check_manager = PanderaCheckManager(
207
+ checkpoint_name=checkpoint_name, schema=schema
208
+ )
209
+ schema = pandera_check_manager.proccess_checks(custom_data)
210
+
211
+ return schema
212
+
213
+
214
+ def _check_compare_data(
215
+ df: SnowparkDataFrame,
216
+ job_context: Optional[SnowparkJobContext],
217
+ checkpoint_name: str,
218
+ output_path: Optional[str] = None,
219
+ ):
220
+ """Compare the data in the provided Snowpark DataFrame with the data in a checkpoint table.
221
+
222
+ This function writes the provided DataFrame to a table and compares it with an existing checkpoint table
223
+ using a hash aggregation query. If there is a data mismatch, it marks the job context as failed and raises a
224
+ SchemaValidationError. If the data matches, it marks the job context as passed.
225
+
226
+ Args:
227
+ df (SnowparkDataFrame): The Snowpark DataFrame to compare.
228
+ job_context (Optional[SnowparkJobContext]): The job context containing the Snowpark session and job state.
229
+ checkpoint_name (str): The name of the checkpoint table to compare against.
230
+ output_path (Optional[str]): The path to the output directory.
231
+
232
+ Raises:
233
+ SchemaValidationError: If there is a data mismatch between the DataFrame and the checkpoint table.
234
+
235
+ """
236
+ _, err = _compare_data(df, job_context, checkpoint_name, output_path)
237
+ if err is not None:
238
+ raise err
239
+
240
+
241
+ @report_telemetry(
242
+ params_list=["df"], return_indexes=[(STATUS_KEY, 0)], multiple_return=True
243
+ )
244
+ def _compare_data(
245
+ df: SnowparkDataFrame,
246
+ job_context: Optional[SnowparkJobContext],
247
+ checkpoint_name: str,
248
+ output_path: Optional[str] = None,
249
+ ) -> tuple[bool, Optional[SchemaValidationError]]:
250
+ """Compare the data in the provided Snowpark DataFrame with the data in a checkpoint table.
251
+
252
+ This function writes the provided DataFrame to a table and compares it with an existing checkpoint table
253
+ using a hash aggregation query. If there is a data mismatch, it marks the job context as failed and raises a
254
+ SchemaValidationError. If the data matches, it marks the job context as passed.
255
+
256
+ Args:
257
+ df (SnowparkDataFrame): The Snowpark DataFrame to compare.
258
+ job_context (Optional[SnowparkJobContext]): The job context containing the Snowpark session and job state.
259
+ checkpoint_name (str): The name of the checkpoint table to compare against.
260
+ output_path (Optional[str]): The path to the output directory.
261
+
262
+ Returns:
263
+ Tuple[bool, Optional[SchemaValidationError]]: A tuple containing a boolean indicating if the data matches
264
+ and an optional SchemaValidationError if there is a data mismatch.
265
+
266
+ Raises:
267
+ SchemaValidationError: If there is a data mismatch between the DataFrame and the checkpoint table.
268
+
269
+ """
270
+ new_table_name = CHECKPOINT_TABLE_NAME_FORMAT.format(checkpoint_name)
271
+ LOGGER.info(
272
+ "Writing Snowpark DataFrame to table: '%s' for checkpoint: '%s'",
273
+ new_table_name,
274
+ checkpoint_name,
275
+ )
276
+ df.write.save_as_table(table_name=new_table_name, mode="overwrite")
277
+
278
+ LOGGER.info(
279
+ "Comparing DataFrame to checkpoint table: '%s' for checkpoint: '%s'",
280
+ new_table_name,
281
+ checkpoint_name,
282
+ )
283
+ expect_df = job_context.snowpark_session.sql(
284
+ EXCEPT_HASH_AGG_QUERY, [checkpoint_name, new_table_name]
285
+ )
286
+
287
+ if expect_df.count() != 0:
288
+ error_message = f"Data mismatch for checkpoint {checkpoint_name}"
289
+ job_context._mark_fail(
290
+ error_message,
291
+ checkpoint_name,
292
+ df,
293
+ DATAFRAME_EXECUTION_MODE,
294
+ )
295
+ _update_validation_result(
296
+ checkpoint_name,
297
+ FAIL_STATUS,
298
+ output_path,
299
+ )
300
+ return False, SchemaValidationError(
301
+ error_message,
302
+ job_context,
303
+ checkpoint_name,
304
+ df,
305
+ )
306
+ else:
307
+ _update_validation_result(checkpoint_name, PASS_STATUS, output_path)
308
+ job_context._mark_pass(checkpoint_name, DATAFRAME_EXECUTION_MODE)
309
+ return True, None
310
+
311
+
312
+ def _find_frame_in(stack: list[inspect.FrameInfo]) -> tuple:
313
+ """Find a specific frame in the provided stack trace.
314
+
315
+ This function searches through the provided stack trace to find a frame that matches
316
+ certain criteria. It looks for frames where the function name is "wrapper" or where
317
+ the code context matches specific regular expressions.
318
+
319
+ Args:
320
+ stack (list[inspect.FrameInfo]): A list of frame information objects representing
321
+ the current stack trace.
322
+
323
+ Returns:
324
+ tuple: A tuple containing the relative path of the file and the line number of the
325
+ matched frame. If no frame is matched, it returns a default key and -1.
326
+
327
+ """
328
+ regex = (
329
+ r"(?<!_check_dataframe_schema_file)"
330
+ r"(?<!_check_dataframe_schema)"
331
+ r"(validate_dataframe_checkpoint|check_dataframe_schema)"
332
+ )
333
+
334
+ first_frames = stack[:7]
335
+ first_frames.reverse()
336
+
337
+ for i, frame in enumerate(first_frames):
338
+ if frame.function == "wrapper" and i - 1 >= 0:
339
+ next_frame = first_frames[i - 1]
340
+ return _get_relative_path(next_frame.filename), next_frame.lineno
341
+
342
+ if len(frame.code_context) >= 0 and re.search(regex, frame.code_context[0]):
343
+ return _get_relative_path(frame.filename), frame.lineno
344
+ return DEFAULT_KEY, -1
345
+
346
+
347
+ def _get_relative_path(file_path: str) -> str:
348
+ """Get the relative path of a file.
349
+
350
+ Args:
351
+ file_path (str): The path to the file.
352
+
353
+ Returns:
354
+ str: The relative path of the file.
355
+
356
+ """
357
+ current_directory = os.getcwd()
358
+ return os.path.relpath(file_path, current_directory)
359
+
360
+
361
+ def _update_validation_result(
362
+ checkpoint_name: str, validation_status: str, output_path: Optional[str] = None
363
+ ) -> None:
364
+ """Update the validation result file with the status of a given checkpoint.
365
+
366
+ Args:
367
+ checkpoint_name (str): The name of the checkpoint to update.
368
+ validation_status (str): The validation status to record for the checkpoint.
369
+ output_path (str): The path to the output directory.
370
+
371
+ Returns:
372
+ None
373
+
374
+ """
375
+ _file = get_checkpoint_file(checkpoint_name)
376
+
377
+ stack = inspect.stack()
378
+
379
+ _file_from_stack, _line_of_code = _find_frame_in(stack)
380
+
381
+ pipeline_result_metadata = ValidationResultsMetadata(output_path)
382
+
383
+ pipeline_result_metadata.clean()
384
+
385
+ pipeline_result_metadata.add_validation_result(
386
+ ValidationResult(
387
+ timestamp=datetime.now().isoformat(),
388
+ file=_file if _file else _file_from_stack,
389
+ line_of_code=_line_of_code,
390
+ checkpoint_name=checkpoint_name,
391
+ result=validation_status,
392
+ )
393
+ )
394
+
395
+ pipeline_result_metadata.save()
@@ -0,0 +1,155 @@
1
+ # Copyright 2025 Snowflake Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import logging
17
+ import os
18
+
19
+ from typing import Optional
20
+
21
+ from snowflake.snowpark_checkpoints.singleton import Singleton
22
+ from snowflake.snowpark_checkpoints.utils.constants import (
23
+ SNOWPARK_CHECKPOINTS_OUTPUT_DIRECTORY_NAME,
24
+ VALIDATION_RESULTS_JSON_FILE_NAME,
25
+ )
26
+ from snowflake.snowpark_checkpoints.validation_results import (
27
+ ValidationResult,
28
+ ValidationResults,
29
+ )
30
+
31
+
32
+ LOGGER = logging.getLogger(__name__)
33
+
34
+
35
+ class ValidationResultsMetadata(metaclass=Singleton):
36
+
37
+ """ValidationResultsMetadata is a class that manages the loading, storing, and updating of validation results.
38
+
39
+ Attributes:
40
+ validation_results (list): A list to store validation results.
41
+ validation_results_file (str): The path to the validation results file.
42
+
43
+ Methods:
44
+ __init__(path: Optional[str] = None):
45
+ Initializes the PipelineResultMetadata instance and loads validation results from a JSON file
46
+ if a path is provided.
47
+ _load(path: Optional[str] = None):
48
+ Loads validation results from a JSON file. If no path is provided, the current working directory is used.
49
+ add_validation_result(validation_result: dict):
50
+ Adds a validation result to the pipeline result list.
51
+ save():
52
+ Saves the validation results to a JSON file in the current working directory.
53
+
54
+ """
55
+
56
+ def __init__(self, path: Optional[str] = None):
57
+ self._load(path)
58
+
59
+ def _load(self, path: Optional[str] = None):
60
+ """Load validation results from a JSON file.
61
+
62
+ Args:
63
+ path (Optional[str]): The directory path where the validation results file is located.
64
+ If not provided, the current working directory is used.
65
+
66
+ Raises:
67
+ Exception: If there is an error reading the validation results file.
68
+
69
+ """
70
+ self.validation_results_directory = path if path else os.getcwd()
71
+ self.validation_results_directory = os.path.join(
72
+ self.validation_results_directory,
73
+ SNOWPARK_CHECKPOINTS_OUTPUT_DIRECTORY_NAME,
74
+ )
75
+
76
+ LOGGER.debug(
77
+ "Setting validation results directory to: '%s'",
78
+ self.validation_results_directory,
79
+ )
80
+
81
+ self.validation_results_file = os.path.join(
82
+ self.validation_results_directory,
83
+ VALIDATION_RESULTS_JSON_FILE_NAME,
84
+ )
85
+
86
+ LOGGER.debug(
87
+ "Setting validation results file to: '%s'", self.validation_results_file
88
+ )
89
+
90
+ self.validation_results = ValidationResults(results=[])
91
+
92
+ if os.path.exists(self.validation_results_file):
93
+ LOGGER.info(
94
+ "Loading validation results from: '%s'", self.validation_results_file
95
+ )
96
+ with open(self.validation_results_file) as file:
97
+ try:
98
+ validation_result_json = file.read()
99
+ self.validation_results = ValidationResults.model_validate_json(
100
+ validation_result_json
101
+ )
102
+ except Exception as e:
103
+ raise Exception(
104
+ f"Error reading validation results file: {self.validation_results_file} \n {e}"
105
+ ) from None
106
+ else:
107
+ LOGGER.info(
108
+ "Validation results file not found: '%s'",
109
+ self.validation_results_file,
110
+ )
111
+
112
+ def clean(self):
113
+ """Clean the validation results list.
114
+
115
+ This method empties the validation results list.
116
+
117
+ """
118
+ if not os.path.exists(self.validation_results_file):
119
+ LOGGER.info("Cleaning validation results...")
120
+ self.validation_results.results = []
121
+
122
+ def add_validation_result(self, validation_result: ValidationResult):
123
+ """Add a validation result to the pipeline result list.
124
+
125
+ Args:
126
+ checkpoint_name (str): The name of the checkpoint.
127
+ validation_result (dict): The validation result to be added.
128
+
129
+ """
130
+ self.validation_results.results.append(validation_result)
131
+
132
+ def save(self):
133
+ """Save the validation results to a file.
134
+
135
+ This method checks if the directory specified by validation results directory
136
+ exists, and if not, it creates the directory. Then, it writes the validation results
137
+ to a file specified by validation results file in JSON format.
138
+
139
+ Raises:
140
+ OSError: If the directory cannot be created or the file cannot be written.
141
+
142
+ """
143
+ if not os.path.exists(self.validation_results_directory):
144
+ LOGGER.debug(
145
+ "Validation results directory '%s' does not exist. Creating it...",
146
+ self.validation_results_directory,
147
+ )
148
+ os.makedirs(self.validation_results_directory)
149
+
150
+ with open(self.validation_results_file, "w") as output_file:
151
+ output_file.write(self.validation_results.model_dump_json())
152
+ LOGGER.info(
153
+ "Validation results successfully saved to: '%s'",
154
+ self.validation_results_file,
155
+ )
@@ -0,0 +1,49 @@
1
+ # Copyright 2025 Snowflake Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ from pydantic import BaseModel
18
+
19
+
20
+ class ValidationResult(BaseModel):
21
+
22
+ """ValidationResult represents the result of a validation checkpoint.
23
+
24
+ Attributes:
25
+ result (str): The result of the validation.
26
+ timestamp (datetime): The timestamp when the validation was performed.
27
+ file (str): The file where the validation checkpoint is located.
28
+ line_of_code (int): The line number in the file where the validation checkpoint is located.
29
+ checkpoint_name (str): The name of the validation checkpoint.
30
+
31
+ """
32
+
33
+ result: str
34
+ timestamp: str
35
+ file: str
36
+ line_of_code: int
37
+ checkpoint_name: str
38
+
39
+
40
+ class ValidationResults(BaseModel):
41
+
42
+ """ValidationResults is a model that holds a list of validation results.
43
+
44
+ Attributes:
45
+ results (list[ValidationResult]): A list of validation results.
46
+
47
+ """
48
+
49
+ results: list[ValidationResult]