csvpath.managers.results.result
1# pylint: disable=C0114 2import os 3from uuid import UUID 4import uuid 5from datetime import datetime 6from typing import Any 7from csvpath import CsvPath 8from csvpath.managers.errors.error import Error 9from csvpath.managers.errors.error_collector import ErrorCollector 10from csvpath.managers.listener import Listener 11from csvpath.managers.metadata import Metadata 12from csvpath.util.printer import Printer 13from csvpath.util.exceptions import CsvPathsException 14from csvpath.util.line_spooler import LineSpooler, CsvLineSpooler 15from .result_serializer import ResultSerializer 16from .readers.readers import ResultReadersFacade 17 18 19class Result(ErrorCollector, Printer, Listener): # pylint: disable=R0902 20 """This class handles the results for a single CsvPath in the 21 context of a CsvPaths run that may apply any number of CsvPath 22 instances against the same file. 23 """ 24 25 def __init__( 26 self, 27 *, 28 paths_name: str, 29 run_dir: str = None, 30 lines: list[list[Any]] = None, 31 csvpath: CsvPath = None, 32 file_name: str = None, 33 run_index: int = None, 34 run_time: datetime = None, 35 runtime_data: dict = None, 36 by_line: bool = False, 37 ): 38 """@private""" 39 ErrorCollector.__init__(self) 40 Printer.__init__(self) 41 Listener.__init__(self, csvpath.config if csvpath is not None else None) 42 self._csvpath = None 43 self._uuid = None 44 self._runtime_data = runtime_data 45 self._paths_name = paths_name 46 self._file_name = file_name 47 self._preceding = None 48 self._print_count = 0 49 self._last_line = None 50 self.run_index = f"{run_index}" 51 self._run_time = run_time 52 self._run_dir = run_dir 53 # 54 # actual_data_file is the file the scanner found that we actually iterated through. 55 # if we are source-mode preceding this may not be the named-file path, which is the 56 # origin data file. 57 # 58 self._actual_data_file = None 59 self._origin_data_file = None 60 self._by_line = by_line 61 # 62 # data_file_path is the path to data.csv of this result 63 # 64 self._data_file_path = None 65 # 66 # are set here: 67 # - error listener / error_collector 68 # - printer 69 # 70 self.csvpath = csvpath 71 # 72 # 73 # 74 if ( 75 csvpath 76 and csvpath.metadata is None 77 or csvpath.identity is None 78 or csvpath.identity == "" 79 ): 80 if csvpath.metadata is None: 81 raise CsvPathsException( 82 "Metadata cannot be None. Check order of operations." 83 ) 84 # 85 # "NAME" is the least favored identifier. if we parse metadata after setting this 86 # identity and the csvpath uses any of the other five identifiers it will take 87 # precedence over this index. if the csvpath uses NAME it will overwrite. 88 # 89 csvpath.metadata["NAME"] = self.run_index 90 # 91 # readers 92 # add these last so that we can be sure they have access to everything they need. 93 # primarily, run_dir, instance, and csvpath 94 # 95 self._errors: list[Error] = None 96 self._printouts: dict[str, list[str]] = None 97 self._unmatched: list[list[Any]] = None 98 self._lines: list[list[Any]] = None 99 self._readers_facade = ResultReadersFacade(self) 100 101 @property 102 def actual_data_file(self) -> str: 103 if self._actual_data_file is None: 104 if self.csvpath.scanner: 105 self._actual_data_file = self.csvpath.scanner.filename 106 return self._actual_data_file 107 108 @property 109 def origin_data_file(self) -> str: 110 if self._origin_data_file is None: 111 self._origin_data_file = self.csvpath.csvpaths.file_manager.get_named_file( 112 self.file_name 113 ) 114 return self._origin_data_file 115 116 @property 117 def uuid(self) -> UUID: 118 if self._uuid is None: 119 self._uuid = uuid.uuid4() 120 return self._uuid 121 122 @uuid.setter 123 def uuid(self, u: UUID) -> None: 124 if not isinstance(u, UUID): 125 raise ValueError("Uuid must be a UUID") 126 self._uuid = u 127 128 @property 129 def run_time(self) -> datetime: 130 return self._run_time 131 132 @property 133 def run_dir(self) -> str: 134 return self._run_dir 135 136 @run_dir.setter 137 def run_dir(self, d: str) -> None: 138 self._run_dir = d 139 140 @property 141 def by_line(self) -> bool: 142 return self._by_line 143 144 @property 145 def source_mode_preceding(self) -> bool: 146 if self._preceding is None: 147 self._preceding = self.csvpath.data_from_preceding 148 return self._preceding 149 150 @property 151 def data_file_path(self) -> str: 152 return os.path.join(self.instance_dir, "data.csv") 153 154 @property 155 def instance_dir(self) -> str: 156 # 157 # would we ever need self.csvpath before it is set? seems unlikely. 158 # 159 i_dir = ResultSerializer(self.csvpath.config.archive_path).get_instance_dir( 160 run_dir=self.run_dir, identity=self.identity_or_index 161 ) 162 return i_dir 163 164 @property 165 def identity_or_index(self) -> str: 166 s = self._csvpath.identity 167 if f"{s}".strip() == "": 168 s = self.run_index 169 return s 170 171 @property 172 def paths_name(self) -> str: # pylint: disable=C0116 173 return self._paths_name 174 175 @paths_name.setter 176 def paths_name(self, paths_name: str) -> None: 177 self._paths_name = paths_name # pragma: no cover 178 179 @property 180 def file_name(self) -> str: # pylint: disable=C0116 181 return self._file_name 182 183 @file_name.setter 184 def file_name(self, file_name: str) -> None: 185 self._file_name = file_name # pragma: no cover 186 187 @property 188 def is_valid(self) -> bool: # pylint: disable=C0116 189 # if the csvpath has not been run -- e.g. because it represents results that were 190 # saved to disk and reloaded -- it won't have a run started time. 191 if self._csvpath and self._csvpath.run_started_at is not None: 192 return self._csvpath.is_valid 193 elif self._runtime_data and "valid" in self._runtime_data: 194 return self._runtime_data["valid"] 195 return False 196 197 @property 198 def last_line(self): # pylint: disable=C0116 199 """@private""" 200 return self._last_line 201 202 # 203 # =============== LISTENING =============== 204 # 205 def metadata_update(self, mdata: Metadata) -> None: 206 """@private""" 207 if isinstance(mdata, Error): 208 self.collect_error(mdata) 209 210 # 211 # =============== CSVPATH ================= 212 # 213 214 @property 215 def csvpath(self) -> CsvPath: # pylint: disable=C0116 216 return self._csvpath 217 218 @csvpath.setter 219 def csvpath(self, path: CsvPath) -> None: 220 """@private""" 221 # during testing or for some other reason we may receive None 222 # let's assume the dev knows what they're doing and just go with it. 223 if path is not None: 224 path.error_manager.add_internal_listener(self) 225 path.add_printer(self) 226 self._csvpath = path 227 228 # 229 # =============== METADATA ================= 230 # 231 232 @property 233 def metadata(self) -> dict[str, Any]: # pylint: disable=C0116 234 return self.csvpath.metadata # pragma: no cover 235 236 # 237 # =============== VARIABLES ================= 238 # 239 240 @property 241 def variables(self) -> dict[str, Any]: # pylint: disable=C0116 242 return self.csvpath.variables # pragma: no cover 243 244 @property 245 def all_variables(self) -> dict[str, Any]: # pylint: disable=C0116 246 return self.csvpath.csvpaths.results_manager.get_variables(self.paths_name) 247 248 # 249 # =============== ERRORS ================= 250 # 251 252 @property 253 def errors(self) -> list[Error]: # pylint: disable=C0116 254 # 255 # if none, use reader 256 # 257 if self._errors is None: 258 self._errors = self._readers_facade.errors 259 return self._errors 260 261 @errors.setter 262 def errors(self, errors: list[Error]) -> None: 263 self._errors = errors 264 265 @property 266 def errors_count(self) -> int: # pylint: disable=C0116 267 if self.errors: 268 return len(self.errors) 269 return 0 270 271 def collect_error(self, error: Error) -> None: # pylint: disable=C0116 272 """@private""" 273 if self.errors is not None: 274 self.errors.append(error) 275 276 def has_errors(self) -> bool: 277 return self.errors_count > 0 278 279 # 280 # =============== PRINTOUTS ================= 281 # 282 283 @property 284 def printouts(self) -> dict[str, list[str]]: 285 # 286 # if none, use reader 287 # 288 if self._printouts is None: 289 self._printouts = self._readers_facade.printouts 290 return self._printouts 291 292 def get_printouts(self, name="default") -> dict[str, list[str]]: 293 if self.printouts and name in self.printouts: 294 return self.printouts[name] 295 return [] 296 297 def set_printouts(self, name: str, lines: list[str]) -> None: 298 """@private""" 299 self.printouts[name] = lines 300 301 def has_printouts(self) -> bool: # pylint: disable=C0116 302 if len(self.printouts) > 0: 303 for k, v in self.printouts.items(): 304 if len(v) > 0: 305 return True 306 return False 307 308 @property 309 def lines_printed(self) -> int: # pylint: disable=C0116 310 """@private""" 311 return self._print_count 312 313 def print(self, string: str) -> None: # pylint: disable=C0116 314 """@private""" 315 self.print_to("default", string) 316 317 def print_to(self, name: str, string: str) -> None: # pylint: disable=C0116 318 """@private""" 319 self._print_count += 1 320 if name not in self.printouts: 321 self.printouts[name] = [] 322 self.printouts[name].append(string) 323 self._last_line = string 324 325 def dump_printing(self) -> None: # pylint: disable=C0116 326 """@private""" 327 for k, v in self.printouts.items(): 328 for line in v: 329 print(f"{k}: {line}") 330 print("") 331 332 def print_statements_count(self) -> int: # pylint: disable=C0116 333 """@private""" 334 i = 0 335 for name in self.printouts: 336 i += len(self.printouts[name]) if self.printouts[name] else 0 337 return i 338 339 # 340 # =============== LINES ================= 341 # 342 343 @property 344 def lines(self) -> list[list[Any]]: 345 if self._lines is None: 346 # 347 # we can assume the caller wants a container for lines. in that case, 348 # we want them to have a container that serializes lines as they come in 349 # rather than waiting for them all to arrive before writing to disk. 350 # 351 # for today we'll just default to CsvLineSpooler, but assume we'll work 352 # in other options later. 353 # 354 self._lines = self._readers_facade.lines 355 return self._lines 356 357 @lines.setter 358 def lines(self, ls: list[list[Any]]) -> None: 359 """@private""" 360 if self._lines and isinstance(self._lines, LineSpooler): 361 self._lines.close() 362 self._lines = ls 363 364 def append(self, line: list[Any]) -> None: 365 """@private""" 366 self.lines.append(line) 367 368 def __len__(self) -> int: 369 if self.lines is not None: 370 if isinstance(self.lines, list): 371 return len(self.lines) 372 i = 0 373 for _ in self.lines.next(): 374 i += 1 375 return i 376 return None 377 378 # 379 # =============== UNMATCHED ================= 380 # 381 382 @property 383 def unmatched(self) -> list[list[Any]]: 384 if self._unmatched is None: 385 # 386 # we can assume the caller wants a container for lines. in that case, 387 # we want them to have a container that serializes lines as they come in 388 # rather than waiting for them all to arrive before writing to disk. 389 # 390 # for today we'll just default to CsvLineSpooler, but assume we'll work 391 # in other options later. 392 # 393 self._unmatched = self._readers_facade.unmatched 394 return self._unmatched 395 396 @unmatched.setter 397 def unmatched(self, lines: list[list[Any]]) -> None: 398 """@private""" 399 self._unmatched = lines 400 401 # 402 # =========================================== 403 # 404 405 def __str__(self) -> str: 406 lastline = 0 407 endline = -1 408 try: 409 # if we haven't started yet -- common situation -- we may blow up. 410 lastline = self.csvpath.line_monitor.physical_line_number 411 endline = self.csvpath.line_monitor.physical_end_line_number 412 except Exception: 413 pass 414 endline = endline + 1 415 return f"""Result 416 file:{self.csvpath.scanner.filename if self.csvpath.scanner else None}; 417 name of paths:{self.paths_name}; 418 name of file:{self.file_name}; 419 run results dir:{self.run_dir}; 420 valid:{self.csvpath.is_valid}; 421 stopped:{self.csvpath.stopped}; 422 last line processed:{lastline}; 423 total file lines:{endline}; 424 matches:{self.csvpath.match_count}; 425 lines matched:{len(self._lines) if self._lines and not isinstance(self._lines, LineSpooler) else -1}; 426 lines unmatched:{len(self.unmatched) if self.unmatched else 0}; 427 print statements:{self.print_statements_count()}; 428 errors:{len(self.errors) if self.errors else -1}"""
class
Result(csvpath.managers.errors.error_collector.ErrorCollector, csvpath.util.printer.Printer, csvpath.managers.listener.Listener):
20class Result(ErrorCollector, Printer, Listener): # pylint: disable=R0902 21 """This class handles the results for a single CsvPath in the 22 context of a CsvPaths run that may apply any number of CsvPath 23 instances against the same file. 24 """ 25 26 def __init__( 27 self, 28 *, 29 paths_name: str, 30 run_dir: str = None, 31 lines: list[list[Any]] = None, 32 csvpath: CsvPath = None, 33 file_name: str = None, 34 run_index: int = None, 35 run_time: datetime = None, 36 runtime_data: dict = None, 37 by_line: bool = False, 38 ): 39 """@private""" 40 ErrorCollector.__init__(self) 41 Printer.__init__(self) 42 Listener.__init__(self, csvpath.config if csvpath is not None else None) 43 self._csvpath = None 44 self._uuid = None 45 self._runtime_data = runtime_data 46 self._paths_name = paths_name 47 self._file_name = file_name 48 self._preceding = None 49 self._print_count = 0 50 self._last_line = None 51 self.run_index = f"{run_index}" 52 self._run_time = run_time 53 self._run_dir = run_dir 54 # 55 # actual_data_file is the file the scanner found that we actually iterated through. 56 # if we are source-mode preceding this may not be the named-file path, which is the 57 # origin data file. 58 # 59 self._actual_data_file = None 60 self._origin_data_file = None 61 self._by_line = by_line 62 # 63 # data_file_path is the path to data.csv of this result 64 # 65 self._data_file_path = None 66 # 67 # are set here: 68 # - error listener / error_collector 69 # - printer 70 # 71 self.csvpath = csvpath 72 # 73 # 74 # 75 if ( 76 csvpath 77 and csvpath.metadata is None 78 or csvpath.identity is None 79 or csvpath.identity == "" 80 ): 81 if csvpath.metadata is None: 82 raise CsvPathsException( 83 "Metadata cannot be None. Check order of operations." 84 ) 85 # 86 # "NAME" is the least favored identifier. if we parse metadata after setting this 87 # identity and the csvpath uses any of the other five identifiers it will take 88 # precedence over this index. if the csvpath uses NAME it will overwrite. 89 # 90 csvpath.metadata["NAME"] = self.run_index 91 # 92 # readers 93 # add these last so that we can be sure they have access to everything they need. 94 # primarily, run_dir, instance, and csvpath 95 # 96 self._errors: list[Error] = None 97 self._printouts: dict[str, list[str]] = None 98 self._unmatched: list[list[Any]] = None 99 self._lines: list[list[Any]] = None 100 self._readers_facade = ResultReadersFacade(self) 101 102 @property 103 def actual_data_file(self) -> str: 104 if self._actual_data_file is None: 105 if self.csvpath.scanner: 106 self._actual_data_file = self.csvpath.scanner.filename 107 return self._actual_data_file 108 109 @property 110 def origin_data_file(self) -> str: 111 if self._origin_data_file is None: 112 self._origin_data_file = self.csvpath.csvpaths.file_manager.get_named_file( 113 self.file_name 114 ) 115 return self._origin_data_file 116 117 @property 118 def uuid(self) -> UUID: 119 if self._uuid is None: 120 self._uuid = uuid.uuid4() 121 return self._uuid 122 123 @uuid.setter 124 def uuid(self, u: UUID) -> None: 125 if not isinstance(u, UUID): 126 raise ValueError("Uuid must be a UUID") 127 self._uuid = u 128 129 @property 130 def run_time(self) -> datetime: 131 return self._run_time 132 133 @property 134 def run_dir(self) -> str: 135 return self._run_dir 136 137 @run_dir.setter 138 def run_dir(self, d: str) -> None: 139 self._run_dir = d 140 141 @property 142 def by_line(self) -> bool: 143 return self._by_line 144 145 @property 146 def source_mode_preceding(self) -> bool: 147 if self._preceding is None: 148 self._preceding = self.csvpath.data_from_preceding 149 return self._preceding 150 151 @property 152 def data_file_path(self) -> str: 153 return os.path.join(self.instance_dir, "data.csv") 154 155 @property 156 def instance_dir(self) -> str: 157 # 158 # would we ever need self.csvpath before it is set? seems unlikely. 159 # 160 i_dir = ResultSerializer(self.csvpath.config.archive_path).get_instance_dir( 161 run_dir=self.run_dir, identity=self.identity_or_index 162 ) 163 return i_dir 164 165 @property 166 def identity_or_index(self) -> str: 167 s = self._csvpath.identity 168 if f"{s}".strip() == "": 169 s = self.run_index 170 return s 171 172 @property 173 def paths_name(self) -> str: # pylint: disable=C0116 174 return self._paths_name 175 176 @paths_name.setter 177 def paths_name(self, paths_name: str) -> None: 178 self._paths_name = paths_name # pragma: no cover 179 180 @property 181 def file_name(self) -> str: # pylint: disable=C0116 182 return self._file_name 183 184 @file_name.setter 185 def file_name(self, file_name: str) -> None: 186 self._file_name = file_name # pragma: no cover 187 188 @property 189 def is_valid(self) -> bool: # pylint: disable=C0116 190 # if the csvpath has not been run -- e.g. because it represents results that were 191 # saved to disk and reloaded -- it won't have a run started time. 192 if self._csvpath and self._csvpath.run_started_at is not None: 193 return self._csvpath.is_valid 194 elif self._runtime_data and "valid" in self._runtime_data: 195 return self._runtime_data["valid"] 196 return False 197 198 @property 199 def last_line(self): # pylint: disable=C0116 200 """@private""" 201 return self._last_line 202 203 # 204 # =============== LISTENING =============== 205 # 206 def metadata_update(self, mdata: Metadata) -> None: 207 """@private""" 208 if isinstance(mdata, Error): 209 self.collect_error(mdata) 210 211 # 212 # =============== CSVPATH ================= 213 # 214 215 @property 216 def csvpath(self) -> CsvPath: # pylint: disable=C0116 217 return self._csvpath 218 219 @csvpath.setter 220 def csvpath(self, path: CsvPath) -> None: 221 """@private""" 222 # during testing or for some other reason we may receive None 223 # let's assume the dev knows what they're doing and just go with it. 224 if path is not None: 225 path.error_manager.add_internal_listener(self) 226 path.add_printer(self) 227 self._csvpath = path 228 229 # 230 # =============== METADATA ================= 231 # 232 233 @property 234 def metadata(self) -> dict[str, Any]: # pylint: disable=C0116 235 return self.csvpath.metadata # pragma: no cover 236 237 # 238 # =============== VARIABLES ================= 239 # 240 241 @property 242 def variables(self) -> dict[str, Any]: # pylint: disable=C0116 243 return self.csvpath.variables # pragma: no cover 244 245 @property 246 def all_variables(self) -> dict[str, Any]: # pylint: disable=C0116 247 return self.csvpath.csvpaths.results_manager.get_variables(self.paths_name) 248 249 # 250 # =============== ERRORS ================= 251 # 252 253 @property 254 def errors(self) -> list[Error]: # pylint: disable=C0116 255 # 256 # if none, use reader 257 # 258 if self._errors is None: 259 self._errors = self._readers_facade.errors 260 return self._errors 261 262 @errors.setter 263 def errors(self, errors: list[Error]) -> None: 264 self._errors = errors 265 266 @property 267 def errors_count(self) -> int: # pylint: disable=C0116 268 if self.errors: 269 return len(self.errors) 270 return 0 271 272 def collect_error(self, error: Error) -> None: # pylint: disable=C0116 273 """@private""" 274 if self.errors is not None: 275 self.errors.append(error) 276 277 def has_errors(self) -> bool: 278 return self.errors_count > 0 279 280 # 281 # =============== PRINTOUTS ================= 282 # 283 284 @property 285 def printouts(self) -> dict[str, list[str]]: 286 # 287 # if none, use reader 288 # 289 if self._printouts is None: 290 self._printouts = self._readers_facade.printouts 291 return self._printouts 292 293 def get_printouts(self, name="default") -> dict[str, list[str]]: 294 if self.printouts and name in self.printouts: 295 return self.printouts[name] 296 return [] 297 298 def set_printouts(self, name: str, lines: list[str]) -> None: 299 """@private""" 300 self.printouts[name] = lines 301 302 def has_printouts(self) -> bool: # pylint: disable=C0116 303 if len(self.printouts) > 0: 304 for k, v in self.printouts.items(): 305 if len(v) > 0: 306 return True 307 return False 308 309 @property 310 def lines_printed(self) -> int: # pylint: disable=C0116 311 """@private""" 312 return self._print_count 313 314 def print(self, string: str) -> None: # pylint: disable=C0116 315 """@private""" 316 self.print_to("default", string) 317 318 def print_to(self, name: str, string: str) -> None: # pylint: disable=C0116 319 """@private""" 320 self._print_count += 1 321 if name not in self.printouts: 322 self.printouts[name] = [] 323 self.printouts[name].append(string) 324 self._last_line = string 325 326 def dump_printing(self) -> None: # pylint: disable=C0116 327 """@private""" 328 for k, v in self.printouts.items(): 329 for line in v: 330 print(f"{k}: {line}") 331 print("") 332 333 def print_statements_count(self) -> int: # pylint: disable=C0116 334 """@private""" 335 i = 0 336 for name in self.printouts: 337 i += len(self.printouts[name]) if self.printouts[name] else 0 338 return i 339 340 # 341 # =============== LINES ================= 342 # 343 344 @property 345 def lines(self) -> list[list[Any]]: 346 if self._lines is None: 347 # 348 # we can assume the caller wants a container for lines. in that case, 349 # we want them to have a container that serializes lines as they come in 350 # rather than waiting for them all to arrive before writing to disk. 351 # 352 # for today we'll just default to CsvLineSpooler, but assume we'll work 353 # in other options later. 354 # 355 self._lines = self._readers_facade.lines 356 return self._lines 357 358 @lines.setter 359 def lines(self, ls: list[list[Any]]) -> None: 360 """@private""" 361 if self._lines and isinstance(self._lines, LineSpooler): 362 self._lines.close() 363 self._lines = ls 364 365 def append(self, line: list[Any]) -> None: 366 """@private""" 367 self.lines.append(line) 368 369 def __len__(self) -> int: 370 if self.lines is not None: 371 if isinstance(self.lines, list): 372 return len(self.lines) 373 i = 0 374 for _ in self.lines.next(): 375 i += 1 376 return i 377 return None 378 379 # 380 # =============== UNMATCHED ================= 381 # 382 383 @property 384 def unmatched(self) -> list[list[Any]]: 385 if self._unmatched is None: 386 # 387 # we can assume the caller wants a container for lines. in that case, 388 # we want them to have a container that serializes lines as they come in 389 # rather than waiting for them all to arrive before writing to disk. 390 # 391 # for today we'll just default to CsvLineSpooler, but assume we'll work 392 # in other options later. 393 # 394 self._unmatched = self._readers_facade.unmatched 395 return self._unmatched 396 397 @unmatched.setter 398 def unmatched(self, lines: list[list[Any]]) -> None: 399 """@private""" 400 self._unmatched = lines 401 402 # 403 # =========================================== 404 # 405 406 def __str__(self) -> str: 407 lastline = 0 408 endline = -1 409 try: 410 # if we haven't started yet -- common situation -- we may blow up. 411 lastline = self.csvpath.line_monitor.physical_line_number 412 endline = self.csvpath.line_monitor.physical_end_line_number 413 except Exception: 414 pass 415 endline = endline + 1 416 return f"""Result 417 file:{self.csvpath.scanner.filename if self.csvpath.scanner else None}; 418 name of paths:{self.paths_name}; 419 name of file:{self.file_name}; 420 run results dir:{self.run_dir}; 421 valid:{self.csvpath.is_valid}; 422 stopped:{self.csvpath.stopped}; 423 last line processed:{lastline}; 424 total file lines:{endline}; 425 matches:{self.csvpath.match_count}; 426 lines matched:{len(self._lines) if self._lines and not isinstance(self._lines, LineSpooler) else -1}; 427 lines unmatched:{len(self.unmatched) if self.unmatched else 0}; 428 print statements:{self.print_statements_count()}; 429 errors:{len(self.errors) if self.errors else -1}"""
This class handles the results for a single CsvPath in the context of a CsvPaths run that may apply any number of CsvPath instances against the same file.
is_valid: bool
188 @property 189 def is_valid(self) -> bool: # pylint: disable=C0116 190 # if the csvpath has not been run -- e.g. because it represents results that were 191 # saved to disk and reloaded -- it won't have a run started time. 192 if self._csvpath and self._csvpath.run_started_at is not None: 193 return self._csvpath.is_valid 194 elif self._runtime_data and "valid" in self._runtime_data: 195 return self._runtime_data["valid"] 196 return False