3

Given the code below, I am trying to identify the arguments sent to decorators that are used by a class.

Specifically, I am trying to identify the value 100 that is passed into the make_hyper decorator that is used in the Dog class.

Optimally, I need to be able to get this value without running the method directly since the actual method code I am working with takes a long time to run.

import inspect

def make_hyper(new_volume):
    def decorator(decorated_method):
        def wrapped_method(self, *args, **kwargs):
            self.volume = new_volume
            return decorated_method(self, *args, **kwargs)
        return wrapped_method
    return decorator


class Dog(object):
    def __init__(self, name):
        self.name = name
        self.volume = 1

    @make_hyper(new_volume=100)
    def bark(self):
        if self.volume >= 10:
            print('[{}]: BARK!!'.format(self.name))
        elif self.volume >= 5:
            print('[{}]: Bark!'.format(self.name))
        else:
            print('[{}]: Bark'.format(self.name))

I have tried to use inspect.getargspec and a handful of other things and have (done my best to) scour Stackoverflow and the Internet at large but I cannot find a solution.

Any help would be greatly appreciated! Thanks for your time!

UPDATE: Someone asked me to clarify what I was trying to do. Apologies for not being more clear.

The actual code that the above toy represents is a test automation framework that includes decorators specifying test tags:

class TestTags(object):
  WIFI = 'wifi'
  BLE = 'ble'
  NIGHTLY = 'nightly'
  REGRESSION = 'regression'

class TestBase(unittest.TestCase):
  <define common test stuff and test decorators>

class ThingTester(TestBase):
  @TestBase.tags(TestTags.WIFI, TestTags.BLE, TestTags.REGRESSION)
  def test_all_the_things(self):
    <test all the things>

# what I'm trying to get
test_tags = ???
print(test_tags)  # prints out ('wifi', 'ble', 'regression')

I wrote a utility that goes through all the test modules, classes and individual tests and creates an HTML+JavaScript page that defines a Test Plan. One piece of data that I want to add to the page is what tags are associated with each test.

I found that I can make the tags decorator save an attribute to the class (i.e. self.tags = tags) but this requires the test to be run before the value is saved to the TestBase object and I need to be able to generate the test plan independently of running the tests.

retsigam
  • 540
  • 1
  • 5
  • 13
  • You are not showing how you expect to fetch and use the decorator's argument value other than normally inside the decorator's own body. Can you clarify what you are trying to do and is not working? – korrigan Feb 22 '18 at 01:44

1 Answers1

2

I'm not sure if I'm understanding entirely what you want, but if you intend to inspect the existing test to create a plan or a report, then you probably want to store the decorator arguments in the test function, instead of the class instance, right? For example:

def TestTags(*tags):
    def decorator(f):
        def wrapper(*args, **kwargs):
            # ...
            return f(*args, **kwargs)
        wrapper.tags = tags
        return wrapper
    return decorator

@TestTags(TAG1, TAG2)
def myTest(...):
    ...

print(myTest.tags)
>>> (TAG1, TAG2)

EDIT: Just realized that doesn't work out of the box for instance methods. You can still use it like this though:

def TestTags(*tags):
    def decorator(f):
        def wrapper(*args, **kwargs):
            # ...
            return f(*args, **kwargs)
        wrapper.tags = tags
        return wrapper
    return decorator

class TestSuite(...):
    @TestTags(TAG1, TAG2)
    def myTest(...):
        ...

print(TestSuite.myTest.tags)  # From class function
>>> (TAG1, TAG2)
myTestSuite = TestSuite()
print(myTestSuite.myTest.__func__.tags)  # From instance method
>>> (TAG1, TAG2)
jdehesa
  • 58,456
  • 7
  • 77
  • 121
  • This is great and it's almost what I need. However, my `tags` decorators include logic that skip a test if the tags sent to the test framework do not match the decorator's arguments. I'm trying to adapt your example to one that returns `f()` rather than `f` but the addition of `f.tags = tags` is lost. Help? – retsigam Feb 22 '18 at 18:06
  • 1
    @retsigam Ah, I see. I've edited the answer to return a proper wrapper, see if that works for you. – jdehesa Feb 22 '18 at 18:15
  • The above mentioned edits solve the issue perfectly! Thank you very much! – retsigam Feb 23 '18 at 16:37