问题
I am using python flask framework. I write a decorator which will be need a parameter, and this parameter will be dynamic.
my decorator like below, will be get a key ,and using the key fetch data from redis.
def redis_hash_shop_style(key):
def fn_wrapper(f):
@wraps(f)
def decorated_function(*args, **kwargs):
data = redis_hash(key)
return data
return decorated_function
return fn_wrapper
and I have a class to using this decorater, code like this
class ShopAreaAndStyleListAPI(Resource):
@redis_hash_shop_style(key='shop_{}_style'.format(g.city.id))
def get(self):
# if not found from redis, query from mysql
pass
As you see, my decorator need a parameter named key
, and I pass the key like this
@redis_hash_shop_style(key='shop_{}_style'.format(g.city.id))
g.city.id
will be get the city's id, if everything is ok, the key will be like this
shop_100_style
but I got the error:
class ShopAreaAndStyleListAPI(Resource):
File "xx.py", line 659, in ShopAreaAndStyleListAPI
@redis_hash_shop_style(key='shop_{}_style'.format(g.city.id))
File "/Users/xx/.virtualenvs/yy/lib/python2.7/site-packages/werkzeug/local.py", line 347, in __getattr__
return getattr(self._get_current_object(), name)
File "/Users/xx/.virtualenvs/yy/lib/python2.7/site-packages/werkzeug/local.py", line 306, in _get_current_object
return self.__local()
File "/Users/xx/.virtualenvs/yy/lib/python2.7/site-packages/flask/globals.py", line 44, in _lookup_app_object
raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that
needed to interface with the current application object in a way.
To solve this set up an application context with app.app_context().
See the documentation for more information.
I am quite confused , in flask, how to pass a dynamic parameter to a decorator?
Thanks.
回答1:
If we check the docs for flask application global, flask.g
, it says:
To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in threaded environments. Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request.
This is achieved by using a thread-local proxy (in flask/globals.py):
g = LocalProxy(partial(_lookup_app_object, 'g'))
The other thing we should keep in mind is that Python is executing the first pass of our decorator during the "compile" phase, outside of any request, or flask
application. That means key
argument get assigned a value of 'shop_{}_style'.format(g.city.id)
when your application starts (when your class is being parsed/decorated), outside of flask
request context.
But we can easily delay accessing to flask.g
by using a lazy proxy, which fetches the value only when used, via callback function. Let's use the one already bundled with flask
, the werkzeug.local.LocalProxy:
from werkzeug.local import LocalProxy
class ShopAreaAndStyleListAPI(Resource):
@redis_hash_shop_style(key=LocalProxy(lambda: 'shop_{}_style'.format(g.city.id)))
def get(self):
# if not found from redis, query from mysql
pass
In general (for non-flask
or non-werkzeug
apps), we can use a similar LazyProxy
from the ProxyTypes package.
Unrelated to this, you'll also want to fix your redis_hash_shop_style
decorator to not only fetch from redis
, but to also update (or create) the value if stale (or non-existing), by calling the wrapped f()
when appropriate.
来源:https://stackoverflow.com/questions/44994612/python-flask-how-to-pass-a-dynamic-parameter-to-a-decorator