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.
Related
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
def register_processor2(processor_name='SomeProcessor'):
def decorator(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = processor_name
transaction_class = Transaction
#staticmethod
def setup(data=None):
pass
#wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
return decorator
def register_processor(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = 'SomeProcessor'
transaction_class = Transaction
#staticmethod
def setup(data=None):
pass
#wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
class TestPaymentMethodEndpoints(APITestCase):
#register_processor
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
Ok so the decorator register_processor works as expected. And the test fails, but I want to make the name of the inner class customizable so I went for a decorator factory implementation instead.
The thing is when running the test decorated with register_processor2 I get the following:
AttributeError: 'TestPaymentMethodEndpoints' object has no attribute '__name__'
This is from #wraps(func), my question is why is func here an instance of TestPaymentMethodEndpoints, and not the bound method?
Also if I remove the #wraps decorator then the test runs and passes.
I'd expect that the test would not be discovered as func_wrapper does not start with test_* and even if it is discovered then it should fail.
Any insight on what is happening and how I'd go about doing this?
EDIT
So I figured it out even if the decorator factory has arguments that have default values you still need to place () when calling it.
But would still love to hear an explanation of what happened in case of the tests passing / getting discovered in the first place.
class TestPaymentMethodEndpoints(APITestCase):
#register_processor()
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
Makes sense now that I think about it :D, gosh you learn something new each day!
I think you're now asking "how come the unittest module can find test cases that have been wrapped in functions with names that don't start test?"
The answer to that is because unittest doesn't use the names of the functions to find the methods to run, it uses the attribute names of the test case classes to find them.
So try running the following code:
from unittest import TestCase
def apply_fixture(func):
def wrap_with_fixture(self):
print('setting up fixture...')
try:
func(self)
finally:
print('tearing down fixture')
return wrap_with_fixture
class MyTestCase(TestCase):
#apply_fixture
def test_something(self):
print('run test')
print('Attributes of MyTestCase: %s' % dir(MyTestCase))
print('test_something method: %s' % MyTestCase.test_something)
mtc = MyTestCase()
mtc.test_something()
You will see that the output from dir contains the name test_something:
Attributes of MyTestCase: ['__call__', ...lots of things..., 'test_something']
but that the value of that attribute is the wrapping function wrap_with_fixture:
test_something method: <function apply_fixture.<locals>.wrap_with_fixture at 0x10d90aea0>
This makes sense when you consider that when you create a function you are both creating a function with the name provided and a local variable with the same name, and that the decorator # syntax is just syntactic sugar, so the following would have been an equally valid albeit longer-winded way of creating your test case class:
class MyTestCase(TestCase):
def test_something(self):
print('run test')
# Overwrite existing 'local' (or 'class' variable in this context)
# with a new value. We haven't deleted the test_something function
# which still exists but now is owned by the function we've created.
test_something = apply_fixture(test_something)
I try to mock the constructor of a class like this https://stackoverflow.com/a/17950141/633961
class MockedHttpResponse(django.http.response.HttpResponseBase):
def check(self, *args, **kwargs):
status=kwargs.get('status')
if status is None and self.status_code==200:
return # default
if not status==self.status_code:
raise self.AssertionErrorStatusCode('%s!=%s' % (self.status_code, status))
def __init__(self, *args, **kwargs):
self.check(*args, **kwargs)
django.http.response.HttpResponseBase.__init__(self, *args, **kwargs)
class AssertionErrorStatusCode(AssertionError):
pass
usage in test:
def test_assert_http_response_status__with_self_mocking(self):
from django.http import HttpResponse
with mock.patch('django.http.response.HttpResponse', testutils.MockedHttpResponse):
HttpResponse(status=200)
But I get this exception:
Traceback (most recent call last):
File "/home/foo_eins_di514/src/djangotools/djangotools/tests/unit/utils/test_testutils.py", line 129, in test_assert_http_response_status__with_self_mocking
HttpResponse(status=200)
File "/home/foo_eins_di514/lib/python2.7/site-packages/django/http/response.py", line 258, in __init__
super(HttpResponse, self).__init__(*args, **kwargs)
TypeError: super(type, obj): obj must be an instance or subtype of type
How can I mock a class and modify its __init__() method?
I can't say for sure since I'm not an expert on how mock works, but unless it's doing some very impressive gymnastics, you've statically bound the original django.http.HttpResponse object to HttpResponse within the test module scope (from django.http import HttpResponse). Unless mock is dynamically modifying locals() \ globals() within the context manager to patch over aliased references to the patch target, no amount of patching on the module itself will impact the HttpResponse object you are using in the test. I think at least one step you need to take is to reference the class from the module explicitly within the patch context manager, though it looks like you could also use with mock.patch('django.http.response.HttpResponse', testutils.MockedHttpResponse) as HttpResponse: to get the desired result. I think you may also want to use new_callable=testutils.MockedHttpResponse as a kwarg to patch.
In a prior question I asked, where a Manager's method looked like:
def activate(key):
try:
profile = self.get(key=key)
except self.model.DoesNotExist:
return None
if not profile.key_expired():
# -> Activate user
return user
return None
It was suggested to use self.get_query_set().get(key=key) instead of self.get(key=key) within the manager method. I was wondering what the reason for this is, as the former seems much more verbose?
I guess the author just likes being verbose. There is no difference. The Manager class' get method is defined as:
def get(self, *args, **kwargs):
return self.get_query_set().get(*args, **kwargs)
You can see it for yourself in django/db/models/manager.py
On views that allow updating/deleting objects, I need a decorator that verifies that the object to be edited belongs to a group(model "loja). Both defined in the url:
/[slug model loja--s_loja]/[viewname-ex:addmenu]/[object id--obj_id]
Because the model of the object can vary, the decorator the model of the object as an argument. Every model that may be passed as an argument has a foreign key to the model "loja" named loja.
The decorator:
def acesso_objecto(modelo):
def wrap(f):
def wrapper(*args, **kwargs):
s_loja = kwargs['s_loja']
obj_id = kwargs['obj_id']
objecto = get_object_or_404(modelo, pk=obj_id)
loja = get_object_or_404(Loja, slug=s_loja)
if objecto.loja is not loja:
raise Http404
else:
return f(*args, **kwargs)
return wrapper
return wrap
Basically, unless the group "loja" and the object exists and the object belongs to that group a 404 error should be raised.
Without the decorator the view works fine, but the decorator always raises 404 because the if statement is always true even when it shouldn't be. If I use the loja.id or loja.slug for verification it works as THEY ARE related, but this function always seems to fail and I have no idea why.
Replace is not with !=.
not loja is evaluating to True, and the if statement is testing the equality between objecto.loja and True.