diff --git a/docs/source/changes.md b/docs/source/changes.md index 3f6a68716..08604c9ba 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -26,6 +26,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and - {pull}`404` allows to use function returns to define task products. - {pull}`405` allows to match function returns to node annotations with prefix trees. - {pull}`406` removes `.value` from `Node` protocol. +- {pull}`407` make `.from_annot` an optional feature of nodes. ## 0.3.2 - 2023-06-07 diff --git a/src/_pytask/collect_utils.py b/src/_pytask/collect_utils.py index 7d9e7e20a..d10549dc3 100644 --- a/src/_pytask/collect_utils.py +++ b/src/_pytask/collect_utils.py @@ -601,5 +601,11 @@ def _evolve_instance(x: Any, instance_from_annot: Node | None) -> Any: if not instance_from_annot: return x - instance_from_annot.from_annot(x) + if not hasattr(instance_from_annot, "from_annot"): + raise AttributeError( + f"The node {instance_from_annot!r} does not define '.from_annot' which is " + f"necessary to complete the node with the value {x!r}." + ) + + instance_from_annot.from_annot(x) # type: ignore[attr-defined] return instance_from_annot diff --git a/src/_pytask/node_protocols.py b/src/_pytask/node_protocols.py index 39e1afb54..b8660a874 100644 --- a/src/_pytask/node_protocols.py +++ b/src/_pytask/node_protocols.py @@ -37,21 +37,6 @@ def save(self, value: Any) -> Any: """Save the value that was returned from a task.""" ... - def from_annot(self, value: Any) -> Any: - """Complete the node by setting the value from an default argument. - - Use it, if you want to add information on how a node handles an argument while - keeping the type of the value unrelated to pytask. - - .. codeblock: python - - def task_example(value: Annotated[Any, PythonNode(hash=True)], produces): - ... - - - """ - ... - @runtime_checkable class PPathNode(Node, Protocol): diff --git a/src/_pytask/nodes.py b/src/_pytask/nodes.py index e4fd9f3f2..ca506f261 100644 --- a/src/_pytask/nodes.py +++ b/src/_pytask/nodes.py @@ -111,7 +111,17 @@ class PathNode(PPathNode): """The path to the file.""" def from_annot(self, value: Path) -> None: - """Set path and if other attributes are not set, set sensible defaults.""" + """Set path and if other attributes are not set, set sensible defaults. + + Use it, if you want to control the name of the node. + + .. codeblock: python + + def task_example(value: Annotated[Any, PathNode(name="value")]): + ... + + + """ if not isinstance(value, Path): raise TypeError("'value' must be a 'pathlib.Path'.") if not self.name: @@ -176,7 +186,18 @@ def save(self, value: Any) -> None: self.value = value def from_annot(self, value: Any) -> None: - """Set the value from a function annotation.""" + """Set the value from a function annotation. + + Use it, if you want to add information on how a node handles an argument while + keeping the type of the value unrelated to pytask. For example, the node could + be hashed. + + .. codeblock: python + + def task_example(value: Annotated[Any, PythonNode(hash=True)]): + ... + + """ self.value = value def state(self) -> str | None: