Source code for easydev.decorators

# -*- python -*-
# -*- coding: utf-8 -*-
#
#  This file is part of the easydev software
#
#  Copyright (c) 2011-2024 -
#
#  File author(s): Thomas Cokelaer (cokelaer, gmail.com)
#
#  Distributed under the BSD3 License.
#
#  Website: https://github.com/cokelaer/easydev
#  website: http://github.com/cokelaer/easydev
#
##############################################################################
"""Handy decorators"""
import threading
from functools import wraps

__all__ = ["ifpylab", "requires", "ifpandas"]


# decorator with arguments and optional arguments for a method
def _require(*args_deco, **kwds_deco):
    """Decorator for class method to check if an attribute exist

    .. doctest::

        from easydev.decorators import require

        class Test(object):
            def __init__(self):
               self.m = 1
            @require('m', "set the m attribute first")
            def print(self):
                print self.m
        t = Test()
        t.print()


    .. todo:: first argument could be a list
    """
    if len(args_deco) != 2:
        raise ValueError(
            "require decorator expects 2 parameter. First one is"
            + "the required attribute. Second one is an error message."
        )
    attribute = args_deco[0]
    msg = args_deco[1]

    if len(attribute.split(".")) > 2:
        raise AttributeError("This version of require decorator introspect only 2 levels")

    def decorator(func):
        # func: function object of decorated method; has
        # useful info like f.func_name for the name of
        # the decorated method.

        def newf(*args, **kwds):
            # This code will be executed in lieu of the
            # method you've decorated.  You can call the
            # decorated method via f(_args, _kwds).
            names = attribute.split(".")

            if len(names) == 1:
                if hasattr(args[0], attribute):
                    return func(*args, **kwds)
                else:
                    raise AttributeError("%s not found. %s" % (names, msg))
            elif len(names) == 2:
                if hasattr(getattr(args[0], names[0]), names[1]):
                    return func(*args, **kwds)
                else:
                    raise AttributeError("%s not found. %s" % (names, msg))

        newf.__name__ = func.__name__
        newf.__doc__ = func.__doc__
        return newf

    return decorator


# for book keeping, could be useful:
"""
def _blocking(not_avail):
    def blocking(f, *args, **kw):
        if not hasattr(f, "thread"):
            # no thread running
            def set_result():
                f.result = f(*args, **kw)
            f.thread = threading.Thread(None, set_result)
            f.thread.start()
            return not_avail
        elif f.thread.isAlive():
            return not_avail
        else:
            # the thread is ended, return the stored result
            del f.thread
            return f.result
    return blocking
"""

# same as require decorator but works with list of stirngs of
# single string and uses the functools utilities
[docs]def requires(requires, msg=""): """Decorator for class method to check if an attribute exist .. doctest:: >>> from easydev.decorators import requires >>> class Test(object): ... def __init__(self): ... self.m = 1 ... self.x = 1 ... @requires(['m','x'], "set the m attribute first") ... def printthis(self): ... print(self.m+self.x) >>> t = Test() >>> t.printthis() 2 """ if isinstance(requires, str): requires = [requires] elif isinstance(requires, list) == False: raise TypeError( "First argument of the /requires/ decorator must be a" + "string or list of string representing the required attributes" + "to be found in your class. Second argument is a " + "complementary message. " ) def actualDecorator(f): @wraps(f) def wrapper(*args, **kwds): for require in requires: if hasattr(args[0], require) == False: raise AttributeError("{} not found in {}. ".format(require, args[0]) + msg) return f(*args, **kwds) return wrapper return actualDecorator
# could be a macro maybe
[docs]def ifpandas(func): """check if pandas is available. If so, just return the function, otherwise returns dumming function that does nothing """ def wrapper(*args, **kwds): return func(*args, **kwds) try: import pandas return wrapper except Exception: # pragma: no cover def dummy(): pass return dummy
[docs]def ifpylab(func): """check if pylab is available. If so, just return the function, otherwise returns dumming function that does nothing """ # for functions def wrapper(*args, **kwds): return func(*args, **kwds) # for methods try: import pylab return wrapper except Exception: # pragma: no cover def dummy(): pass return dummy