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.

run_index
actual_data_file: str
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
origin_data_file: str
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
uuid: uuid.UUID
117    @property
118    def uuid(self) -> UUID:
119        if self._uuid is None:
120            self._uuid = uuid.uuid4()
121        return self._uuid
run_time: datetime.datetime
129    @property
130    def run_time(self) -> datetime:
131        return self._run_time
run_dir: str
133    @property
134    def run_dir(self) -> str:
135        return self._run_dir
by_line: bool
141    @property
142    def by_line(self) -> bool:
143        return self._by_line
source_mode_preceding: bool
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
data_file_path: str
151    @property
152    def data_file_path(self) -> str:
153        return os.path.join(self.instance_dir, "data.csv")
instance_dir: str
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
identity_or_index: str
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
paths_name: str
172    @property
173    def paths_name(self) -> str:  # pylint: disable=C0116
174        return self._paths_name
file_name: str
180    @property
181    def file_name(self) -> str:  # pylint: disable=C0116
182        return self._file_name
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
metadata: dict[str, typing.Any]
233    @property
234    def metadata(self) -> dict[str, Any]:  # pylint: disable=C0116
235        return self.csvpath.metadata  # pragma: no cover
variables: dict[str, typing.Any]
241    @property
242    def variables(self) -> dict[str, Any]:  # pylint: disable=C0116
243        return self.csvpath.variables  # pragma: no cover
all_variables: dict[str, typing.Any]
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)
errors: list[csvpath.managers.errors.error.Error]
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
errors_count: int
266    @property
267    def errors_count(self) -> int:  # pylint: disable=C0116
268        if self.errors:
269            return len(self.errors)
270        return 0
def has_errors(self) -> bool:
277    def has_errors(self) -> bool:
278        return self.errors_count > 0
printouts: dict[str, list[str]]
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
def get_printouts(self, name='default') -> dict[str, list[str]]:
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 []
def has_printouts(self) -> bool:
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