简体   繁体   中英

Understanding my own decorator

I have written a decorator that is working correctly but i stumbled with the correct solution by trial and error and my litle knowledge about decorators tells me that something is not well defined.

The case is i'm mocking a Rest Api to do some TDD, and this Rest is behind a token security. So before making any request i first must get my user token. I'm using httpretty for mocking the API.

So far i had to register_uri in every test case, one to mock the /token resource and another to test any other resource. But i found that very cumbersome, so a came with a solution to write a simple decorator that would mock the /token and then only had to mock the tested resource.

This is my currently working decorator...

def activate_security(func):
    def test_case(test_case):
        httpretty.enable()
        uri = 'http://{}:{}/token'.format(HOST, PORT)
        httpretty.register_uri(httpretty.GET, uri,
                               body=dumps({'token': 'dummy_token'}),
                               content_type='application/json')
        test_case()
        httpretty.disable()
    return test_case

And this is how is called.

@activate_security
@httpretty.activate
def test_case_one(self):
    #Test case here

I had to pass the test_case parameter to the inner function 'cause without it it wouldn't work, and that test_case is test_case_one method, which i thought it would be passed in the func argument, but func in the outer scope holds the object at memory of test_case.

Should't be func the returned value of a decorator? If i do that, the decorator doesn't work. When the inner function is passed that parameter?

You are decorating methods, so your resulting wrapper function needs a self parameter, just like normal functions being used in a class.

All that is different is that you used a different name fro that self parameter, test_case . As it happens, the instance is callable, and calling it runs the test, so you are in essence doing self() to run the test again .

Just name the parameter self and pass it to the wrapped function:

def activate_security(func):
    def wrapper(self):
        httpretty.enable()
        uri = 'http://{}:{}/token'.format(HOST, PORT)
        httpretty.register_uri(httpretty.GET, uri,
                               body=dumps({'token': 'dummy_token'}),
                               content_type='application/json')
        func(self)
        httpretty.disable()
    return wrapper

The wrapper() function then replaces the original test_case_one function, and when the test is run the wrapper() function is bound to the test case instance and will be passed that instance as self ; in your wrapper you can then call the unbound func() by simply passing on self to it.

For debugging purposes it is often nicer to have some function attributes copied over from the wrapped function to the wrapper; the @functools.wraps() decorator can take care of these details for you:

import functools

def activate_security(func):
    @functools.wraps(func)
    def wrapper(self):
        httpretty.enable()
        uri = 'http://{}:{}/token'.format(HOST, PORT)
        httpretty.register_uri(httpretty.GET, uri,
                               body=dumps({'token': 'dummy_token'}),
                               content_type='application/json')
        func(self)
        httpretty.disable()
    return wrapper

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM