# Licensed under a 3-clause BSD style license - see PYFITS.rst import operator import warnings from textwrap import indent from astropy.utils.exceptions import AstropyUserWarning class VerifyError(Exception): """ Verify exception class. """ class VerifyWarning(AstropyUserWarning): """ Verify warning class. """ VERIFY_OPTIONS = [ "ignore", "warn", "exception", "fix", "silentfix", "fix+ignore", "fix+warn", "fix+exception", "silentfix+ignore", "silentfix+warn", "silentfix+exception", ] class _Verify: """ Shared methods for verification. """ def run_option( self, option="warn", err_text="", fix_text="Fixed.", fix=None, fixable=True ): """ Execute the verification with selected option. """ text = err_text if option in ["warn", "exception"]: fixable = False # fix the value elif not fixable: text = f"Unfixable error: {text}" else: if fix: fix() text += " " + fix_text return (fixable, text) def verify(self, option="warn"): """ Verify all values in the instance. Parameters ---------- option : str Output verification option. Must be one of ``"fix"``, ``"silentfix"``, ``"ignore"``, ``"warn"``, or ``"exception"``. May also be any combination of ``"fix"`` or ``"silentfix"`` with ``"+ignore"``, ``"+warn"``, or ``"+exception"`` (e.g. ``"fix+warn"``). See :ref:`astropy:verify` for more info. """ opt = option.lower() if opt not in VERIFY_OPTIONS: raise ValueError(f"Option {option!r} not recognized.") if opt == "ignore": return errs = self._verify(opt) # Break the verify option into separate options related to reporting of # errors, and fixing of fixable errors if "+" in opt: fix_opt, report_opt = opt.split("+") elif opt in ["fix", "silentfix"]: # The original default behavior for 'fix' and 'silentfix' was to # raise an exception for unfixable errors fix_opt, report_opt = opt, "exception" else: fix_opt, report_opt = None, opt if fix_opt == "silentfix" and report_opt == "ignore": # Fixable errors were fixed, but don't report anything return if fix_opt == "silentfix": # Don't print out fixable issues; the first element of each verify # item is a boolean indicating whether or not the issue was fixable line_filter = lambda x: not x[0] elif fix_opt == "fix" and report_opt == "ignore": # Don't print *unfixable* issues, but do print fixed issues; this # is probably not very useful but the option exists for # completeness line_filter = operator.itemgetter(0) else: line_filter = None unfixable = False messages = [] for fixable, message in errs.iter_lines(filter=line_filter): if fixable is not None: unfixable = not fixable messages.append(message) if messages: messages.insert(0, "Verification reported errors:") messages.append("Note: astropy.io.fits uses zero-based indexing.\n") if fix_opt == "silentfix" and not unfixable: return elif report_opt == "warn" or (fix_opt == "fix" and not unfixable): for line in messages: warnings.warn(line, VerifyWarning) else: raise VerifyError("\n" + "\n".join(messages)) class _ErrList(list): """ Verification errors list class. It has a nested list structure constructed by error messages generated by verifications at different class levels. """ def __init__(self, val=(), unit="Element"): super().__init__(val) self.unit = unit def __str__(self): return "\n".join(item[1] for item in self.iter_lines()) def iter_lines(self, filter=None, shift=0): """ Iterate the nested structure as a list of strings with appropriate indentations for each level of structure. """ element = 0 # go through the list twice, first time print out all top level # messages for item in self: if not isinstance(item, _ErrList): if filter is None or filter(item): yield item[0], indent(item[1], 4 * shift * " ") # second time go through the next level items, each of the next level # must present, even it has nothing. for item in self: if isinstance(item, _ErrList): next_lines = item.iter_lines(filter=filter, shift=shift + 1) try: first_line = next(next_lines) except StopIteration: first_line = None if first_line is not None: if self.unit: # This line is sort of a header for the next level in # the hierarchy yield None, indent(f"{self.unit} {element}:", 4 * shift * " ") yield first_line yield from next_lines element += 1