I have a problem with decorator. I'm trying to write my own decorator with optional argument.
This is how its done now:
def CheckPremissions(manager=1):
def wrap(func):
def wrapper(request, *args, **kwargs):
if request.user.is_anonymous():
return HttpResponseRedirect(reverse('login'))
logged_user = getRelatedWorker(request.user)
if (logged_user == None):
return HttpResponseRedirect('accounts/no_worker_error.html')
if self.manager != 0:
try:
dzial = Dzial.objects.get(kierownik=logged_user)
except Dzial.DoesNotExist:
isManager = False
else:
isManager = True
if not isManager:
return HttpResponseRedirect('accounts/denied_logged.html')
return func(request, *args, **kwargs)
return wrapper
return wrap
Code is looking good (for me), but when I use a decorator, I'm getting following error:
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8080/applications/show
Django Version: 1.4.1
Python Version: 2.7.3
Traceback:
File "/home/marcin/projekt/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
188. response = middleware_method(request, response)
File "/home/marcin/projekt/lib/python2.7/site-packages/django/middleware/common.py" in process_response
94. if response.status_code == 404:
Exception Type: AttributeError at /applications/show
Exception Value: 'function' object has no attribute 'status_code'
What am I doing wrong?
I suspect you are applying the decorator incorrectly. You need to call it to specify the manager parameter:
#CheckPremissions()
def someview(request):
pass
or to specify it explicitly:
#CheckPremissions(manager=0)
def someview(request):
pass
You have a different problem in your decorator as well; you refer to self.manager in the code:
if self.manager != 0:
but this is no instance, and there is no self parameter. I think you meant:
if manager:
(where you can test for the variable to be non-zero by treating it as a boolean). Oh, and you may want to fix the spelling of the decorator; you probably meant CheckPermissions instead. :-)
Related
I want my endpoints to throw a 404 status code WITH a message like "parameter foo not found" if the request is missing a required field.
Since i have too many endpoints, adding 3 lines of code (try, except, return) is not a happy solution for me.
That's why i want to make a function similar to get_object_or_404 from django.shortcuts, where just using the function, the request returns 404 instead of just raising an exception and crashing the server.
Taking a look to the get_object_or_404 function, i note that it does raise Http404 and this does work, but it always returns "detail": "Not Found" ignoring any parameter i pass to the exception raising code.
Here's what i did:
class ParamNotFound(Exception):
def __init__(self, param_name):
self.param_name = param_name
self.status_code = 404
def __str__(self) -> str:
return f"Param {self.param_name} not found"
def get_body_param_or_404(params, param_name: str):
param = params.get(param_name, None)
if param is None:
raise ParamNotFound(f"param_name {param_name} not found")
return param
But this does only raise a exception and crash the server if i dont explicitly catch it.
Is there a clean and short way to do this?
Well, the answer was more complicated that i thought.
I ended implementing a middleware, where i can catch my own exception returning a custom JsonResponse.
class ParamMiddleware:
def __init__(self, get_response) -> None:
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, ParamNotFound):
return JsonResponse({"msg": str(exception), "status": 404})
I use tornado and a jwt decorator as below:
def jwtauth(handler_class):
"""
Tornado JWT Auth Decorator
"""
def wrap_execute(handler_execute):
def require_auth(handler, kwargs):
auth = handler.request.headers.get(AUTHORIZATION_HEADER)
if auth:
parts = auth.split()
if not is_valid_header(parts):
return_header_error(handler)
token = parts[1]
try:
result = jwt.decode(
token,
SECRET_KEY,
options=jwt_options
)
except Exception as err:
return_auth_error(handler, str(err))
else:
handler._transforms = []
handler.write(MISSING_AUTHORIZATION_KEY)
handler.finish()
return result
def _execute(self, transforms, *args, **kwargs):
try:
result = require_auth(self, kwargs)
except Exception:
return False
return handler_execute(self, transforms, *args, **kwargs)
return _execute
handler_class._execute = wrap_execute(handler_class._execute)
return handler_class
#jwtauth
class MyHandler(tornado.web.RequestHandler):
def post(self):
unit = json.loads(self.request.body.decode('utf-8'))
# TODO:
# get the result from jwtauth decorator and use it here
print(result) # The result from jwtauth
Now, I'd like to get the jwt decode result and pass into MyHandler for further verification. Can I do it? I checked most of the comment that I can pass the parameter to a decorator but I cannot get from it. Is is possible to pass the jwtauth result to my function?
A class decorator takes your class and spits out a new version of your class (usually with some features added to it). In this case, the #jwtauth decorator takes your class and spits out a new class that makes sure that each request is checked for a valid JWT token in the authorization header. tornado.web.RequestHandler._execute internally calls post. The current behavior is that if the JWT token auth fails, then post will never be called.
In short, you probably want to just raise an error below instead of returning False.
try:
result = require_auth(self, kwargs)
except Exception:
return False
If you need to add more logic about what kind of error to raise, then you probably want to pass that into the decorator along with the class.
I recently upgraded Django from 2.0.7 to 2.1.1, a new error occurs in which I get this error 'functools.partial' object has no attribute '__name__'.
I'd like to understand if my fix is right and what caused this new error to happen, I couldn't find anything on the django release notes related to this issue, maybe I missed it.
decorators.py
def auth0_login_required(function):
def wrap(request, *args, **kwargs):
if request.isAuthenticated or request.user.is_staff:
pass
else:
raise Http404()
return function(request, *args, **kwargs)
wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__ # ERROR HERE
return wrap
How it is used, views.py:
#method_decorator(auth0_login_required, name='dispatch')
class Dashboard(View):
...
For the fix I just removed wrap.__name__ = function.__name__, but I'm not sure if it'll break something else.
Rather than manually copy things across, use the #functools.wraps() decorator to handle this for you:
from functools import wraps
def auth0_login_required(function):
#wraps(function)
def wrap(request, *args, **kwargs):
if request.isAuthenticated or request.user.is_staff:
pass
else:
raise Http404()
return function(request, *args, **kwargs)
return wrap
The #wraps() decorator (via the functools.update_wrapper() function it calls knows how to handle functools.partial objects correctly (or rather, it can handle the fact that functools.partial objects have no __name__ attribute).
It's fine that the wrapped functools.partial() object found on the View class doesn't have a __name__ attribute, what's not fine is that you then don't copy that attribute at all even when you are decorating functions that do have the attribute. If you don't want to use #wraps() you'd have to manually copy the attribute across and handle the exception yourself:
try:
wrap.__name__ = function.__name__
except AttributeError:
pass
try:
wrap.__doc__ = function.__doc__
except AttributeError:
pass
but take into account that this doesn't copy the __qualname__, __module__ and __annotations__ attributes, doesn't handle any custom attributes set on function (which other decorators might rely on). #functools.wraps() does take care of all of those, plus it sets the __wrapped__ attribute on the decorator wrapper function that would let you unwrap the decorator again.
I have a python module security.py which defines a decorator authorized().
I want to test the decorator. The decorator will receive a flask request header.
The decorator is sth like this:
def authorized():
def _authorized(wrapped_func):
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
return _authorized
I want to mock the flask request header using a #patch decorator.The test I wrote is sth like this:
#patch('security.request.headers', Mock(side_effect=lambda *args, **kwargs: MockHeaders({})))
def test_no_authorization_token_in_header(self):
#security.authorized()
def decorated_func(token='abc'):
return access_token
result = decorated_func()
self.assertEqual(result, None)
class MockHeaders(object):
def __init__(self, json_data):
self.json_data=json_data
but I always get the following error:
name = 'request'
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
How should I do it right?
Mock the whole request object to avoid triggering the context lookup:
#patch('security.request')
and build up the mock from there:
#patch('security.request')
def test_no_authorization_token_in_header(self, mock_request):
mock_request.headers= {}
#security.authorized()
def decorated_func(token='abc'):
return token
self.assertRaises(Abort):
result = decorated_func()
Since a missing token results in an Abort exception being raised, you should explicitly test for that. Note that the request.headers attribute is not called anywhere, so side_effect or return_value attributes don't apply here.
I ignored MockHeaders altogether; your decorator is not using json_data and your implementation is lacking a __contains__ method, so in tests wouldn't work on that. A plain dictionary suffices for the current code-under-test.
Side note: authorized is a decorator factory, but it doesn't take any parameters. It'd be clearer if you didn't use a factory there at all. You should also use functools.wraps() to ensure that any metadata other decorators add are properly propagated:
from functools import wraps
def authorized(wrapped_func):
#wraps(wrapped_func)
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
then use the decorator directly (so no call):
#security.authorized
def decorated_func(token='abc'):
return access_token
Can someone please explain to me the difference between these two blocks of code. The first one works while the latter throws the error which I've indicated in the title.
def login_required(method):
#functools.wraps(method)
def wrapper(*args, **kwargs):
if 'username' in flask.session:
return method(*args, **kwargs)
else:
flask.flash("A login is required to see the page!")
return flask.redirect(flask.url_for('index'))
return wrapper
AND
def login_required(method):
#functools.wraps(method)
def wrapper(*args,**kwargs):
if "username" in flask.session:
return method(*args,**kwargs)
else:
flask.flash("A login is required to see the page!")
return flask.redirect(flask.url_for('index'))
return wrapper
In the first code sample, you correctly return the wrapper function at the end of the login_required function.
In the second code sample you've got the return wrapper inside the wrapper function itself. Just de-dent that last line and you should be all set.