diff --git a/pyflow/__init__.py b/pyflow/__init__.py index f4cde2f..8e6f63b 100644 --- a/pyflow/__init__.py +++ b/pyflow/__init__.py @@ -2,6 +2,14 @@ from __future__ import absolute_import +import warnings + +warnings.formatwarning = ( + lambda mess, category, filename, lineno, *args: f"\033[93m[{category.__name__}] {filename}:{lineno}\n{mess}\n\033[0m" +) +warnings.filterwarnings("default", category=DeprecationWarning) +warn = warnings.warn + from .attributes import ( Aviso, Complete, diff --git a/pyflow/anchor.py b/pyflow/anchor.py index 404cb06..893263a 100644 --- a/pyflow/anchor.py +++ b/pyflow/anchor.py @@ -1,5 +1,7 @@ import os +from . import warn + class AnchorMixin: """ @@ -12,8 +14,10 @@ def __init__(self, *args, **kwargs): variables.update(kwargs) if "out" in kwargs: - print( - "WARNING! out option is deprecated for nodes, use the log_directory option in the host instead" + warn( + "'out' option is deprecated for nodes, use the log_directory option in the host instead", + DeprecationWarning, + stacklevel=2, ) for control_variable in ("files", "include", "home", "out", "extn"): diff --git a/pyflow/deployment.py b/pyflow/deployment.py index c8aa70f..654ab07 100644 --- a/pyflow/deployment.py +++ b/pyflow/deployment.py @@ -5,6 +5,7 @@ import os import shutil +from pyflow import warn from pyflow.html import FileListHTMLWrapper @@ -232,7 +233,7 @@ def create_directory(self, path): try: os.makedirs(path) except Exception: - print("WARNING: Couldn't create directory: {}".format(path)) + warn("Couldn't create directory: {}".format(path), stacklevel=0) def check(self, target): """ diff --git a/pyflow/host.py b/pyflow/host.py index e93756a..35ed405 100644 --- a/pyflow/host.py +++ b/pyflow/host.py @@ -8,8 +8,10 @@ import shutil import textwrap +from . import warn from .attributes import Label, Limit from .base import STACK +from .inspect import get_value_at_caller from .nodes import DuplicateNodeError, Family, ecflow_name SET_ECF_VARIABLES = """ @@ -307,9 +309,13 @@ def build_label(self): def script_submit_arguments(self, submit_arguments): if len(submit_arguments) > 0: - print( - f"Host {self.__class__.__name__} does not support scheduler submission arguments. \ - Submission arguments will be ignored in the script generation", + node = get_value_at_caller("self", 2) + name = getattr(node, "fullname", getattr(node, "name", node)) + warn( + f"{name}: Host {self.__class__.__name__} does not support scheduler submission arguments, which " + f"will be ignored in the script generation. ", + UserWarning, + stacklevel=0, ) return [] @@ -1169,8 +1175,12 @@ def _translate_sthost(val): args.append(pragma) else: if arg in deprecated: - print( - f"WARNING! '{arg}' is deprecated, use '{deprecated[arg]}' instead" + node = get_value_at_caller("self", stacklevel=2) + name = getattr(node, "fullname", getattr(node, "name", node)) + warn( + f"{name}: {arg}' is deprecated in TroikaHost, use '{deprecated[arg]}' instead", + DeprecationWarning, + stacklevel=0, ) arg = deprecated[arg] if arg is not None: diff --git a/pyflow/inspect.py b/pyflow/inspect.py new file mode 100644 index 0000000..71e7921 --- /dev/null +++ b/pyflow/inspect.py @@ -0,0 +1,23 @@ +import inspect + + +def get_value_at_caller(target="self", stacklevel=2): + """ + returns the value of a variable in the caller's frame. Useful for getting + caller objects or variables at caller level, e.g, a Task name when defining its + submit arguments on its Host. + :param target: The name of the variable to retrieve from the caller's frame. + Defaults to "self", which is common in class methods. + :param stacklevel: The number of frames to go back in the call stack. + Defaults to 2, which means it will look at the frame of immediate caller. + """ + + frame = inspect.stack()[1].frame + for _ in range(stacklevel - 1): + if frame is None: + raise ValueError(f"No caller frame found with stacklevel {stacklevel}.") + frame = frame.f_back + # Get local variables in the caller's frame + locals_ = frame.f_locals + + return locals_.get(target, None) diff --git a/pyflow/nodes.py b/pyflow/nodes.py index 33eab0a..650f4a5 100644 --- a/pyflow/nodes.py +++ b/pyflow/nodes.py @@ -1161,9 +1161,9 @@ def deploy_suite(self, target=FileSystem, node=None, **options): script, includes = t.generate_script() try: target.deploy_task(t.deploy_path, script, includes) - except RuntimeError: + except Exception as e: print(f"\nERROR when deploying task: {t.fullname}\n") - raise + raise (e) for f in node.all_families: manual = self.generate_stub(f.manual) if manual: diff --git a/tests/test_host.py b/tests/test_host.py index a46d58f..09fb0b4 100644 --- a/tests/test_host.py +++ b/tests/test_host.py @@ -315,7 +315,6 @@ def test_troika_host(): def test_host_submit_args(): - submit_args = { "troika": { "tasks": 2, # deprecated option, will be translated to total_tasks