1

I am implementing a database connector class in python. I will use the retry decorator from tenacity library to retry the connection of database when it times out.

I want to pass the self.retry_count and self.retry_interval to the arguments in retry decorator.

## etl_connect.py
from sqlalchemy import create_engine
import pymysql
import logging
from tenacity import *

class Connector():
    def __init__(self, mode, conn_str, retry_count, retry_interval):
        self.mode = mode
        self.conn_str = conn_str
        self.retry_count = retry_count
        self.retry_interval = retry_interval
        self.engine = None
        self.conn = None

    @retry(wait=wait_fixed(self.retry_interval), stop=stop_after_attempt(self.retry_count))
    def mysql_connect(self):
        logging.info('Connecting to mysql. (retry count=%d)' % (self.mysql_connect.retry.statistics['attempt_number']))
        mysql_engine = create_engine(self.conn_str)
        mysql_conn = mysql_engine.connect()
        logging.info('Connected to mysql successfully with %d attempt(s).' % (self.mysql_connect.retry.statistics['attempt_number']))
        return (mysql_engine, mysql_conn)

Now call the mysql_connect function:

## call.py
from etl_connect import *
mysql_connector = Connector('mysql', 'mysql database string here', 5, 10)
engine, conn = mysql_connector.mysql_connect()

But it shows: NameError: name 'self' is not defined.

Traceback (most recent call last):
  File "call.py", line 5, in <module>
    from etl_connect import *
  File "/home/developer/ETL_modules/etl_connect.py", line 19, in <module>
    class Connector():
  File "/home/developer/ETL_modules/etl_connect.py", line 56, in Connector
    @retry(wait=wait_fixed(self.retry_interval), stop=stop_after_attempt(self.retry_count))
NameError: name 'self' is not defined

Are there any ways that I can pass self.retry_count & self.retry_interval to the decorator?

ingyhere
  • 11,818
  • 3
  • 38
  • 52
Kevin Lee
  • 401
  • 3
  • 9
  • 22
  • Here's the answer: https://stackoverflow.com/questions/11731136/python-class-method-decorator-with-self-arguments – Antonio Spadaro Apr 02 '20 at 11:48
  • 1
    Note that answer assumes you are writing the decorator; if `retry` is something you are importing, you would need to define your *own* decorator that wraps it. – chepner Apr 02 '20 at 11:50
  • Does this answer your question? [Python class method decorator with self arguments?](https://stackoverflow.com/questions/11731136/python-class-method-decorator-with-self-arguments) – jpf Apr 02 '20 at 11:50

4 Answers4

2

Instead of decorating the method, call retry when you call the method.

## etl_connect.py
from sqlalchemy import create_engine
import pymysql
import logging
from tenacity import *

class Connector():
    def __init__(self, mode, conn_str, retry_count, retry_interval):
        self.mode = mode
        self.conn_str = conn_str
        self.retry_count = retry_count
        self.retry_interval = retry_interval
        self.engine = None
        self.conn = None

    def _mysql_connect(self):
        logging.info('Connecting to mysql. (retry count=%d)' % (self.mysql_connect.retry.statistics['attempt_number']))
        mysql_engine = create_engine(self.conn_str)
        mysql_conn = mysql_engine.connect()
        logging.info('Connected to mysql successfully with %d attempt(s).' % (self.mysql_connect.retry.statistics['attempt_number']))
        return (mysql_engine, mysql_conn)

    def mysql_connect(self):
        d = retry(
              wait=wait_fixed(self.retry_interval),
              stop=stop_after_attempt(self.retry_count)
            )
        # One of these two should work, depending on how
        # retry is actually defined.
        return d(Connector._mysql_connect)(self)
        # return d(self._mysql_connect)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • This worked for me. Can you explain why the '(self)' is necessary? – lemon master Oct 22 '20 at 21:44
  • Which `self` are you talking about? – chepner Oct 23 '20 at 11:53
  • Sorry for not clarifying -- the `(self)` in the return statement of mysql_connect. – lemon master Oct 26 '20 at 17:01
  • In either case, `_mysql_connect` is an instance method, and so needs to be called with or on an instance of `Connector`. Whether you pass it explicitly, or pass a bound method to `retry`, depends on whether `retry` (left undefined in the original question) takes a zero- or 1-argument callable. – chepner Oct 26 '20 at 18:25
0

Unless the decorator (retry) is defined as part of the class (and is not static) then you cannot reference an object instance in it.

NirO
  • 216
  • 2
  • 12
0

If you don't access @retry decorator and you prevent to edit it you can define a variable out of your class definition. Or you can define this base config in your setting's file then import it. Please look at this:

## etl_connect.py
from sqlalchemy import create_engine
import pymysql
import logging
from tenacity import *


base_config = {
    'retry_count': 3,
    'retry_interval': 30,
    ...
}

class Connector():
    def __init__(self, mode, conn_str):
        self.mode = mode
        self.conn_str = conn_str
        self.engine = None
        self.conn = None

    @retry(wait=wait_fixed(base_config['retry_interval']), stop=stop_after_attempt(base_config['retry_count']))
    def mysql_connect(self):
        logging.info('Connecting to mysql. (retry count=%d)' % (self.mysql_connect.retry.statistics['attempt_number']))
        mysql_engine = create_engine(self.conn_str)
        mysql_conn = mysql_engine.connect()
        logging.info('Connected to mysql successfully with %d attempt(s).' % (self.mysql_connect.retry.statistics['attempt_number']))
        return (mysql_engine, mysql_conn)

Or import from setting's file:

from sqlalchemy import create_engine
import pymysql
import logging
from tenacity import *

from settings import RETRY_COUNT
from settings import RETRY_INTERVAL

I know this way is solid but these options should define in your settings and no need to pass them every time you want make-instance of your class.

Ali Hallaji
  • 3,712
  • 2
  • 29
  • 36
0

Assuming you can't easily redefine the tenacity retry decorator, you could wrap it with one of your own that references the values in the Connector instance.

Here's what I mean:

# Wrapper for tenacity retry decorator
def my_retry(func):
    def wrapped(conn, *args, **kwargs):
        tdecorator = retry(wait=wait_fixed(conn.retry_interval),
                           stop=stop_after_attempt(conn.retry_count))
        decorated = tdecorator(func)
        return decorated(conn, *args, **kwargs)
    return wrapped


class Connector():
    def __init__(self, mode, conn_str, retry_count, retry_interval):
        self.mode = mode
        self.conn_str = conn_str
        self.retry_count = retry_count
        self.retry_interval = retry_interval
        self.engine = None
        self.conn = None

    @my_retry
    def mysql_connect(self):
        logging.info('Connecting to mysql. (retry count=%d)' % (self.mysql_connect.retry.statistics['attempt_number']))
        mysql_engine = create_engine(self.conn_str)
        mysql_conn = mysql_engine.connect()
        logging.info('Connected to mysql successfully with %d attempt(s).' % (self.mysql_connect.retry.statistics['attempt_number']))
        return (mysql_engine, mysql_conn)
martineau
  • 119,623
  • 25
  • 170
  • 301