Python decorate methods with variable number of positional args and optional arg
Question: I am writing my first Python (3.4) application using SQLalchemy. I have
several methods which all have a very similar pattern. They take an optional
argument `session` which defaults to `None`. If `session` is passed, the
function uses that session, otherwise it opens and uses a new session. For
example, consider the following method:

    
    
    def _stocks(self, session=None):
        """Return a list of all stocks in database."""
        newsession = False
        if not session:
            newsession = True
            session = self.db.Session()
        stocks = [stock.ticker for stock in session.query(Stock).all()]
        if newsession:
            session.close()
        return stocks
    

So, being new to Python and eager to learn all of its power, I thought this
smelt like the perfect time to learn a little something about Python
decorators. So after a lot of reading, like this [this series of blog
posts](http://www.artima.com/weblogs/viewpost.jsp?thread=240808) and
[this](http://stackoverflow.com/a/1594484/4530863) fantastic SO answer, I
wrote the following decorator:

    
    
    from functools import wraps
    
    def session_manager(func):
        """
        Manage creation of session for given function.
    
        If a session is passed to the decorated function, it is simply
        passed through, otherwise a new session is created.  Finally after
        execution of decorated function, the new session (if created) is
        closed/
        """
        @wraps(func)
        def inner(that, session=None, *args, **kwargs):
            newsession = False
            if not session:
                newsession = True
                session = that.db.Session()
            func(that, session, *args, **kwargs)
            if newsession:
                session.close()
            return func(that, session, *args, **kwargs)
        return inner
    

And it seems to work great. The original method is now reduced to:

    
    
    @session_manager
    def _stocks(self, session=None):
        """Return a list of all stocks in database."""
        return [stock.ticker for stock in session.query(Stock).all()]
    

HOWEVER, when I apply the decorator to a function that takes some positional
arguments in addition to the optional `session`, I get an error. So trying to
write:

    
    
    @session_manager
    def stock_exists(self, ticker, session=None):
        """
        Check for existence of stock in database.
    
        Args:
            ticker (str): Ticker symbol for a given company's stock.
            session (obj, optional): Database session to use.  If not
                provided, opens, uses and closes a new session.
    
        Returns:
            bool: True if stock is in database, False otherwise.
        """
        return bool(session.query(Stock)
                    .filter_by(ticker=ticker)
                    .count()
                    )
    

and running like `print(client.manager.stock_exists('AAPL'))` gives an
`AttributeError` with the following traceback:

    
    
    Traceback (most recent call last):
      File "C:\Code\development\Pynance\pynance.py", line 33, in <module>
        print(client.manager.stock_exists('GPX'))
      File "C:\Code\development\Pynance\pynance\decorators.py", line 24, in inner
        func(that, session, *args, **kwargs)
      File "C:\Code\development\Pynance\pynance\database\database.py", line 186, in stock_exists
        .count()
    AttributeError: 'NoneType' object has no attribute 'query'
    [Finished in 0.7s]
    

So I am guessing by the traceback, that I am messing up the order of the
arguments, but I can't figure out how to order them properly. I have functions
that I want to decorate that can take 0-3 arguments in addition to the
`session`. Can someone please point out the error in my methodology?


Answer: Change

    
    
    def inner(that, session=None, *args, **kwargs):
    

to

    
    
    def inner(that, *args, session=None, **kwargs):
    

and

    
    
    return func(that, session, *args, **kwargs)
    

to

    
    
    return func(that, *args, session=session, **kwargs)
    

It works:

    
    
    def session_manager(func):
    
        def inner(that, *args, session=None, **kwargs):
            if not session:
                session = object()
            return func(that, *args, session=session, **kwargs)
    
        return inner
    
    
    class A():
    
        @session_manager
        def _stocks(self, session=None):
            print(session)
            return True
    
        @session_manager
        def stock_exists(self, ticker, session=None):
            print(ticker, session)
            return True
    
    a = A()
    a._stocks()
    a.stock_exists('ticker')
    

Output:

    
    
    $ python3 test.py
    <object object at 0x7f4197810070>
    ticker <object object at 0x7f4197810070>
    

When you use `def inner(that, session=None, *args, **kwargs)` any second
positional argument (counting `self`) is treated as `session` argument. So
when you call `manager.stock_exists('AAPL')` `session` gets value `AAPL`.

