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 and this 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?
Change
to
and
to
It works:
Output:
When you use
def inner(that, session=None, *args, **kwargs)
any second positional argument (countingself
) is treated assession
argument. So when you callmanager.stock_exists('AAPL')
session
gets valueAAPL
.