From d620c7be55b2656d1e9ce677b67d9a9cde04ca31 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 29 Apr 2024 14:50:11 -0700 Subject: [PATCH] Remove unused decorator And reformat tests in the vein of usage in Function and Macro --- pyiron_workflow/io_preview.py | 113 +--------------------------------- tests/unit/test_io_preview.py | 60 +++++++++++++----- 2 files changed, 44 insertions(+), 129 deletions(-) diff --git a/pyiron_workflow/io_preview.py b/pyiron_workflow/io_preview.py index 46a4e323..c6f21773 100644 --- a/pyiron_workflow/io_preview.py +++ b/pyiron_workflow/io_preview.py @@ -130,7 +130,7 @@ class ScrapesIO(HasIOPreview, ABC): @classmethod @abstractmethod def _io_defining_function(cls) -> callable: - """Must return a static class method.""" + """Must return a static method.""" _output_labels: ClassVar[tuple[str] | None] = None # None: scrape them _validate_output_labels: ClassVar[bool] = True # True: validate against source code @@ -356,114 +356,3 @@ def inputs(self) -> Inputs: @property def outputs(self) -> OutputsWithInjection: return self._outputs - - -class DecoratedNode(StaticNode, ScrapesIO, ABC): - """ - A static node whose IO is defined by a function's information (and maybe output - labels). - """ - - -def decorated_node_decorator_factory( - parent_class: type[DecoratedNode], - io_static_method: callable, - decorator_docstring_additions: str = "", - **parent_class_attr_overrides, -): - """ - A decorator factory for building decorators to dynamically create new subclasses - of some subclass of :class:`DecoratedNode` using the function they decorate. - - New classes get their class name and module set using the decorated function's - name and module. - - Args: - parent_class (type[DecoratedNode]): The base class for the new node class. - io_static_method: The static method on the :param:`parent_class` which will - store the io-defining function the resulting decorator will decorate. - :param:`parent_class` must override :meth:`_io_defining_function` inherited - from :class:`DecoratedNode` to return this method. This allows - :param:`parent_class` classes to have unique names for their io-defining - functions. - decorator_docstring_additions (str): Any extra text to add between the main - body of the docstring and the arguments. - **parent_class_attr_overrides: Any additional attributes to pass to the new, - dynamically created class created by the resulting decorator. - - Returns: - (callable): A decorator that takes creates a new subclass of - :param:`parent_class` that uses the wrapped function as the return value of - :meth:`_io_defining_function` for the :class:`DecoratedNode` mixin. - """ - if getattr(parent_class, io_static_method.__name__) is not io_static_method: - raise ValueError( - f"{io_static_method.__name__} is not a method on {parent_class}" - ) - if not isinstance(io_static_method, FunctionType): - raise TypeError(f"{io_static_method.__name__} should be a static method") - - def as_decorated_node_decorator( - *output_labels: str, - validate_output_labels: bool = True, - ): - output_labels = None if len(output_labels) == 0 else output_labels - - @builds_class_io - def as_decorated_node(io_defining_function: callable): - if not callable(io_defining_function): - raise AttributeError( - f"Tried to create a new child class of {parent_class.__name__}, " - f"but got {io_defining_function} instead of a callable." - ) - - return type( - io_defining_function.__name__, - (parent_class,), # Define parentage - { - io_static_method.__name__: staticmethod(io_defining_function), - "__module__": io_defining_function.__module__, - "_output_labels": output_labels, - "_validate_output_labels": validate_output_labels, - **parent_class_attr_overrides, - }, - ) - - return as_decorated_node - - as_decorated_node_decorator.__doc__ = dedent( - f""" - A decorator for dynamically creating `{parent_class.__name__}` sub-classes by - wrapping a function as the `{io_static_method.__name__}`. - - The returned subclass uses the wrapped function (and optionally any provided - :param:`output_labels`) to specify its IO. - - {decorator_docstring_additions} - - Args: - *output_labels (str): A name for each return value of the graph creating - function. When empty, scrapes output labels automatically from the - source code of the wrapped function. This can be useful when returned - values are not well named, e.g. to make the output channel - dot-accessible if it would otherwise have a label that requires - item-string-based access. Additionally, specifying a _single_ label for - a wrapped function that returns a tuple of values ensures that a - _single_ output channel (holding the tuple) is created, instead of one - channel for each return value. The default approach of extracting - labels from the function source code also requires that the function - body contain _at most_ one `return` expression, so providing explicit - labels can be used to circumvent this (at your own risk). (Default is - empty, try to scrape labels from the source code of the wrapped - function.) - validate_output_labels (bool): Whether to compare the provided output labels - (if any) against the source code (if available). (Default is True.) - - Returns: - (callable[[callable], type[{parent_class.__name__}]]): A decorator that - transforms a function into a child class of `{parent_class.__name__}` - using the decorated function as - `{parent_class.__name__}.{io_static_method.__name__}`. - """ - ) - return as_decorated_node_decorator diff --git a/tests/unit/test_io_preview.py b/tests/unit/test_io_preview.py index fa366028..d7cdddae 100644 --- a/tests/unit/test_io_preview.py +++ b/tests/unit/test_io_preview.py @@ -3,26 +3,52 @@ import unittest from pyiron_workflow.channels import NOT_DATA -from pyiron_workflow.io_preview import ( - ScrapesIO, decorated_node_decorator_factory, OutputLabelsNotValidated -) +from pyiron_workflow.io_preview import ScrapesIO, OutputLabelsNotValidated +from pyiron_workflow.snippets.factory import classfactory -class ScraperParent(ScrapesIO, ABC): - - @staticmethod - @abstractmethod - def io_function(*args, **kwargs): - pass - +class ScrapesFromDecorated(ScrapesIO): @classmethod - def _io_defining_function(cls): - return cls.io_function - - -as_scraper = decorated_node_decorator_factory( - ScraperParent, ScraperParent.io_function -) + def _io_defining_function(cls) -> callable: + return cls._decorated_function + + +@classfactory +def scraper_factory( + io_defining_function, + validate_output_labels, + io_defining_function_uses_self, + /, + *output_labels, +): + return ( + io_defining_function.__name__, + (ScrapesFromDecorated,), # Define parentage + { + "_decorated_function": staticmethod(io_defining_function), + "__module__": io_defining_function.__module__, + "_output_labels": None if len(output_labels) == 0 else output_labels, + "_validate_output_labels": validate_output_labels, + "_io_defining_function_uses_self": io_defining_function_uses_self + }, + {}, + ) + + +def as_scraper( + *output_labels, + validate_output_labels=True, + io_defining_function_uses_self=False, +): + def scraper_decorator(fnc): + scraper_factory.clear(fnc.__name__) # Force a fresh class + factory_made = scraper_factory( + fnc, validate_output_labels, io_defining_function_uses_self, *output_labels + ) + factory_made._class_returns_from_decorated_function = fnc + factory_made.preview_io() + return factory_made + return scraper_decorator class TestIOPreview(unittest.TestCase):