Example #1
0
def pytask_execute_task_setup(task: Task) -> None:
    """Take a short-cut for skipped tasks during setup with an exception."""
    is_unchanged = has_mark(task, "skip_unchanged")
    if is_unchanged:
        raise SkippedUnchanged

    ancestor_failed_marks = get_marks(task, "skip_ancestor_failed")
    if ancestor_failed_marks:
        message = "\n".join(
            skip_ancestor_failed(*mark.args, **mark.kwargs)
            for mark in ancestor_failed_marks)
        raise SkippedAncestorFailed(message)

    is_skipped = has_mark(task, "skip")
    if is_skipped:
        raise Skipped

    skipif_marks = get_marks(task, "skipif")
    if skipif_marks:
        marker_args = [
            skipif(*mark.args, **mark.kwargs) for mark in skipif_marks
        ]
        message = "\n".join(arg[1] for arg in marker_args if arg[0])
        should_skip = any(arg[0] for arg in marker_args)
        if should_skip:
            raise Skipped(message)
Example #2
0
def pytask_collect_task(
    session: Session, path: Path, name: str, obj: Any
) -> Task | None:
    """Collect a task which is a function.

    There is some discussion on how to detect functions in this `thread
    <https://stackoverflow.com/q/624926/7523785>`_. :class:`types.FunctionType` does not
    detect built-ins which is not possible anyway.

    """
    if (name.startswith("task_") or has_mark(obj, "task")) and callable(obj):
        dependencies = parse_nodes(session, path, name, obj, depends_on)
        products = parse_nodes(session, path, name, obj, produces)

        markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else []
        kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}

        # Get the underlying function to avoid having different states of the function,
        # e.g. due to pytask_meta, in different layers of the wrapping.
        unwrapped = inspect.unwrap(obj)

        return Task(
            base_name=name,
            path=path,
            function=unwrapped,
            depends_on=dependencies,
            produces=products,
            markers=markers,
            kwargs=kwargs,
        )
    else:
        return None
Example #3
0
def _extract_priorities_from_tasks(tasks: list[Task]) -> dict[str, int]:
    """Extract priorities from tasks.

    Priorities are set via the ``pytask.mark.try_first`` and ``pytask.mark.try_last``
    markers. We recode these markers to numeric values to sort all available by
    priorities. ``try_first`` is assigned the highest value such that it has the
    rightmost position in the list. Then, we can simply call :meth:`list.pop` on the
    list which is far more efficient than ``list.pop(0)``.

    """
    priorities = {
        task.name: {
            "try_first": has_mark(task, "try_first"),
            "try_last": has_mark(task, "try_last"),
        }
        for task in tasks
    }
    tasks_w_mixed_priorities = [
        name for name, p in priorities.items()
        if p["try_first"] and p["try_last"]
    ]

    if tasks_w_mixed_priorities:
        name_to_task = {task.name: task for task in tasks}
        reduced_names = [
            name_to_task[name].short_name for name in tasks_w_mixed_priorities
        ]
        text = format_strings_as_flat_tree(reduced_names,
                                           "Tasks with mixed priorities",
                                           TASK_ICON)
        raise ValueError(
            "'try_first' and 'try_last' cannot be applied on the same task. See the "
            f"following tasks for errors:\n\n{text}")

    # Recode to numeric values for sorting.
    numeric_mapping = {(True, False): 1, (False, False): 0, (False, True): -1}
    numeric_priorities = {
        name: numeric_mapping[(p["try_first"], p["try_last"])]
        for name, p in priorities.items()
    }

    return numeric_priorities
def _check_if_task_is_skipped(task_name: str, dag: nx.DiGraph) -> bool:
    task = dag.nodes[task_name]["task"]
    is_skipped = has_mark(task, "skip")

    if is_skipped:
        return True

    skip_if_markers = get_marks(task, "skipif")
    is_any_true = any(
        _skipif(*marker.args, **marker.kwargs)[0]
        for marker in skip_if_markers)
    return is_any_true
Example #5
0
def pytask_collect_file(
    session: Session, path: Path, reports: list[CollectionReport]
) -> list[CollectionReport] | None:
    """Collect a file."""
    if any(path.match(pattern) for pattern in session.config["task_files"]):
        spec = importlib_util.spec_from_file_location(path.stem, str(path))

        if spec is None:
            raise ImportError(f"Can't find module {path.stem!r} at location {path}.")

        mod = importlib_util.module_from_spec(spec)
        spec.loader.exec_module(mod)

        collected_reports = []
        for name, obj in inspect.getmembers(mod):
            # Ensures that tasks with this decorator are only collected once.
            if has_mark(obj, "task"):
                continue

            if has_mark(obj, "parametrize"):
                names_and_objects = session.hook.pytask_parametrize_task(
                    session=session, name=name, obj=obj
                )
            else:
                names_and_objects = [(name, obj)]

            for name_, obj_ in names_and_objects:
                report = session.hook.pytask_collect_task_protocol(
                    session=session, reports=reports, path=path, name=name_, obj=obj_
                )
                if report is not None:
                    collected_reports.append(report)

        return collected_reports
    else:
        return None
Example #6
0
def pytask_execute_task_setup(session: Session, task: Task) -> None:
    """Exit persisting tasks early.

    The decorator needs to be set and all nodes need to exist.

    """
    if has_mark(task, "persist"):
        try:
            for name in node_and_neighbors(session.dag, task.name):
                node = (session.dag.nodes[name].get("task")
                        or session.dag.nodes[name]["node"])
                node.state()
        except NodeNotFoundError:
            all_nodes_exist = False
        else:
            all_nodes_exist = True

        if all_nodes_exist:
            raise Persisted
Example #7
0
def pytask_collect_file(
        session: Session, path: Path,
        reports: list[CollectionReport]) -> list[CollectionReport] | None:
    """Collect a file."""
    if (any(path.match(pattern) for pattern in session.config["task_files"])
            and COLLECTED_TASKS[path]):
        # Remove tasks from the global to avoid re-collection if programmatic interface
        # is used.
        tasks = COLLECTED_TASKS.pop(path)

        name_to_function = parse_collected_tasks_with_task_marker(tasks)

        collected_reports = []
        for name, function in name_to_function.items():
            session.hook.pytask_parametrize_kwarg_to_marker(
                obj=function,
                kwargs=function.pytask_meta.
                kwargs,  # type: ignore[attr-defined]
            )

            if has_mark(function, "parametrize"):
                names_and_objects = session.hook.pytask_parametrize_task(
                    session=session, name=name, obj=function)
            else:
                names_and_objects = [(name, function)]

            for name_, obj_ in names_and_objects:
                report = session.hook.pytask_collect_task_protocol(
                    session=session,
                    reports=reports,
                    path=path,
                    name=name_,
                    obj=obj_)
                if report is not None:
                    collected_reports.append(report)

        return collected_reports
    else:
        return None