Interfaces for dependencies and products

Different interfaces exist for dependencies and products, and it might not be obvious when to use what. This guide gives you an overview of the different strengths of each approach.

Legend

  • ✅ = True

  • ❌ = False

  • ➖ = Does not apply

Dependencies

In general, pytask regards everything as a task dependency if it is not marked as a product. Thus, you can also think of the following examples as how to inject values into a task. When we talk about products later, the same interfaces will be used.

def task(arg: ... = ...)

Annotated[..., value]

@task(kwargs=...)

Not deprecated

No type annotations required

Flexible choice of argument name

Supports third-party functions as tasks

Default argument

You can pass a value to a task as a default argument.

from pathlib import Path


def task_example(path: Path = Path("input.txt")) -> None: ...

Annotation with value

It is possible to include the value in the type annotation.

It is especially helpful if you pass a PNode to the task. If you passed a node as the default argument, type checkers like mypy would expect the node to enter the task, but the value injected into the task depends on the nodes load() method. For a PathNode

from pathlib import Path
from typing import Annotated

from pytask import PathNode


def task_example(path: Annotated[Path, PathNode(path=Path("input.txt"))]) -> None: ...

@task(kwargs=...)

You can use the kwargs argument of the @task decorator to pass a dictionary. It applies to dependencies and products alike.

from pathlib import Path

from pytask import task


@task(kwargs={"path": Path("input.txt")})
def task_example(path: Path) -> None: ...

Products

def task(arg: Annotated[..., Product] = ...)

Annotated[..., value, Product]

produces

@task(produces=...)

def task() -> Annotated[..., value]

Not deprecated

No type annotations required

Flexible choice of argument name

Supports third-party functions as tasks

Allows to pass custom node while preserving type of value

Product annotation

The syntax is the same as Default argument, but the Product annotation turns the argument into a task product.

from pathlib import Path
from typing import Annotated

from pytask import Product


def task_write_file(path: Annotated[Path, Product] = Path("file.txt")) -> None:
    path.touch()

Product annotation with value

The syntax is the same as Annotation with value, but the Product annotation turns the argument into a task product.

from pathlib import Path
from typing import Annotated

from pytask import PathNode
from pytask import Product


def task_write_file(
    path: Annotated[Path, PathNode(path=Path("file.txt")), Product],
) -> None:
    path.touch()

produces

Without using any type annotation, you can use produces as a magical argument name to treat every value passed to it as a task product.

from pathlib import Path


def task_write_file(produces: Path = Path("file.txt")) -> None:
    produces.touch()

Return annotation

You can also add a node or a value that will be parsed to a node to the annotation of the return type. It allows us to treat the returns of the task function as products.

from pathlib import Path
from typing import Annotated


def task_write_file() -> Annotated[str, Path("file.txt")]:
    return ""

@task(produces=...)

In situations where the task return is the product like Return annotation, but you cannot modify the type annotation of the return, use the argument produces of the @task decorator.

Pass the node or value you otherwise include in the type annotation to produces.

from pathlib import Path

from pytask import task


@task(produces=Path("file.txt"))
def task_write_file() -> str:
    return ""