robotframework-xmlvalidator 1.0.0__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.
- robotframework_xmlvalidator-1.0.0.dist-info/LICENSE +201 -0
- robotframework_xmlvalidator-1.0.0.dist-info/METADATA +686 -0
- robotframework_xmlvalidator-1.0.0.dist-info/RECORD +8 -0
- robotframework_xmlvalidator-1.0.0.dist-info/WHEEL +4 -0
- xmlvalidator/XmlValidator.py +1605 -0
- xmlvalidator/__init__.py +60 -0
- xmlvalidator/xml_validator_results.py +413 -0
- xmlvalidator/xml_validator_utils.py +484 -0
xmlvalidator/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2024-2025 Michael Hallik
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
#
|
|
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
|
|
13
|
+
# implied.
|
|
14
|
+
#
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
This module serves as the package entry point, by exposing the
|
|
21
|
+
XmlValidator class directly at the package level.
|
|
22
|
+
|
|
23
|
+
It further provides versioning metadata for use in documentation and
|
|
24
|
+
automation.
|
|
25
|
+
|
|
26
|
+
Features:
|
|
27
|
+
|
|
28
|
+
- Defines the public API (__all__, aliasing).
|
|
29
|
+
- Simplifies imports by exposing XmlValidator at the top level.
|
|
30
|
+
- Supports dynamic version retrieval/discovery from installed metadata.
|
|
31
|
+
- Maintains compatibility with Python < 3.8 via fallback imports.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# pylint: disable=C0103:invalid-name # On account of the class name, which is not snake-cased (required by RF).
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Import versioning utilities to fetch package metadata dynamically.
|
|
39
|
+
# - `version` retrieves the installed package version from metadata.
|
|
40
|
+
# - `PackageNotFoundError` handles cases where the package is not installed.
|
|
41
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
42
|
+
# Expose XmlValidator directly for cleaner imports.
|
|
43
|
+
from .XmlValidator import XmlValidator
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Alias (i.e.: make available) XmlValidator at the package level for RF.
|
|
47
|
+
xmlvalidator = XmlValidator
|
|
48
|
+
|
|
49
|
+
# Define package metadata & control what is exposed when importing the package.
|
|
50
|
+
__all__ = ["XmlValidator"] # Controls what's exposed by: from package import *.
|
|
51
|
+
__author__ = "Michael Hallik"
|
|
52
|
+
# Expose the version (package version or fallback version).
|
|
53
|
+
try:
|
|
54
|
+
# Fetches version when installed as package.
|
|
55
|
+
__version__ = version("robotframework-xmlvalidator")
|
|
56
|
+
except PackageNotFoundError:
|
|
57
|
+
# Fall back when package not installed (default version for development).
|
|
58
|
+
__version__ = "0.0.1"
|
|
59
|
+
import warnings
|
|
60
|
+
warnings.warn("Package metadata not found, using fallback version.")
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Copyright 2024-2025 Michael Hallik
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
#
|
|
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
|
|
13
|
+
# implied.
|
|
14
|
+
#
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Provides result-handling components for XML validation operations.
|
|
21
|
+
|
|
22
|
+
This module defines:
|
|
23
|
+
|
|
24
|
+
- ValidatorResultRecorder:
|
|
25
|
+
Encapsulates validation results, including valid/invalid files and
|
|
26
|
+
associated error details. Supports logging and also exporting to CSV.
|
|
27
|
+
- ValidatorResult:
|
|
28
|
+
A lightweight result wrapper for encapsulating success/failure states
|
|
29
|
+
and their corresponding values or errors.
|
|
30
|
+
|
|
31
|
+
These classes are used internally by the XmlValidator library and
|
|
32
|
+
associated utilities to manage and report outcomes of validation
|
|
33
|
+
tasks.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Standard library imports.
|
|
38
|
+
from dataclasses import dataclass, field
|
|
39
|
+
from datetime import datetime
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, Dict, List, Optional
|
|
42
|
+
# Third party library imports.
|
|
43
|
+
import pandas as pd
|
|
44
|
+
from robot.api import logger
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# This class is structured as a dataclass for default field setup, but
|
|
48
|
+
# (also) provides utility methods for result recording and reporting.
|
|
49
|
+
@dataclass
|
|
50
|
+
class ValidatorResultRecorder:
|
|
51
|
+
"""
|
|
52
|
+
Collects and manages results from XML validation runs.
|
|
53
|
+
|
|
54
|
+
This class serves as an internal aggregator for storing validation
|
|
55
|
+
outcomes, including:
|
|
56
|
+
|
|
57
|
+
- A summary of valid and invalid XML files.
|
|
58
|
+
- Detailed validation errors grouped by file.
|
|
59
|
+
|
|
60
|
+
It supports logging of individual errors and summary statistics to
|
|
61
|
+
the Robot Framework log, as well as exporting all errors to a
|
|
62
|
+
CSV file.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
|
|
66
|
+
- errors_by_file (List[Dict[str, Any]]):
|
|
67
|
+
A list of validation error dictionaries, each tagged with its
|
|
68
|
+
corresponding file name.
|
|
69
|
+
|
|
70
|
+
- validation_summary (Dict[str, List[str]]):
|
|
71
|
+
A dictionary with two keys: 'valid' and 'invalid'. Each key maps
|
|
72
|
+
to a list of file names.
|
|
73
|
+
|
|
74
|
+
This class is used internally by XmlValidator to record, summarize,
|
|
75
|
+
and export validation results.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
__version__ = '1.0.0'
|
|
79
|
+
|
|
80
|
+
errors_by_file: List[Dict[str, Any]] = field(default_factory=list)
|
|
81
|
+
validation_summary: Dict[str, List[str]] = field(
|
|
82
|
+
default_factory=lambda: {"valid": [], "invalid": []}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _get_summary(self) -> Dict[str, int]:
|
|
86
|
+
"""
|
|
87
|
+
Constructs a summary of validation outcomes.
|
|
88
|
+
|
|
89
|
+
This internal method returns a dictionary containing the count
|
|
90
|
+
of validated, valid, and invalid files. It safely handles cases
|
|
91
|
+
where either category is missing from the summary.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
|
|
95
|
+
- Dict[str, int]:
|
|
96
|
+
{
|
|
97
|
+
"Total_files validated": int,
|
|
98
|
+
"Valid files": int,
|
|
99
|
+
"Invalid files": int
|
|
100
|
+
}
|
|
101
|
+
"""
|
|
102
|
+
valid_files = len(self.validation_summary.get("valid", []))
|
|
103
|
+
invalid_files = len(self.validation_summary.get("invalid", []))
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"Total_files validated": valid_files + invalid_files,
|
|
107
|
+
"Valid files": valid_files,
|
|
108
|
+
"Invalid files": invalid_files,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def add_file_errors(
|
|
112
|
+
self,
|
|
113
|
+
file_path: Path,
|
|
114
|
+
error_details: List[Dict[str, Any]] | Dict[str, Any] | None
|
|
115
|
+
) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Adds validation error(s) for a given XML file.
|
|
118
|
+
|
|
119
|
+
Accepts either a single error dictionary or a list of error
|
|
120
|
+
dictionaries and appends them to the `errors_by_file` attribute.
|
|
121
|
+
Each error entry is tagged with the file name.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
|
|
125
|
+
- file_path (Path):
|
|
126
|
+
The path of the XML file that caused the error(s).
|
|
127
|
+
|
|
128
|
+
- error_details (Dict or List[Dict] or None):
|
|
129
|
+
The validation error(s) to record. If a single dictionary is
|
|
130
|
+
provided, it is internally converted to a list.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
|
|
134
|
+
None
|
|
135
|
+
"""
|
|
136
|
+
if error_details:
|
|
137
|
+
# Normalize error_details to always be a list.
|
|
138
|
+
if isinstance(error_details, dict):
|
|
139
|
+
error_details = [error_details]
|
|
140
|
+
# Append each error to the errors_by_file list.
|
|
141
|
+
for error in error_details:
|
|
142
|
+
error_entry = {"file_name": file_path.name, **error}
|
|
143
|
+
self.errors_by_file.append(error_entry)
|
|
144
|
+
|
|
145
|
+
def add_invalid_file(
|
|
146
|
+
self,
|
|
147
|
+
file_path: Path
|
|
148
|
+
) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Records a file as invalid and logs the result.
|
|
151
|
+
|
|
152
|
+
Adds the file name to the `invalid` list within the
|
|
153
|
+
`validation_summary` attribute and emits a warning log
|
|
154
|
+
to Robot Framework output.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
|
|
158
|
+
- file_path (Path):
|
|
159
|
+
The path to the XML file that failed validation.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
|
|
163
|
+
None
|
|
164
|
+
"""
|
|
165
|
+
logger.warn("\tXML is invalid:")
|
|
166
|
+
self.validation_summary["invalid"].append(file_path.name)
|
|
167
|
+
|
|
168
|
+
def add_valid_file(
|
|
169
|
+
self,
|
|
170
|
+
file_path: Path
|
|
171
|
+
) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Records a file as valid and logs the result.
|
|
174
|
+
|
|
175
|
+
Adds the file name to the `valid` list within the
|
|
176
|
+
`validation_summary` attribute and emits a confirmation
|
|
177
|
+
log to the Robot Framework output.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
|
|
181
|
+
- file_path (Path):
|
|
182
|
+
The path to the XML file that passed validation.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
|
|
186
|
+
None
|
|
187
|
+
"""
|
|
188
|
+
logger.info("\tXML is valid!", also_console=True)
|
|
189
|
+
self.validation_summary["valid"].append(file_path.name)
|
|
190
|
+
|
|
191
|
+
def log_file_errors(
|
|
192
|
+
self,
|
|
193
|
+
errors: List[Dict[str, Any]]
|
|
194
|
+
) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Logs a list of validation errors to the Robot Framework log.
|
|
197
|
+
|
|
198
|
+
Each error dictionary is logged under a numbered header
|
|
199
|
+
(e.g., "Error #1") followed by its individual key-value
|
|
200
|
+
pairs.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
|
|
204
|
+
- errors (List[Dict[str, Any]]):
|
|
205
|
+
A list of dictionaries containing validation error details for
|
|
206
|
+
one or more XML files.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
|
|
210
|
+
None
|
|
211
|
+
"""
|
|
212
|
+
for idx, error in enumerate(errors):
|
|
213
|
+
logger.warn(f'\t\tError #{idx}:')
|
|
214
|
+
for key, value in error.items():
|
|
215
|
+
logger.warn(f"\t\t\t{key}: {value}")
|
|
216
|
+
|
|
217
|
+
def log_summary(self) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Logs a summary of validation results to the Robot Framework log.
|
|
220
|
+
|
|
221
|
+
This method retrieves the number of valid, invalid, and total
|
|
222
|
+
files from `_get_summary()` and prints them in a structured
|
|
223
|
+
format.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
|
|
227
|
+
None
|
|
228
|
+
"""
|
|
229
|
+
for category, value in self._get_summary().items():
|
|
230
|
+
logger.info(f"{category}: {value}.", also_console=True)
|
|
231
|
+
|
|
232
|
+
def reset(self) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Clears all stored validation results.
|
|
235
|
+
|
|
236
|
+
This method resets the internal state of the result recorder,
|
|
237
|
+
including:
|
|
238
|
+
|
|
239
|
+
- `errors_by_file`:
|
|
240
|
+
cleared
|
|
241
|
+
- `validation_summary`:
|
|
242
|
+
reset to default structure with empty 'valid' and 'invalid'
|
|
243
|
+
lists
|
|
244
|
+
|
|
245
|
+
Call this method before starting a new validation run if you
|
|
246
|
+
want to discard previous results.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
|
|
250
|
+
None
|
|
251
|
+
"""
|
|
252
|
+
self.errors_by_file.clear()
|
|
253
|
+
self.validation_summary = {"valid": [], "invalid": []}
|
|
254
|
+
|
|
255
|
+
def write_errors_to_csv(self,
|
|
256
|
+
errors: List[ Dict[str, Any] ],
|
|
257
|
+
output_path: Path,
|
|
258
|
+
include_timestamp: Optional[bool] = False,
|
|
259
|
+
file_name_column: Optional[str] = None
|
|
260
|
+
) -> str:
|
|
261
|
+
"""
|
|
262
|
+
Writes a list of validation errors to a CSV file.
|
|
263
|
+
|
|
264
|
+
This method takes a list of error dictionaries and writes them
|
|
265
|
+
to a CSV file at the specified output path. Optionally, it
|
|
266
|
+
appends a timestamp to the file name and reorders columns to
|
|
267
|
+
place a specific column (e.g., "file_name") first.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
|
|
271
|
+
- errors (List[Dict[str, Any]]):
|
|
272
|
+
A list of dictionaries, where each dictionary contains details
|
|
273
|
+
of a validation error. Each key in the dictionaries
|
|
274
|
+
corresponds to a column in the output CSV.
|
|
275
|
+
|
|
276
|
+
- output_path (Path):
|
|
277
|
+
The base path for the output CSV file. A timestamp will be
|
|
278
|
+
appended to the filename if `include_timestamp` is True.
|
|
279
|
+
|
|
280
|
+
- include_timestamp (bool, optional):
|
|
281
|
+
If True, appends a timestamp (in the format
|
|
282
|
+
`YYYY-MM-DD_HH-MM-SS`) to the output file name. Defaults to
|
|
283
|
+
False.
|
|
284
|
+
|
|
285
|
+
- file_name_column (str, optional):
|
|
286
|
+
The name of the column to be placed first in the CSV. If the
|
|
287
|
+
specified column is not present, the original column order is
|
|
288
|
+
preserved.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
|
|
292
|
+
- ValueError:
|
|
293
|
+
Raised if the `errors` list is empty or improperly formatted.
|
|
294
|
+
|
|
295
|
+
- IOError:
|
|
296
|
+
Raised if writing the CSV fails due to file system issues.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
|
|
300
|
+
- str:
|
|
301
|
+
The resolved path of the created CSV file.
|
|
302
|
+
|
|
303
|
+
Notes:
|
|
304
|
+
|
|
305
|
+
- If `errors` is an empty list, the method exits early and logs
|
|
306
|
+
an informational message without creating a file.
|
|
307
|
+
- Column reordering occurs only if `file_name_column` exists in
|
|
308
|
+
the error dictionaries.
|
|
309
|
+
- The method uses `pandas` for CSV generation.
|
|
310
|
+
"""
|
|
311
|
+
# Return if no errors were passed.
|
|
312
|
+
if not errors:
|
|
313
|
+
logger.info("No errors to write to CSV.")
|
|
314
|
+
return ''
|
|
315
|
+
# Generate a timestamp to be added to the filename.
|
|
316
|
+
timestamp = f'_{datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f")}' \
|
|
317
|
+
if include_timestamp else None
|
|
318
|
+
# Construct the output path.
|
|
319
|
+
output_csv_path = (
|
|
320
|
+
output_path.parent / f"errors{timestamp if timestamp else ''}.csv"
|
|
321
|
+
)
|
|
322
|
+
# Convert the errors list to a DataFrame.
|
|
323
|
+
df = pd.DataFrame(errors)
|
|
324
|
+
# Ensure the specified column is first, if provided.
|
|
325
|
+
if file_name_column and file_name_column in df.columns:
|
|
326
|
+
columns_order = [file_name_column] + [
|
|
327
|
+
col for col in df.columns if col != file_name_column
|
|
328
|
+
]
|
|
329
|
+
df = df[columns_order]
|
|
330
|
+
# Write the DataFrame to a CSV file.
|
|
331
|
+
try:
|
|
332
|
+
df.to_csv(output_csv_path, index=False)
|
|
333
|
+
logger.info(
|
|
334
|
+
f"Validation errors exported to '{output_csv_path}'.",
|
|
335
|
+
also_console=True
|
|
336
|
+
)
|
|
337
|
+
except IOError as e:
|
|
338
|
+
raise IOError(
|
|
339
|
+
f"Failed to write CSV file: {output_csv_path}."
|
|
340
|
+
) from e
|
|
341
|
+
return str( output_csv_path.resolve() )
|
|
342
|
+
|
|
343
|
+
class ValidatorResult: # pylint: disable=R0903:too-few-public-methods
|
|
344
|
+
"""
|
|
345
|
+
Encapsulates the result of an operation in a success-or-failure format.
|
|
346
|
+
|
|
347
|
+
`ValidatorResult` provides a structured way to handle the outcome of
|
|
348
|
+
operations throughout the XML validation library. It captures whether
|
|
349
|
+
an operation succeeded and includes either the result (`value`) or
|
|
350
|
+
the error (`error`) — but not both.
|
|
351
|
+
|
|
352
|
+
This pattern allows methods to return a single object regardless of
|
|
353
|
+
success or failure, simplifying error handling.
|
|
354
|
+
|
|
355
|
+
Attributes:
|
|
356
|
+
|
|
357
|
+
- success (bool):
|
|
358
|
+
True if the operation was successful; False otherwise.
|
|
359
|
+
|
|
360
|
+
- value (Any, optional):
|
|
361
|
+
The returned data from a successful operation.
|
|
362
|
+
|
|
363
|
+
- error (Any, optional):
|
|
364
|
+
Error information if the operation failed.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
__version__ = '0.0.1'
|
|
368
|
+
|
|
369
|
+
def __init__(
|
|
370
|
+
self,
|
|
371
|
+
success: bool,
|
|
372
|
+
value: Optional[Any] = None,
|
|
373
|
+
error: Optional[Any] = None
|
|
374
|
+
) -> None:
|
|
375
|
+
"""
|
|
376
|
+
Initializes a ValidatorResult instance.
|
|
377
|
+
|
|
378
|
+
Used to encapsulate the outcome of an operation, including
|
|
379
|
+
success state, result value, or error information.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
|
|
383
|
+
- success (bool):
|
|
384
|
+
Whether the operation was successful.
|
|
385
|
+
- value (Any, optional):
|
|
386
|
+
The result of the operation, if successful. Defaults to None.
|
|
387
|
+
- error (Any, optional):
|
|
388
|
+
Error details if the operation failed. Defaults to None.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
|
|
392
|
+
None
|
|
393
|
+
"""
|
|
394
|
+
self.success = success
|
|
395
|
+
self.value = value
|
|
396
|
+
self.error = error
|
|
397
|
+
|
|
398
|
+
def __repr__(self) -> str:
|
|
399
|
+
"""
|
|
400
|
+
Returns a string representation of the ValidatorResult instance.
|
|
401
|
+
|
|
402
|
+
If the result is successful, includes the value.
|
|
403
|
+
|
|
404
|
+
Otherwise, includes the error details.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
|
|
408
|
+
str
|
|
409
|
+
"""
|
|
410
|
+
if self.success: # pylint: disable=R1705:no-else-return
|
|
411
|
+
return f"Result(success=True, value={self.value})"
|
|
412
|
+
else:
|
|
413
|
+
return f"Result(success=False, error={self.error})"
|