def do_continue(self, arg): # type: ignore ret = super().do_continue(arg) if cls._recursive_debug == 0: assert cls._config is not None console.print() capman = self._pytask_capman capturing = PytaskPDB._is_capturing(capman) if capturing: console.rule( "PDB continue (IO-capturing resumed)", characters=">", style=None, ) assert capman is not None capman.resume() else: console.rule("PDB continue", characters=">", style=None) if not self._pytask_live_manager.is_started: self._pytask_live_manager.resume() assert cls._pluginmanager is not None self._continued = True return ret
def wrapper(*args: Any, **kwargs: Any) -> None: capman = session.config["pm"].get_plugin("capturemanager") live_manager = session.config["pm"].get_plugin("live_manager") # Order is important! Pausing the live object before the capturemanager would # flush the table to stdout and it will be visible in the captured output. capman.suspend(in_=True) out, err = capman.read() live_manager.stop() if out or err: console.print() if out: console.rule("Captured stdout", style=None) console.print(out) if err: console.rule("Captured stderr", style=None) console.print(err) _pdb.runcall(task_function, *args, **kwargs) live_manager.resume() capman.resume()
def _print_errored_task_report(session: Session, report: ExecutionReport) -> None: """Print the traceback and the exception of an errored report.""" task_name = format_task_id( task=report.task, editor_url_scheme=session.config["editor_url_scheme"], short_name=True, ) text = Text.assemble("Task ", task_name, " failed", style="failed") console.rule(text, style=report.outcome.style) console.print() if report.exc_info and isinstance(report.exc_info[1], Exit): console.print(format_exception_without_traceback(report.exc_info)) else: console.print( render_exc_info(*report.exc_info, session.config["show_locals"])) console.print() show_capture = session.config["show_capture"] for when, key, content in report.sections: if key in ("stdout", "stderr") and show_capture in ( ShowCapture[key.upper()], ShowCapture.ALL, ): console.rule(f"Captured {key} during {when}", style=None) console.print(content)
def pytask_collect_log( session: Session, reports: list[CollectionReport], tasks: list[Task] ) -> None: """Log collection.""" session.collection_end = time.time() console.print(f"Collected {len(tasks)} task{'' if len(tasks) == 1 else 's'}.") failed_reports = [r for r in reports if r.outcome == CollectionOutcome.FAIL] if failed_reports: counts = count_outcomes(reports, CollectionOutcome) console.print() console.rule( Text("Failures during collection", style=CollectionOutcome.FAIL.style), style=CollectionOutcome.FAIL.style, ) for report in failed_reports: if report.node is None: header = "Error" else: if isinstance(report.node, Task): short_name = format_task_id( report.node, editor_url_scheme="no_link", short_name=True ) else: short_name = reduce_node_name(report.node, session.config["paths"]) header = f"Could not collect {short_name}" console.rule( Text(header, style=CollectionOutcome.FAIL.style), style=CollectionOutcome.FAIL.style, ) console.print() console.print( render_exc_info(*report.exc_info, session.config["show_locals"]) ) console.print() panel = create_summary_panel( counts, CollectionOutcome, "Collected errors and tasks" ) console.print(panel) session.hook.pytask_log_session_footer( session=session, duration=session.collection_end - session.collection_start, outcome=CollectionOutcome.FAIL if counts[CollectionOutcome.FAIL] else CollectionOutcome.SUCCESS, ) raise CollectionError
def pytask_log_session_footer( duration: float, outcome: CollectionOutcome | TaskOutcome, ) -> None: """Format the footer of the log message.""" formatted_duration = _format_duration(duration) message = Text(f"{outcome.description} in {formatted_duration}", style=outcome.style) console.rule(message, style=outcome.style)
def collect(**config_from_cli: Any | None) -> NoReturn: """Collect tasks and report information about them.""" config_from_cli["command"] = "collect" try: # Duplication of the same mechanism in :func:`pytask.main.main`. pm = get_plugin_manager() from _pytask import cli pm.register(cli) pm.hook.pytask_add_hooks(pm=pm) config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli) session = Session.from_config(config) except (ConfigurationError, Exception): session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED console.print_exception() else: try: session.hook.pytask_log_session_header(session=session) session.hook.pytask_collect(session=session) session.hook.pytask_resolve_dependencies(session=session) tasks = _select_tasks_by_expressions_and_marker(session) common_ancestor = _find_common_ancestor_of_all_nodes( tasks, session.config["paths"], session.config["nodes"]) dictionary = _organize_tasks(tasks) if dictionary: _print_collected_tasks( dictionary, session.config["nodes"], session.config["editor_url_scheme"], common_ancestor, ) console.print() console.rule(style="neutral") except CollectionError: session.exit_code = ExitCode.COLLECTION_FAILED except ResolvingDependenciesError: session.exit_code = ExitCode.RESOLVING_DEPENDENCIES_FAILED except Exception: session.exit_code = ExitCode.FAILED console.print_exception() console.rule(style="failed") sys.exit(session.exit_code)
def profile(**config_from_cli: Any) -> NoReturn: """Show information about tasks like runtime and memory consumption of products.""" config_from_cli["command"] = "profile" try: # Duplication of the same mechanism in :func:`pytask.main.main`. pm = get_plugin_manager() from _pytask import cli pm.register(cli) pm.hook.pytask_add_hooks(pm=pm) config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli) session = Session.from_config(config) except (ConfigurationError, Exception): # pragma: no cover session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED exc_info: tuple[ type[BaseException], BaseException, TracebackType | None ] = sys.exc_info() console.print(render_exc_info(*exc_info, show_locals=config["show_locals"])) else: try: session.hook.pytask_log_session_header(session=session) session.hook.pytask_collect(session=session) session.hook.pytask_resolve_dependencies(session=session) profile: dict[str, dict[str, Any]] = { task.name: {} for task in session.tasks } session.hook.pytask_profile_add_info_on_task( session=session, tasks=session.tasks, profile=profile ) profile = _process_profile(profile) _print_profile_table(profile, session.tasks, session.config) session.hook.pytask_profile_export_profile(session=session, profile=profile) console.rule(style="neutral") except CollectionError: # pragma: no cover session.exit_code = ExitCode.COLLECTION_FAILED except Exception: # pragma: no cover session.exit_code = ExitCode.FAILED console.print_exception() console.rule(style="failed") sys.exit(session.exit_code)
def pytask_log_session_header(session: Session) -> None: """Log the header of a pytask session.""" console.rule("Start pytask session", style=None) console.print( f"Platform: {sys.platform} -- Python {platform.python_version()}, " f"pytask {_pytask.__version__}, pluggy {pluggy.__version__}") console.print(f"Root: {session.config['root']}") if session.config["config"] is not None: console.print(f"Configuration: {session.config['config']}") plugin_info = session.config["pm"].list_plugin_distinfo() if plugin_info: formatted_plugins_w_versions = ", ".join( _format_plugin_names_and_versions(plugin_info)) console.print(f"Plugins: {formatted_plugins_w_versions}")
def pytask_resolve_dependencies_log( session: Session, report: ResolvingDependenciesReport) -> None: """Log errors which happened while resolving dependencies.""" console.print() console.rule( Text("Failures during resolving dependencies", style="failed"), style="failed", ) console.print() console.print( render_exc_info(*report.exc_info, session.config["show_locals"])) console.print() console.rule(style="failed")
def dag(**config_from_cli: Any) -> NoReturn: """Create a visualization of the project's directed acyclic graph.""" try: pm = get_plugin_manager() from _pytask import cli pm.register(cli) pm.hook.pytask_add_hooks(pm=pm) config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli) session = Session.from_config(config) except (ConfigurationError, Exception): console.print_exception() session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED else: try: session.hook.pytask_log_session_header(session=session) import_optional_dependency("pydot") check_for_optional_program( session.config["layout"], extra="The layout program is part of the graphviz package which you " "can install with conda.", ) session.hook.pytask_collect(session=session) session.hook.pytask_resolve_dependencies(session=session) dag = _refine_dag(session) _write_graph(dag, session.config["output_path"], session.config["layout"]) except CollectionError: session.exit_code = ExitCode.COLLECTION_FAILED except ResolvingDependenciesError: session.exit_code = ExitCode.RESOLVING_DEPENDENCIES_FAILED except Exception: session.exit_code = ExitCode.FAILED exc_info = remove_internal_traceback_frames_from_exc_info(sys.exc_info()) console.print() console.print(Traceback.from_exception(*exc_info)) console.rule(style="failed") sys.exit(session.exit_code)
def _create_session(config_from_cli: dict[str, Any]) -> nx.DiGraph: """Create a session object.""" try: pm = get_plugin_manager() from _pytask import cli pm.register(cli) pm.hook.pytask_add_hooks(pm=pm) config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli) session = Session.from_config(config) except (ConfigurationError, Exception): console.print_exception() session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED else: try: session.hook.pytask_log_session_header(session=session) import_optional_dependency("pydot") check_for_optional_program(session.config["layout"]) session.hook.pytask_collect(session=session) session.hook.pytask_resolve_dependencies(session=session) except CollectionError: session.exit_code = ExitCode.COLLECTION_FAILED except ResolvingDependenciesError: session.exit_code = ExitCode.RESOLVING_DEPENDENCIES_FAILED except Exception: session.exit_code = ExitCode.FAILED console.print_exception() console.rule(style="failed") return session
def pytask_execute_log_end(session: Session, reports: list[ExecutionReport]) -> bool: """Log information on the execution.""" session.execution_end = time.time() counts = count_outcomes(reports, TaskOutcome) if session.config["show_traceback"]: console.print() if counts[TaskOutcome.FAIL]: console.rule( Text("Failures", style=TaskOutcome.FAIL.style), style=TaskOutcome.FAIL.style, ) console.print() for report in reports: if report.outcome in (TaskOutcome.FAIL, TaskOutcome.SKIP_PREVIOUS_FAILED): _print_errored_task_report(session, report) console.rule(style="dim") panel = create_summary_panel(counts, TaskOutcome, "Collected tasks") console.print(panel) session.hook.pytask_log_session_footer( session=session, duration=session.execution_end - session.execution_start, outcome=TaskOutcome.FAIL if counts[TaskOutcome.FAIL] else TaskOutcome.SUCCESS, ) if counts[TaskOutcome.FAIL]: raise ExecutionError return True
def wrapper(*args: Any, **kwargs: Any) -> None: capman = session.config["pm"].get_plugin("capturemanager") live_manager = session.config["pm"].get_plugin("live_manager") try: task_function(*args, **kwargs) except Exception: # Order is important! Pausing the live object before the capturemanager # would flush the table to stdout and it will be visible in the captured # output. capman.suspend(in_=True) out, err = capman.read() live_manager.pause() if out or err: console.print() if out: console.rule("Captured stdout", style=None) console.print(out) if err: console.rule("Captured stderr", style=None) console.print(err) exc_info = remove_internal_traceback_frames_from_exc_info( sys.exc_info()) console.print() console.rule("Traceback", characters=">", style=None) console.print( render_exc_info(*exc_info, session.config["show_locals"])) post_mortem(exc_info[2]) live_manager.resume() capman.resume() raise
def _init_pdb(cls, method: str, *args: Any, **kwargs: Any) -> pdb.Pdb: # noqa: U100 """Initialize PDB debugging, dropping any IO capturing.""" if cls._pluginmanager is None: capman = None live_manager = None else: capman = cls._pluginmanager.get_plugin("capturemanager") live_manager = cls._pluginmanager.get_plugin("live_manager") if capman: capman.suspend(in_=True) if live_manager: live_manager.pause() if cls._config: console.print() if cls._recursive_debug == 0: # Handle header similar to pdb.set_trace in py37+. header = kwargs.pop("header", None) if header is not None: console.rule(header, characters=">", style=None) else: capturing = cls._is_capturing(capman) if capturing: console.rule( f"PDB {method} (IO-capturing turned off)", characters=">", style=None, ) else: console.rule(f"PDB {method}", characters=">", style=None) _pdb = cls._import_pdb_cls(capman, live_manager)(**kwargs) return _pdb
def clean(**config_from_cli: Any) -> NoReturn: """Clean the provided paths by removing files unknown to pytask.""" config_from_cli["command"] = "clean" try: # Duplication of the same mechanism in :func:`pytask.main.main`. pm = get_plugin_manager() from _pytask import cli pm.register(cli) pm.hook.pytask_add_hooks(pm=pm) config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli) session = Session.from_config(config) except Exception: session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED exc_info: tuple[type[BaseException], BaseException, TracebackType | None] = sys.exc_info() console.print(render_exc_info(*exc_info)) else: try: session.hook.pytask_log_session_header(session=session) session.hook.pytask_collect(session=session) known_paths = _collect_all_paths_known_to_pytask(session) exclude = session.config["exclude"] include_directories = session.config["directories"] unknown_paths = _find_all_unknown_paths(session, known_paths, exclude, include_directories) common_ancestor = find_common_ancestor(*unknown_paths, *session.config["paths"]) if unknown_paths: targets = "Files" if session.config["directories"]: targets += " and directories" console.print(f"\n{targets} which can be removed:\n") for path in unknown_paths: short_path = relative_to(path, common_ancestor) if session.config["mode"] == "dry-run": console.print(f"Would remove {short_path}") else: should_be_deleted = session.config[ "mode"] == "force" or click.confirm( f"Would you like to remove {short_path}?") if should_be_deleted: if not session.config["quiet"]: console.print(f"Remove {short_path}") if path.is_dir(): shutil.rmtree(path) else: path.unlink() else: console.print() console.print( "There are no files and directories which can be deleted.") console.print() console.rule(style=None) except CollectionError: session.exit_code = ExitCode.COLLECTION_FAILED console.rule(style="failed") except Exception: exc_info = sys.exc_info() console.print( render_exc_info(*exc_info, show_locals=config["show_locals"])) console.rule(style="failed") session.exit_code = ExitCode.FAILED sys.exit(session.exit_code)