3

I am trying to make a class that will make api requests, retrying based on configuration options passed in to the retrying.retry decorator, and handle different error codes in the correct way for each job.

Here is my code:

from retrying import retry


class APIRequester:
    def __init__(self, url, **kwargs):
        self.url = url
        self.retry_kwargs = kwargs


    @retry(**self.retry_kwargs) # Obviously doesn't know what self is
    def make_request(self):
        pass

How can I pass in parameters to this method decorator? I tried making them a class attribute, which didn't work either.

flybonzai
  • 3,763
  • 11
  • 38
  • 72
  • Class attributes or instance attributes? Why is `__init__` setting a class attribute, and why are you trying to pass an instance attribute to the decorator? – user2357112 Oct 11 '17 at 17:03
  • How else would I pass in the values to my method decorator? Also, fixed the class attribute thing, that was just something I was trying. – flybonzai Oct 11 '17 at 17:04
  • You don't need a decorator. Save the kwargs as an instance attribute, and access them from a loop inside `make_request` as needed. – chepner Oct 11 '17 at 17:04
  • The `retry` method is part of https://pypi.python.org/pypi/retrying, which I think needs to be called as a decorator – flybonzai Oct 11 '17 at 17:05

4 Answers4

4

A couple of notes/questions:

  1. The @retry decorator will be applied to the make_request method at the time the class is created, while retry_kwargs will only become available when an instance of the class is created, and thus the former must precede the latter.

In which case, the former cannot depend on information that becomes available in the latter, ... as long as you use the decorator syntax ...

  1. The decorator syntax

     @decorator
         def xxx(...):
         ...
    

    is just syntax sugar for

     def xxx(...):
         ...
     xxx = decorate(xxx)
    

which means that, along with the fact that Python is very dynamic, you could force the issue by doing something like

    class APIRequester:
        def __init__(self, url, **kwargs):
            self.url = url
            self.retry_kwargs = kwargs
            APIRequester.make_request = retry(**kwargs)(APIRequester.make_request)

        def make_request(self):
            pass

Whether this particular decorator chokes on the self parameter or not, I cannot tell you.

Will you have more than one instance of APIRequester? If so, note that the method will be re-decorated each time a new instance is created: can this work sensibly? (I doubt it.) But see the edit below ...

If you do not have more that one instance, then you probably don't need to rely on information that becomes availale at the singleton's construction time.

The above were some general Python principles. I doubt that you really want to force the issue in this case. It seems to me that you are trying to use the decorator in a way that it was not designed to be used.

Edit: instancemethods

If you replace the line that does the decorating in the constructor with

self.make_request = retry(**kwargs)(self.make_request)

then each instance will get its own decorated version of the function. This should avoid any problems with re-decoration of the same function. There may will still be problems with self getting in the way. In that case, you could remove the self parameter from the definition and wrap it with staticmethod:

self.make_request = retry(**kwargs)(staticmethod(self.make_request))

Or better still, use decorator syntax to apply staticmethod to make_request at the place where you define it, the way Guido inteded it.

Like this, it even stands a chance of working! :-)

Community
  • 1
  • 1
jacg
  • 2,040
  • 1
  • 14
  • 27
  • There is no decorator any more, BTW, and each method to be decorated should have that ugly structure in the constructor. Totally defeats the purpose. – Roman Susi Oct 11 '17 at 17:56
  • 1
    @RomanSusi There **is** a decorator, it's just not using the syntax sugar any more. I was explaining the Python principles; I was not claiming that it is pretty, or a good idea, or likely to work as intended with the specific decorator in the question. – jacg Oct 11 '17 at 18:01
  • Yes, your answer has great educational value, but decorator syntax is not used any more. – Roman Susi Oct 11 '17 at 18:08
1

Of course self is available in the decorator at the call time. See answers to How to decorate a method inside a class? , on which I based my answer here:

def my_retry(fn):
    from functools import wraps
    @wraps(fn)
    def wrapped(self):
        print(self.retry_kwargs)
        for i in range(self.retry_kwargs["times"]):
            # you have total control
            fn(self)
            # around your method. Can even call it multiple times,
            # call with original retry: 
        retry(**self.retry_kwargs)(fn)(self)
        # check exceptions, return some value (here None), etc
        # 
    return wrapped

class APIRequester(object):
    def __init__(self, url, **kwargs):
        self.url = url
        self.retry_kwargs = kwargs

    @my_retry
    def make_request(self):
        print("method")

a = APIRequester('http://something', times=3)
a.make_request()

That is, original decorator is wrapped with a new, configuration-aware decorator. No need to change the constructor, syntax remains simple.

Roman Susi
  • 4,135
  • 2
  • 32
  • 47
1

Decorator is just a syntax sugar for func=decorator(func). You can do the assignment yourself:

class APIRequester:
    def __init__(self, url, **kwargs):
        self.url = url
        self.make_request = retry(**kwargs)(self.make_request)

    def make_request(self):
        pass

This will internally replace a method (descriptor) by a function, but it will work as expected.

VPfB
  • 14,927
  • 6
  • 41
  • 75
0

Retry decorator doesn't support class method, because instance of the class is implicitly passed to function. Please decorate normal function. If you want to wrap function into class, please decorate static method.

songxunzhao
  • 3,149
  • 1
  • 11
  • 13
  • Careful: when you say 'class method' people might take it to meat Pythons `classmethod`, which is not what you meant. – jacg Oct 11 '17 at 18:14
  • Echoing the comment above for anyone else. This answer does apply to `classmethod` (since the class object is passed in automatically), but the "class method" in the answer is referring to a regular instance method in Python, which is also unsupported since `self` is passed in. – ely Jun 25 '20 at 16:11