Source code for petab.petablint

#!/usr/bin/env python3

"""Command line tool to check for correct format"""

import argparse
import logging
import sys

import pydantic
from colorama import Fore
from colorama import init as init_colorama
from jsonschema.exceptions import ValidationError as SchemaValidationError

import petab.v1 as petab
from petab.v1 import validate_yaml_semantics, validate_yaml_syntax
from petab.v1.C import FORMAT_VERSION
from petab.versions import get_major_version

logger = logging.getLogger(__name__)


[docs] class LintFormatter(logging.Formatter): """Custom log formatter""" formats = { logging.DEBUG: Fore.CYAN + "%(message)s", logging.INFO: Fore.GREEN + "%(message)s", logging.WARN: Fore.YELLOW + "%(message)s", logging.ERROR: Fore.RED + "%(message)s", }
[docs] def format(self, record): # pylint: disable=protected-access format_orig = self._style._fmt self._style._fmt = LintFormatter.formats.get(record.levelno, self._fmt) result = logging.Formatter.format(self, record) self._style._fmt = format_orig return result
[docs] def parse_cli_args(): """Parse command line arguments""" parser = argparse.ArgumentParser( description="Check if a set of files adheres to the PEtab format." ) # General options: parser.add_argument( "-v", "--verbose", dest="verbose", action="store_true", help="More verbose output", ) # Call with set of files group = parser.add_argument_group( "Check individual files *DEPRECATED*. Please contact us via " "https://github.com/PEtab-dev/libpetab-python/issues, " "if you need this." ) group.add_argument( "-s", "--sbml", dest="sbml_file_name", help="SBML model filename" ) group.add_argument( "-o", "--observables", dest="observable_file_name", help="Observable table", ) group.add_argument( "-m", "--measurements", dest="measurement_file_name", help="Measurement table", ) group.add_argument( "-c", "--conditions", dest="condition_file_name", help="Conditions table", ) group.add_argument( "-p", "--parameters", dest="parameter_file_name", help="Parameter table", ) group.add_argument( "--vis", "--visualizations", dest="visualization_file_name", help="Visualization table", ) group = parser.add_mutually_exclusive_group() group.add_argument( "-y", "--yaml", dest="yaml_file_name_deprecated", help="PEtab YAML problem filename. " "*DEPRECATED* pass the file name as positional argument instead.", ) group.add_argument( dest="yaml_file_name", help="PEtab YAML problem filename", nargs="?", ) args = parser.parse_args() if any( ( args.sbml_file_name, args.condition_file_name, args.measurement_file_name, args.parameter_file_name, ) ): logger.warning( "Passing individual tables to petablint is deprecated, please " "provide a PEtab YAML file instead. " "Please contact us via " "https://github.com/PEtab-dev/libpetab-python/issues, " "if you need this." ) if args.yaml_file_name or args.yaml_file_name_deprecated: parser.error( "When providing a yaml file, no other files may be specified." ) if args.yaml_file_name_deprecated: logger.warning( "The -y/--yaml option is deprecated. " "Please provide the YAML file as a positional argument." ) if args.yaml_file_name: parser.error( "Please provide only one of --yaml or positional argument." ) args.yaml_file_name = args.yaml_file_name or args.yaml_file_name_deprecated return args
[docs] def main(): """Run PEtab validator""" init_colorama(autoreset=True) ch = logging.StreamHandler() ch.setFormatter(LintFormatter()) logging.basicConfig(level=logging.DEBUG, handlers=[ch]) args = parse_cli_args() if args.verbose: ch.setLevel(logging.DEBUG) else: ch.setLevel(logging.WARN) if args.yaml_file_name: try: validate_yaml_syntax(args.yaml_file_name) except SchemaValidationError as e: path = "" if e.absolute_path: # construct a path to the error location inside the YAML file path = list(e.absolute_path) path = ( f" at {path[0]}" + "".join(f"[{str(p)}]" for p in path[1:]) + ": " ) logger.error( "Provided YAML file does not adhere to the PEtab schema" f"{path}: {e.args[0]}" ) sys.exit(1) except ValueError as e: logger.error(e) sys.exit(1) match get_major_version(args.yaml_file_name): case 1: validate_yaml_semantics(args.yaml_file_name) if petab.is_composite_problem(args.yaml_file_name): # TODO: further checking: # https://github.com/ICB-DCM/PEtab/issues/191 # petab.CompositeProblem.from_yaml(args.yaml_file_name) return problem = petab.Problem.from_yaml(args.yaml_file_name) ret = petab.lint.lint_problem(problem) sys.exit(ret) case 2: from petab.v2.lint import lint_problem try: validation_issues = lint_problem(args.yaml_file_name) if validation_issues: # Handle petab.v2.lint.ValidationTask issues validation_issues.log(logger=logger) sys.exit(1) logger.info("PEtab format check completed successfully.") sys.exit(0) except pydantic.ValidationError as e: # Handle Pydantic validation errors for err in e.errors(): loc = ", ".join(str(loc) for loc in err["loc"]) msg = err["msg"] # TODO: include model info here once available # https://github.com/pydantic/pydantic/issues/7224 logger.error(f"Error in field(s) `{loc}`: {msg}") sys.exit(1) case _: logger.error( "The provided PEtab files are of unsupported version " f"or the `{FORMAT_VERSION}` field is missing in the yaml " "file." ) # DEPRECATED - only supported for v1 logger.debug("Looking for...") if args.sbml_file_name: logger.debug(f"\tSBML model: {args.sbml_file_name}") if args.condition_file_name: logger.debug(f"\tCondition table: {args.condition_file_name}") if args.observable_file_name: logger.debug(f"\tObservable table: {args.observable_file_name}") if args.measurement_file_name: logger.debug(f"\tMeasurement table: {args.measurement_file_name}") if args.parameter_file_name: logger.debug(f"\tParameter table: {args.parameter_file_name}") if args.visualization_file_name: logger.debug(f"\tVisualization table: {args.visualization_file_name}") try: problem = petab.Problem.from_files( sbml_file=args.sbml_file_name, condition_file=args.condition_file_name, measurement_file=args.measurement_file_name, parameter_file=args.parameter_file_name, observable_files=args.observable_file_name, visualization_files=args.visualization_file_name, ) except FileNotFoundError as e: logger.error(e) sys.exit(1) ret = petab.lint.lint_problem(problem) sys.exit(ret)
if __name__ == "__main__": main()