decorator to fetch arguments from global objects


A

andrea crotti

Using a CouchDB server we have a different database object potentially for
every request.

We already set that db in the request object to make it easy to pass it
around form our django app, however it would be nice if I could set it once
in the API and automatically fetch it from there.

Basically I have something like

class Entity:
def save_doc(db)
...

I would like basically to decorate this function in such a way that:
- if I pass a db object use it
- if I don't pass it in try to fetch it from a global object
- if both don't exist raise an exception

Now it kinda of works already with the decorator below.
The problem is that the argument is positional so I end up maybe passing it
twice.
So I have to enforce that 'db' if there is passed as first argument..

It would be a lot easier removing the db from the arguments but then it
would look too magic and I didn't want to change the signature.. any other
advice?

def with_optional_db(func):
"""Decorator that sets the database to the global current one if
not passed in or if passed in and None
"""
@wraps(func)
def _with_optional_db(*args, **kwargs):
func_args = func.func_code.co_varnames
db = None
# if it's defined in the first elements it needs to be
# assigned to *args, otherwise to kwargs
if 'db' in func_args:
assert 'db' == func_args[0], "Needs to be the first defined"
else:
db = kwargs.get('db', None)

if db is None:
kwargs['db'] = get_current_db()

assert kwargs['db'] is not None, "Can't have a not defined database"
ret = func(*args, **kwargs)
return ret

return _with_optional_db
 
Ad

Advertisements

S

Steven D'Aprano

Using a CouchDB server we have a different database object potentially
for every request.

We already set that db in the request object to make it easy to pass it
around form our django app, however it would be nice if I could set it
once in the API and automatically fetch it from there.

Basically I have something like

class Entity:
def save_doc(db)
...

You missed a self, and a colon :)

I would like basically to decorate this function in such a way that:
- if I pass a db object use it
- if I don't pass it in try to fetch it from a global object
- if both don't exist raise an exception

def decorate(method):
@functools.wraps(method)
def inner(self, db=None, *args, **kwargs):
if db is None:
db = get_current_db()
return method(self, db, *args, **kwargs)
return inner


class Entity:
@decorate
def save_doc(self, db):
...

ought to do it, assuming get_current_db() raises an appropriate exception.

The usual Python argument passing rules apply. If you call the wrapped
function with positional arguments, db must be given explicitly. If you
call it with keyword arguments, it doesn't.

If you prefer, you can make db a keyword only argument:

# Python 3 version:
def decorate(method):
@functools.wraps(method)
def inner(self, *args, **kwargs, db=None):
if db is None:
db = get_current_db()
return method(self, db, *args, **kwargs)
return inner


# Python 2 version:
def decorate(method):
@functools.wraps(method)
def inner(self, *args, **kwargs):
db = kwargs.get('db', None)
try:
del kwargs['db']
except KeyError:
pass
if db is None:
db = get_current_db()
return method(self, db, *args, **kwargs)
return inner
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top