0

I have a following decorator.

def allow_disable_in_tests(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        version = ??? # name of the func of method’s class name
        need_to_switch_off_in_tests = cache.get('switch_off_in_tests', version=version)

        if settings.IM_IN_TEST_MODE and need_to_switch_off_in_tests:
            return None

        value = func(*args, **kwargs)
        return value
    return wrapper

There are 2 types of objects this decorator might accept as a func:

  1. Standalone function.

  2. Method of the class(bound method, static method and class method all possible)

Question is how to get from inside decorator name of the :

  1. function in case of func is standalone function.

  2. method’s class name in case of func is method of the class

This will be used in version .

Decorator should be able to work with both types of objects.

Thanks

3
  • 1
    Try func.__name__ for functions. Commented Jun 19, 2020 at 14:35
  • 1
    and func.__qualname__ for methods. Commented Jun 19, 2020 at 14:42
  • @kubatucka . version = func.__qualname__.split('.')[0] returns func name in case of fucntion and class name in case of method. Better then i expected, thanks a lot. Please post full answer - i will accept it Commented Jun 19, 2020 at 14:48

1 Answer 1

2

You can use __qualname__ and __module__ to derive this information. __qualname__ will describe where the class is defined within a module according to the class or function it was defined in.

However, you are putting test logic in production code, which is a bit of a code smell. You'd be better off using the monkey patching features of your testing framework to patch these functions when running your test suite. For example, with pytest:

import pytest
from functools import wraps
from inspect import signature

class FuncPatch:
    def __init__(self, parent, name, retval=None):
        self.parent = parent
        self.name = name
        self.retval = retval

def get_things_to_patch():
    import mymodule
    return (
        FuncPatch(mymodule, 'my_func'),
        FuncPatch(mymodule.MyClass, 'method'),
        FuncPatch(mymodule.MyClass, 'static'),
        FuncPatch(mymodule.MyClass, 'class_', retval='special'),
    )

def create_test_function(func, retval, decorator=None):
    func = getattr(func, '__func__', func) # unwrap if classmethod or normal method
    sig = signature(func)
    @wraps(func)
    def f(*args, **kwargs):
        # check func was called with correct params raises TypeError if wrong
        sig.bind(*args, **kwargs)
        return retval
    if decorator:
        f = decorator(f)
    return f

@pytest.fixture
def patch_all_the_things(monkeypatch):
    for patch in get_things_to_patch():
        decorator = None
        if (isinstance(patch.parent, type)
                and not callable(patch.parent.__dict__[patch.name])
        ):
            # quick hack to detect staticmethod or classmethod
            decorator = type(patch.parent.__dict__[patch.name])

        to_patch = getattr(patch.parent, patch.name)
        func = create_test_function(to_patch, patch.retval, decorator)
        monkeypatch.setattr(patch.parent, patch.name, func)

# things to test
def my_func():
    return 'my_func'

class MyClass:
    @staticmethod
    def static():
        return 'static'
    @classmethod
    def class_(cls):
        return 'class'
    def method(self):
        return 'method'

# actual tests
def test_my_func(patch_all_the_things):
    assert my_func() is None

def test_my_class(patch_all_the_things):
    assert MyClass().method() is None
    assert MyClass.method(MyClass()) is None
    assert MyClass.static() is None
    assert MyClass.class_() == 'special'
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.