Skip to content

task

trestle.core.commands.task ¤

Trestle task command.

logger ¤

Classes¤

TaskCmd (CommandPlusDocs) ¤

Run arbitrary trestle tasks in a simple and extensible methodology.

Source code in trestle/core/commands/task.py
class TaskCmd(CommandPlusDocs):
    """Run arbitrary trestle tasks in a simple and extensible methodology."""

    name = 'task'

    def _init_arguments(self) -> None:
        self.add_argument(
            'task',
            nargs='?',
            type=str,
            help='The name of the task to be run, trestle task -l will list available tasks.'
        )
        self.add_argument('-l', '--list', action='store_true', help='List the available tasks')
        self.add_argument(
            '-c', '--config', type=pathlib.Path, help='Pass a customized configuration file specifically for a task'
        )
        self.add_argument('-i', '--info', action='store_true', help='Print information about a particular task.')

    def _run(self, args: argparse.Namespace) -> int:
        try:
            logger.debug('Entering trestle task.')
            log.set_log_level_from_args(args)
            # Initial logic for conflicting args
            if args.task and args.list:
                raise TrestleIncorrectArgsError('Task name or -l can be provided not both.')

            if not args.task and not args.list:
                raise TrestleIncorrectArgsError(
                    'Either a trestle task or "-l/--list" shoudl be passed as input arguments.'
                )

            # Ensure trestle directory (must be true)
            trestle_root = args.trestle_root  # trestle root is set via command line in args. Default is cwd.
            if not trestle_root or not file_utils.is_valid_project_root(args.trestle_root):
                raise TrestleError(f'Given directory: {trestle_root} is not a trestle project.')

            config_path = trestle_root / const.TRESTLE_CONFIG_DIR / const.TRESTLE_CONFIG_FILE

            if args.config:
                config_path = pathlib.Path(args.config)
            if not config_path.exists():
                raise TrestleError(f'Config file at {config_path} does not exist.')

            # permit ${name} in config definitions
            global_config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
            global_config.read_file(config_path.open('r', encoding=const.FILE_ENCODING))
            # run setup
            task_index = self._build_task_index()

            # Clean to run
            if args.list:
                self._list_tasks(task_index)
                return CmdReturnCodes.SUCCESS.value
            # run the task
            if args.task not in task_index.keys():
                raise TrestleIncorrectArgsError(f'Unknown trestle task: {args.task}')

            logger.debug(f'Loading task: {args.task}')
            section_label = 'task.' + args.task
            config_section: Optional[configparser.SectionProxy] = None
            if section_label in global_config.sections():
                config_section = global_config[section_label]
            else:
                logger.warning(
                    f'Config file was not configured with the appropriate section for the task: "[{section_label}]"'
                )

            task = task_index[args.task](config_section)
            if args.info:
                task.print_info()
                return CmdReturnCodes.SUCCESS.value

            simulate_result = task.simulate()
            if not (simulate_result == TaskOutcome.SIM_SUCCESS):
                raise TrestleError(f'Task {args.task} reported a {simulate_result}')

            actual_result = task.execute()
            if not (actual_result == TaskOutcome.SUCCESS):
                raise TrestleError(f'Task {args.task} reported a {actual_result}')

            logger.info(f'Task: {args.task} executed successfully.')
            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while executing Trestle task')

    def _build_task_index(self) -> Dict[str, Type[TaskBase]]:
        """Build an index of all classes in which are tasks and present as a dictionary."""
        task_index: Dict[str, Type[TaskBase]] = {}
        pkgpath = str(pathlib.Path(trestle.tasks.__file__).parent)
        for _, name, _ in pkgutil.iter_modules([pkgpath]):
            __import__(f'trestle.tasks.{name}')
            clsmembers = inspect.getmembers(sys.modules[f'trestle.tasks.{name}'], inspect.isclass)
            for candidate in clsmembers:
                if issubclass(candidate[1], TaskBase):
                    task_index[candidate[1].name] = candidate[1]
        return task_index

    def _list_tasks(self, task_index: Dict[str, Type[TaskBase]]) -> None:
        logger.info('Available tasks:')
        for key in task_index.keys():
            logger.info(f'    {key}')
name ¤

handler: python