I am attempting to combine two decorator tutorials into a single decorator that will log function arguments at a specified log level.
The first tutorial is from here and looks like this (and works as expected):
import logging
logging.basicConfig(level=logging.DEBUG)
def dump_args(func):
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = func.func_name
logger = logging.getLogger(fname)
def echo_func(*args, **kwargs):
"""
Log arguments, including name, type and value
"""
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.debug(" args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
return func(*args, **kwargs)
return echo_func
The second tutorial is from here .
My combined code looks like this and produces an error.
#decorators.py
from functools import wraps
import logging
logging.basicConfig(level=logging.DEBUG)
def logged(level=logging.INFO, name=None, message=None):
'''
Dump function arguments to log file.
Optionally, change the logging level of the call, the name of the logger to
use and the specific message to log as well
'''
def decorate(func):
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = name if name else func.__module__
logger = logging.getLogger(fname)
logmsg = message if message else None
@wraps(func)
def wrapper(*args, **kwargs):
"""
Log arguments, including name, type and value
"""
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.log(level, " args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
if logmsg:
logger.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
It is being called from my flask application like this:
@app.route("/hello/")
@app.route("/hello/<name>")
@api_decorators.logged
def hello(name=None):
s = "Hello"
if name:
s = "%s %s!" % (s, name)
else:
s = "%s %s!" % (s, "World!")
return s
The error that is produced is
TypeError: decorate() got an unexpected keyword argument 'name'
The entire stack trace is
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\flask\app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "C:\Python27\lib\site-packages\flask\app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "C:\Python27\lib\site-packages\flask\app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "C:\Python27\lib\site-packages\flask\app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Python27\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Python27\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Python27\lib\site-packages\flask\app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
TypeError: decorate() got an unexpected keyword argument 'name'
How can I fix the combined code to eliminate this error?
You need to actually call the logged
decorator, even if you don't have a parameter to pass.
@api_decorators.logged()
def hello(name=None):
Your code has three functions:
logged
function, which creates a decorator that is configured with the arguments passed to it. It returns: decorator
function, which takes a function to decorate. It returns: So your code should be called like this:
logged()(hello)(name='something')
#Call 1 2 3, which calls hello inside it
But in your code, it is called like this:
logged(hello)(name='something')
#Call 1 2
The decorator
function doesn't expect the name
argument, which is what causes the error.
You can use a hack allow the decorator to be used without calling it first. You need to detect when the decorator is used without being called. I think it would be something like this:
def logged(level=logging.INFO, name=None, message=None):
...
# At the bottom, replace the return with this
# If level is callable, that means logged is being called as a decorator
if callable(level):
f = level
level = logging.INFO
return decorator(f)
else:
return decorator
You can do this by modifying the signature to be similar to this:
def logged(func=None, level=logging.INFO, name=None, message=None):
In this case, you will remove the decorate
function you implemented and leave the wrapper
function:
def logged(func=None, level=logging.INFO, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
# get function arguments name
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
# get function name
fname = name if name else func.__name__
logger = logging.getLogger(fname)
logmsg = message if message else None
@wraps(func)
def wrapper(*args, **kwargs):
def format_arg(arg):
return '%s=%s<%s>' % (arg[0], arg[1].__class__.__name__, arg[1])
logger.log(level, " args => {0}".format(', '.join(
format_arg(entry) for entry in zip(argnames, args) + kwargs.items())))
if logmsg:
logger.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
This takes advantage of the partial
method.
Example results utilizing your posted application code (unmodified). This doesn't require the ()
on the @api_decorators.logged
call:
2014-10-21 15:53:08,756 - INFO - hello - args =>
2014-10-21 15:53:12,730 - INFO - hello - args => name=unicode<Andy>
The first one is a call to /hello/
and the second is a call to /hello/Andy
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.