Source code for neofoam.framework.decorator
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 NeoFOAM authors
"""Decorators for marking functions as operations or conditions."""
from __future__ import annotations
import functools
import inspect
from typing import Any, Callable, TypeVar
from .types import OperationMetadata, OpType, OperationNumber
F = TypeVar("F", bound=Callable[..., Any])
def _is_decorated_method(method: Callable[..., Any]) -> bool:
return callable(method) and hasattr(method, "_metadata")
def decorated_member_functions(instance: Any) -> list[Callable[..., Any]]:
decorated_functions = []
for name, method in vars(instance.__class__).items():
if _is_decorated_method(method):
# Get the bound method
# otherwise we would be appending unbound methods aka without self
bound_method = getattr(instance, name)
decorated_functions.append(bound_method)
return decorated_functions
[docs]
def operation(
_func: F | None = None,
operation_number: OperationNumber | int | None = None,
depends_on: list[str] | None = None,
) -> F | Callable[[F], F]:
"""decorator Factory to mark a decorator to mark a function as an operation in the workflow."""
def _operation_decorator(_func: F) -> F:
"""Decorator to mark a function as an operation in the workflow."""
@functools.wraps(_func)
def wrapper(*args: object, **kwargs: object) -> Any:
return _func(*args, **kwargs)
op_num = (
OperationNumber(operation_number)
if isinstance(operation_number, int)
else operation_number
)
wrapper._metadata = OperationMetadata( # type: ignore[attr-defined]
op_type=OpType.OPERATION,
op_name=_func.__name__,
operation_number=op_num,
depends_on=depends_on,
)
return wrapper # type: ignore[return-value]
if _func is None:
return _operation_decorator
else:
return _operation_decorator(_func)
[docs]
def condition(
_func: F | None = None,
operation_number: OperationNumber | int | None = None,
depends_on: list[str] | None = None,
) -> F | Callable[[F], F]:
"""decorator Factory to mark a decorator to mark a function as a condition in the workflow."""
def _condition_decorator(_func: F) -> F:
"""Decorator to mark a function as a condition in the workflow."""
# check if return value is a Condition instance
return_annotation = inspect.signature(_func).return_annotation
# Require return annotation to be present and be Condition
if return_annotation is inspect.Signature.empty:
raise TypeError(
f"Function {_func.__name__} must have a return type annotation of 'bool'"
)
if return_annotation.__name__ != bool.__name__:
raise TypeError(
f"Return type of {_func.__name__} must be bool not a {return_annotation}"
)
@functools.wraps(_func)
def wrapper(*args: object, **kwargs: object) -> Any:
return _func(*args, **kwargs)
op_num = (
OperationNumber(operation_number)
if isinstance(operation_number, int)
else operation_number
)
wrapper._metadata = OperationMetadata( # type: ignore[attr-defined]
op_type=OpType.CONDITION,
op_name=_func.__name__,
operation_number=op_num,
depends_on=depends_on,
)
return wrapper # type: ignore[return-value]
if _func is None:
return _condition_decorator
else:
return _condition_decorator(_func)