34

I am working on a legacy django project, in there somewhere there is a class defined as follows;

from django.http import HttpResponse

class Response(HttpResponse):
    def __init__(self, template='', calling_context='' status=None):
        self.template = template
        self.calling_context = calling_context
        HttpResponse.__init__(self, get_template(template).render(calling_context), status)

and this class is used in views as follows

def some_view(request):
    #do some stuff
    return Response('some_template.html', RequestContext(request, {'some keys': 'some values'}))

this class was mainly created so that they could use it to perform assertions in the unit tests .i.e they are not using django.test.Client to test the views but rather they create a mock request and pass that to view as(calling the view as a callable) in the tests as follows

def test_for_some_view(self):
    mock_request = create_a_mock_request()
    #call the view, as a function
    response = some_view(mock_request) #returns an instance of the response class above
    self.assertEquals('some_template.html', response.template)
    self.assertEquals({}, response.context)

The problem is that half way through the test suite(quite a huge test suite), some tests begin blowing up when executing the

return Response('some_template.html', RequestContext(request, {'some keys': 'some values'}))

and the stack trace is

self.template = template
AttributeError: can't set attribute 

the full stack trace looks something like

======================================================================
ERROR: test_should_list_all_users_for_that_specific_sales_office
 ----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/austiine/Projects/mped/console/metrics/tests/unit/views/sales_office_views_test.py",   line 106, in test_should_list_all_users_for_that_specific_sales_office
    response = show(request, sales_office_id=sales_office.id)
File "/Users/austiine/Projects/mped/console/metrics/views/sales_office_views.py", line 63, in show
    "sales_office_users": sales_office_users}))
File "/Users/austiine/Projects/mped/console/metrics/utils/response.py", line 9, in __init__
    self.template = template
    AttributeError: can't set attribute

the actual failing test is

def test_should_list_all_users_for_that_specific_sales_office(self):
    user_company = CompanyFactory.create()
    request = self.mock_request(user_company)
    #some other stuff

    #calling the view
    response = show(request, sales_office_id=sales_office.id)
    self.assertIn(user, response.calling_context["sales_office_users"])
    self.assertNotIn(user2, response.calling_context["sales_office_users"])

code for the show view

def show(request, sales_office_id):
    user = request.user
    sales_office = []
    sales_office_users = []
    associated_market_names = []
    try:
        sales_office = SalesOffice.objects.get(id=sales_office_id)
        sales_office_users = User.objects.filter(userprofile__sales_office=sales_office)
        associated_market_names = Market.objects.filter(id__in=           (sales_office.associated_markets.all())).values_list("name", flat=True)
        if user.groups.all()[0].name == UserProfile.COMPANY_AO:
            associated_market_names = [market.name for market in sales_office.get_sales_office_user_specific_markets(user)]
        except:
            pass
    return Response("sales_office/show.html", RequestContext(request, {'keys': 'values'}))
austiine
  • 470
  • 1
  • 5
  • 12
  • Can you show us the whole traceback error message? – Stephen Lin Dec 10 '14 at 09:03
  • the biggest part of the stack trace is just a bunch of absolute paths for test file where the code blows up, the view and the actual file where the exception originates nevertheless i'll paste here – austiine Dec 10 '14 at 09:13
  • Try to print type(template) before self.template = template and tell me the print info. – Stephen Lin Dec 10 '14 at 09:30
  • print type(template) returns and print type(self.template) returns *** AttributeError: 'Response' object has no attribute 'templates' – austiine Dec 10 '14 at 09:52
  • It seams to me that `self` in response have a read only attribute named `template`. Can you post `show()` method of `sales_office_views.py` module? – Michele d'Amico Dec 10 '14 at 13:26
  • @Micheled'Amico yeah seems so to me as well but when i stick a pdb in the constructor and execute self.template i get "*** AttributeError: 'Response' object has no attribute 'templates" instead of something like *** AttributeError: 'Response' object has no attribute 'template' – austiine Dec 10 '14 at 16:18
  • @Micheled'Amico added the code for show at the bottom – austiine Dec 10 '14 at 16:24
  • repr(type(self).__dict__) will show the attributes of the Request instance. Is 'template' one of them? – XORcist Dec 10 '14 at 16:38
  • @XORcist no, template does not exist when i put a pdb as the first line of the __init__ method and i execute that line, i "['__module__', '__doc__', '__init__']" and self.__dict__ is {}. – austiine Dec 11 '14 at 08:39
  • may be one thing i didnt clarify is that some test for views which are using the response class pass while others fail, thats whats weird – austiine Dec 11 '14 at 08:40

3 Answers3

115

This answer doesn't address the specifics of this question, but explains the underlying issue. This specific exception "AttributeError: can't set attribute" is raised (see source) when the attribute you're attempting to change is actually a property that doesn't have a setter. If you have access to the library's code, adding a setter would solve the problem.

EDIT: updated source link to new location in the code.

Edit2:

Example of a setter:

class MAMLMetaLearner(nn.Module):
    def __init__(
            self,
            args,
            base_model,

            inner_debug=False,
            target_type='classification'
    ):
        super().__init__()
        self.args = args  # args for experiment
        self.base_model = base_model
        assert base_model is args.model

        self.inner_debug = inner_debug
        self.target_type = target_type

    @property
    def lr_inner(self) -> float:
        return self.args.inner_lr

    @lr_inner.setter
    def lr_inner(self, new_val: float):
        self.args.inner_lr = new_val
Charlie Parker
  • 5,884
  • 57
  • 198
  • 323
yoniLavi
  • 2,624
  • 1
  • 24
  • 30
  • 11
    I suppose it should be considered an achievement, having my answer get a negative score, while all the others are at zero. I'm "absolutely" better. :) – yoniLavi Dec 29 '14 at 15:25
  • 1
    "source" link is 404, but in the current code I found properties [here as python](https://github.com/python/cpython/blob/5ff7132313eb651107b179d20218dfe5d4e47f13/Objects/descrobject.c#L1278), or as the [actual C code](https://github.com/python/cpython/blob/5ff7132313eb651107b179d20218dfe5d4e47f13/Objects/descrobject.c#L1415) – luckydonald Jun 21 '17 at 16:44
  • Is it possible to have a property without a setter? – Stevoisiak Jan 31 '18 at 17:14
  • @StevenVascellaro, sure, that's exactly the issue in the question. A property is read-only unless a setter is explicitly provided. – yoniLavi Jan 31 '18 at 22:25
  • I don't understand, why do I need to set a setter I thought in python we were free to do whatever we wanted...I've set random fields before why is this one giving me issues? https://github.com/facebookresearch/higher/issues/96#issuecomment-771982784 – Charlie Parker Feb 02 '21 at 21:22
  • 2
    @CharlieParker, you're right in general, but in this case, by using the `@property` decorator, the library's developer intentionally limited the ability to set that attribute. Here's a relevant question for a similar use case - https://softwareengineering.stackexchange.com/questions/371540/should-a-python-property-decorator-be-used-without-a-setter-or-deleter – yoniLavi Feb 04 '21 at 01:45
  • 1
    @yoniLavi those stupid developers. Why would they do that!? (hint I did it... :/ , now I'm wondering why I did it and how to fix it...) – Charlie Parker Feb 09 '22 at 19:45
  • can you provide an example of adding a setter in python? Is it with a dectorator or something so that the `self.x = y` work? – Charlie Parker Feb 09 '22 at 19:51
0

It looks like you don't use self.template in Response class. Try like this:

class Response(HttpResponse):
    def __init__(self, template='', calling_context='' status=None):
        HttpResponse.__init__(self, get_template(template).render(calling_context), status)
Stephen Lin
  • 4,852
  • 1
  • 13
  • 26
  • yes self.template is not used inside in the Response class, its used in tests for assertions like this; self.assertEquals('some_template.html', response.template) the same with calling_context – austiine Dec 11 '14 at 07:04
  • the whole response class is there for use in tests – austiine Dec 11 '14 at 07:05
  • @austiine So after deleting two lines, was there still the same error? – Stephen Lin Dec 11 '14 at 08:05
  • no that error goes away but my tests fail because they except response to have template and calling_context attributes – austiine Dec 11 '14 at 08:15
0

I took a look to django source code I've no idea where template or templates attribute come from in HttpResponse. But I can propose to you to change your test approach and migrate to mock framework. You can rewrite your test like:

@patch("qualified_path_of_response_module.response.Response", spec=Response)
def test_should_list_all_users_for_that_specific_sales_office(self,mock_resp):
    user_company = CompanyFactory.create()
    request = self.mock_request(user_company)
    #some other stuff

    #calling the view
    response = show(request, sales_office_id=sales_office.id)
    self.assertTrue(mock_resp.called)
    context = mock_resp.call_args[0][2]
    self.assertIn(user, context["sales_office_users"])
    self.assertNotIn(user2, context["sales_office_users"])

@patch decorator replace your Response() class by a MagicMock() and pass it to your test method as mock_resp variable. You can also use patch as context manager by with construct but decorators are the cleaner way to do it. I don't know if Response is just a stub class for testing but in that case you can patch directly HttpResponce, but it depends from your code.

You can find details about call_args here. Maybe you need to use spec attribute because django make some type checking... but try with and without it (I'm not a django expert). Explore mock framework: it'll give to you lot of powerful tools to make simple tests.

Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76