i have a decorator named "auth"
def auth(check_func=validate_login):
def decorator(view):
def wrapper(*args, **kwargs):
auth = check_func()
if auth:
return view(*args, **kwargs)
return bottle.redirect('/login.html')
return wrapper
return decorator
the auth decorator is used like this
#get('/')
#view("someview")
#auth()
def handler():
#myhandlercode
so the auth decorator calls the view function which renders my template in bottle.py.
But now i want to return json instead of rendering a view. So what changes do i have to make to the auth decorator code to make this happen? I am confused as to how to call the handler instead of the view from the auth code.
EDIT 1 : Bottle allows you to return dict, it directly converts it to json. And i dont want to use the view at all, i just want to return json to the user from my handlers. So should i just remove the #view decorator? and what should i call instead in the auth decorator?
Bottle route decorators are able to apply decorators to you without destroy auto json feature.
#get('/', apply=[auth])
def handler():
...
If you don't want a view, just remove your #view decorator, Bottle handle dicts gracefully, converting it to a JSON answer.
I dont believe its right to have auth shoehorning in json dumping.
Here's an example with plain ol python of using decorators
def validate():
return True
def auth(valid=validate):
def _auth(f):
def _auth_wrap():
if not valid():
raise Exception('redirect')
return f()
return _auth_wrap
return _auth
def view(tmpl):
def _view(f):
def _view_wrap():
return tmpl.format(f())
return _view_wrap
return _view
#view('Hello, {0}')
#auth()
def handler():
return 'World'
handler
# outputs: __main__._view_wrap
handler()
# outputs: 'Hello, World'
and also the line
return tmpl.format(f())
The f func is __main__._auth_wrap which is calling validate and returning the executed handler.
so then you would handle dumping json by doing something else besides tmpl.format in the above, such as calling a seperate method and passing needed info, or shoehorning into view decorator, which either way will be more appropriate.
So to answer the end question a little better, if you want to dynamically decide whether or not to dump json based on the request and bottle.py's view func doesn't support this, then you could make a view wrapper similar to the above that does the checks on the request object or whatever you want to use to determine json output, and then either call bottle.py's view or json.dumps on f() within _view_wrap
If you want to make a func always dump json, then delete the view decorator and create a json decorator similar to the view decorator above that will return json.dumps(f())
The main point here is to keep auth doing what it's name implies.
You are returning Json (or rather a python dictionary) from your view, right? In that case you don't have to change anything in the decorator. That which the view returns is not immediately forwarded to the user's browser, it is first processed by bottle then returned to the user. When you return a dictionary from the view it is treated as Json, when returning a template string it is treated as Html.
Related
Say, I have a hand-crafted #login-required decorator:
from functools import wraps
def login_required(decorated_function):
"""Decorator to check if user is logged in."""
#wraps(decorated_function)
def wrapper(*args, **kwargs):
if False: # just to check it's working
return decorated_function(*args, **kwargs)
else:
flash('You need to login, to access this page')
return redirect(url_for('login'))
return wrapper
and a function, decorated with #app.route() and #login_required (endpoint for login omitted for brevity):
#app.route('/')
#login_required
def index():
return "Hello!"
Now, if I try to access /, as expected, it won't let me and will redirect to the login page.
Though, if I swipe the the order of the decorators i.e.:
#login_required
#app.route('/')
def index():
return "Hello!"
then I am able to access /, even though I shouldn't be.
I am aware that Flask documentation on the subject states:
When applying further decorators, always remember that the route() decorator is the outermost.
I have also seen other questions on the same issue.
What I'm curious about is not what is the proper way to do it (#app.route() decorator must be outermost - got it), but rather why it is working this way (i.e. what is the mechanics behind it).
I took a look at #app.route() source code:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
This answer, helped me to understand mechanism of decorators, more or less. Though, I have never seen function just returned (without calling it) before, so I did a little experiment myself (which turned out to be workable, of course):
def my_decorator():
def decorator (function):
return function
return decorator
#my_decorator()
def test():
print('Hi')
test()
So, I would like to understand:
Why order of decorators matter in the exact case above and for #app.route() and other decorators in general (which is the same answer, I guess)? What confuses me, is that #app.route() just adds url rule to the app (i.e. self.add_url_rule(rule, endpoint, f, **options) and returns the function, that's it, so why would order matter?
Does #app.route() overrides all the decorators above it (how if so)?
I am also aware, that decorators application order is from bottom to top, though it doesn't make things any clearer, for me. What am I missing?
You have almost explained it yourself! :-) app.route does
self.add_url_rule(rule, endpoint, f, **options)
But the key is that f here is whatever function was decorated. If you apply app.route first, it adds a URL rule for the original function (without the login decorator). The login decorator wraps the function, but app.route has already stored the original unwrapped version, so the wrapping has no effect.
It may help to envision "unrolling" the decorators. Imagine you did it like this:
# plain function
def index():
return "Hello!"
login_wrapped = login_required(index) # login decorator
both_wrapped = app.route('/')(login_wrapped) # route decorator
This is the "right" way where the login wrap happens first and then the route. In this version, the function that app.route sees is already wrapped with the login wrapper. The wrong way is:
# plain function
def index():
return "Hello!"
route_wrapped = app.route('/')(index) # route decorator
both_wrapped = login_wrapped(route_wrapped) # login decorator
Here you can see that what app.route sees is only the plain unwrapped version. The fact that the function is later wrapped with the login decorator has no effect, because by that time the route decorator has already finished.
I'm trying to add an access_rights decorator to my Bottle app to check permissions when accessing a route. However, it's not getting the decorated function's arguments, which causes an error when trying to call my decorated function again.
Here's an example of code using the decorator:
#route('/users')
#access_rights({'POST': ['admin']})
def users(user):
pass
The user parameter comes from a Bottle plugin I wrote that gets the user from the token passed with the request. This is my current decorator:
def access_rights(permissions):
def decorator(f):
def wrapper(*args, **kwargs):
# Check permissions rights here (not implemented yet)
return f(*args, **kwargs)
return wrapper
return decorator
With this, I get TypeError: users() takes exactly 1 argument (0 given) when doing a GET /users, meaning args and kwargs were both empty. However, when I change the decorator as follows, it works:
def access_rights(permissions):
def decorator(f):
return f
return decorator
I haven't worked with decorators a lot, but from my understanding, both implementations above should call the users function with its original parameters, yet for some reason the first one doesn't get the parameters. Why is that?
Your route handler, function users, expects one parameter.
But your decorator, access_rights, which you wrap around users, isn't passing a user param; it's just passing any params that it received (and, in this case, there aren't any, hence the "0 given" part of the error message).
An example should help clarify. Here's a small but complete working app, based on your original code:
from bottle import route, Bottle
app = Bottle()
def access_rights(permissions):
def decorator(f):
def wrapper(*args, **kwargs):
# Check permissions rights here (not implemented yet)
the_user = 'ron' # hard-coded for this example
return f(the_user, *args, **kwargs)
return wrapper
return decorator
#app.route('/users')
#access_rights({'POST': ['admin']})
def users(user):
return ['hello, {}'.format(user)]
app.run(host='127.0.0.1', port=8080, debug=True)
Note that the only substantial change I made was to have access_rights actually pass a user param on down the line. (How it determines the user is, naturally, up to you--presumably it's the "not implemented yet" part that you called out in your comment).
Hope that helps!
I'm doing a blog for my self from scratch and everything is working, even the sessions.
Now I'm trying to limit the admin with a decorator called #require_login. I think I'm doing something really wrong, besides the fact that is not working.
Here is my decorator:
def requirir_login(func):
request = make_response()
if session['logged_in'] == True:
return func
else:
print("no hay sesion registrada webon")
and here is it used, decorating the admin function:
#app.route("/admin")
#requirir_login
def admin():
users = User.objects
return render_template("admin.html", users=users)
My logic behind this is to check if there is a session then return the admin function. If not I wanted to check in the terminal that message for test purposes.
I haven't decided what to do if there is not a session yet. I would possibly redirect to the log-in page or something.
Your decorator needs to provide a wrapper function, which will be called in place of the decorated function. Only when that wrapper is being called is an actual request being routed and can you test the session:
from functools import wraps
def requirir_login(func):
#wraps(func)
def wrapper(*args, **kwargs):
if session['logged_in']:
return func(*args, **kwargs)
else:
print("no hay sesion registrada webon")
return wrapper
When a decorator is applied, it is called an its return value replaces the decorated function. Here wrapper is returned, so that now becomes your view function.
The wrapper passes on all arguments untouched, making your decorator suitable for any view function regardless of the arguments they expect to be passed in from the route.
I also made a few other changes to improve the functionality of your decorator:
You don't need to test for == True; that is what if is for, to test if the result of an expression is true or not.
I used the #functools.wraps() decorator to give your wrapper the same name and documentation string as the original wrapped view function, always helpful when debugging.
You could indeed use a redirect to the login form if you have one:
return redirect(url_for('login'))
if your login view is named login.
I'm need opportunity to add http-header(X-Accel-Expires) for each add_view.
And for add_static_view.
Ideally would be pass parameter, something like add_view(..., x_accel_expires=100), add_static_view(..., x_accel_expires=100), but the pyramid can't this.
I can do Base View, where add http-header X-Accel-Expires.
I will only need to add an attribute in each view, something like: add_headers = (('X-Accel-Expires', '100'),).
But how can add this header for add_static_view?
For the case of add_view you can use the decorator argument as documented by view configuration parameters:
A dotted Python name to a function (or the function itself) which will be used to decorate the registered view callable. The decorator function will be called with the view callable as a single argument. The view callable it is passed will accept (context, request). The decorator must return a replacement view callable which also accepts (context, request). The decorator may also be an iterable of decorators, in which case they will be applied one after the other to the view, in reverse order.
This is the example given in the documentation:
#view_config(..., decorator=(decorator2, decorator1))
def myview(request):
pass
Is similar to doing:
#view_config(...)
#decorator2
#decorator1
def myview(request):
pass
This would allow you to write the following for example:
def accel_headers_factory(expires=100):
def add_accel_headers(view):
def wrapped_view(context, request):
resp = view(context, request)
resp.headers.append(('X-Accel-Expires', expires))
return wrapped_view
return add_accel_headers
Then use:
#view_config(..., decorator=(accel_headers_factory(500),))
def myview(request):
return {}
This would then always add the X-Accel-Expires header to the response as returned from the view.
Unfortunately it doesn't look like add_static_view allows you to pass it a decorator argument.
I am creating a Python Flask app and created the decorator and views below. The decorator works great when viewing the index, but when you logout and it redirects using the url_for index it throws a builderror. Why would
def logged_in(fn):
def decorator():
if 'email' in session:
return fn()
else:
return render_template('not-allowed.html', page="index")
return decorator
#app.route('/')
#logged_in
def index():
email = session['email']
return render_template('index.html', auth=True, page="index", marks=marks)
#app.route('/sign-out')
def sign_out():
session.pop('email')
print(url_for('index'))
return redirect(url_for('index'))
Any ideas? The error is: BuildError: ('index', {}, None)
The problem here is that decorator() function which you return has different name than the function it is decorating, so the URL builder can't find your index view. You need to use wraps() decorator from functools module to copy the name of the original function. Another problem (which you still have to encounter) is that you don't accept the arguments in your decorator and pass it to the original function. Here's is the corrected decorator:
from functools import wraps
def logged_in(fn):
#wraps(fn)
def decorator(*args, **kwargs):
if 'email' in session:
return fn(*args, **kwargs)
else:
# IMO it's nicer to abort here and handle it in errorhandler.
abort(401)
return decorator
A bit more explanations: in Python decorator is a function which takes another function as its argument and returns a function as its result. So the following
#logged_in
def index(): pass
is essentially identical to
def index(): pass
index = logged_in(index)
The problem in this case was that what your logged_in decorator returns is not the original function, but a wrapper (called decorator in your code), which wraps the original function. This wrapper has a different name (decorator) than the original function it is wrapping. Now app.route() decorator, which you call after logged_in, sees this new function and uses its name (decorator) to register a route for it. Here lies the problem: you want the decorated function to have the same name (index), so it could be used in url_for() to get a route for it. That's why you need to copy the name manually
decorator.__name__ = fn.__name__
or better use update_wrapper and wraps helpers from functools module, which do that and even more for you.