[Django] #20636: AttributeError: 'Settings' object has no attribute '_original_allowed_hosts'

728 views
Skip to first unread message

Django

unread,
Jun 20, 2013, 5:59:31 PM6/20/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
---------------------------------------------+------------------------
Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
---------------------------------------------+------------------------
When running the tests for http://github.com/aaugustin/django-sesame
against stable/1.5.x, I'm hitting this exception after they complete:
{{{
Traceback (most recent call last):
File "/Users/myk/Documents/dev/django/django/core/management/base.py",
line 222, in run_from_argv
self.execute(*args, **options.__dict__)
File
"/Users/myk/Documents/dev/django/django/core/management/commands/test.py",
line 72, in execute
super(Command, self).execute(*args, **options)
File "/Users/myk/Documents/dev/django/django/core/management/base.py",
line 255, in execute
output = self.handle(*args, **options)
File
"/Users/myk/Documents/dev/django/django/core/management/commands/test.py",
line 89, in handle
failures = test_runner.run_tests(test_labels)
File "/Users/myk/Documents/dev/django/django/test/simple.py", line 370,
in run_tests
self.teardown_test_environment()
File "/Users/myk/Documents/dev/django/django/test/simple.py", line 341,
in teardown_test_environment
teardown_test_environment()
File "/Users/myk/Documents/dev/django/django/test/utils.py", line 104,
in teardown_test_environment
settings.ALLOWED_HOSTS = settings._original_allowed_hosts
File "/Users/myk/Documents/dev/django/django/conf/__init__.py", line 54,
in __getattr__
return getattr(self._wrapped, name)
AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
}}}

I'm positive the tests used to work, and this application is only a few
dozen lines of code and doesn't do anything really weird with settings.

It uses `@override_settings`, but I've tried to reproduce a similar
pattern in Django's test suite and I didn't hit the bug:
{{{
from django.test.utils import override_settings

@override_settings(MIDDLEWARE_CLASSES=('a',))
class Decorated(TestCase):
def test_basic_math(self):
self.assertEqual(2 + 2, 4)

@override_settings(MIDDLEWARE_CLASSES=('b',))
class DoubleDecorated(TestCase):
pass
}}}

I'm not sure why this happens, but since something that used to work now
fails with a cryptic error, we should probably do something about it.

--
Ticket URL: <https://code.djangoproject.com/ticket/20636>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jun 20, 2013, 10:46:46 PM6/20/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------

Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------
Changes (by charettes):

* cc: charettes (added)


Comment:

I hit a similar issue last week. Some people on IRC suggested that it
might be due to an unbalanced `override_setting` but it wasn't the case.

Somehow I ended up with two `django.conf.Settings` instance, one that was
assigned the `_original_allowed_hosts` attribute while the test suite
teardown mechanism attempted to delete it from the second one thus raising
the same exception.

I suggest you place `pdb` a breakpoint in `django.conf.Settings.__init__`
and make sure only one instance is created. Hope that helps.

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:1>

Django

unread,
Jun 21, 2013, 3:36:01 AM6/21/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------

Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by aaugustin):

I did some debug and could confirm `_original_allowed_hosts` was set, and
later not found, on the same object (same id). Something must have deleted
it in the mean time.

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:2>

Django

unread,
Jun 21, 2013, 9:46:35 AM6/21/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------

Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by claudep):

Here is what seems to happen

a) `get_commands()` accesses `settings.INSTALLED_APPS`, which triggers
`Settings.__init__` (instance A)
b) inside `Settings.__init__`, import of settings module
(`sesame.tests.settings`)
c) as settings is inside tests module, this is triggering a new import
chain (sesame.tests, django, etc.) ... until django.db accesses settings
d) `LazySettings._wrapped` is still empty (remember we are still in
`Settings.__init__`), hence creation of a new Settings instance (B).
e) still inside the import process, `override_settings` is setting its
wrapped attribute to Settings B
f) initial `Settings.__init__` finishes, so `settings._wrapped` is
instance A
g) `setup_test_environment` set `_original_allowed_hosts` on Settings A
h) after tests finishes, `override_settings` restore `settings._wrapped`
to Settings B (value of `override_settings.wrapped`, see e)
i) `teardown_test_environment` is complaining that Settings B has no
`_original_allowed_hosts`.

We could point at several problems in this process:

* `override_settings.wrapped` could be set in `enable()` instead of in
`__init__` (to be tested, but may be #20290)
* Django should not access settings at module import time (I already
addressed this for django.db in 1.6)
* Do not put your settings modules inside another module which imports
much of Django

I let you choose which to address first...

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:3>

Django

unread,
Jun 21, 2013, 11:34:26 AM6/21/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------

Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by charettes):

@claudep I hit the exact same issue you described but I don't think it's
the case with @aaugustin since the `_original_allowed_hosts` is set and
deleted on the same instance of `Settings`.

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:4>

Django

unread,
Jun 24, 2013, 1:45:28 PM6/24/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------

Reporter: aaugustin | Owner: nobody
Type: Bug | Status: new
Component: Testing framework | Version: master
Severity: Release blocker | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by aaugustin):

I double-checked in case I had missed something, and I confirm that steps
g) and i) operate on the same settings object. However, its `_wrapped`
attribute has changed!

I think I'm seeing a variant of the scenario described by Claude, and the
root cause lies in his first bullet point: `override_settings.wrapped` is
set in `__init__` rather than in `enable`, which is indeed #20290.

I see three options:

- backport #20290,
- store `_original_allowed_hosts` somewhere else — like
`original_email_backend` which is stored on the `django.core.mail` module.
- catch and drop the AttributeError — after all we're in test teardown, we
don't really care.

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:5>

Django

unread,
Jun 24, 2013, 2:40:57 PM6/24/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------
Reporter: aaugustin | Owner: nobody
Type: Bug | Status: closed

Component: Testing framework | Version: master
Severity: Release blocker | Resolution: fixed

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------
Changes (by Aymeric Augustin <aymeric.augustin@…>):

* status: new => closed
* resolution: => fixed


Comment:

In [changeset:"02619227a9271047c7d4b62a6aec744ca17f7270"]:
{{{
#!CommitTicketReference repository=""
revision="02619227a9271047c7d4b62a6aec744ca17f7270"
[1.5.x] Fixed #20636 -- Stopped stuffing values in the settings.

In Django < 1.6, override_settings restores the settings module that was
active when the override_settings call was executed, not when it was
run. This can make a difference when override_settings is applied to a
class, since it's executed when the module is imported, not when the
test case is run.

In addition, if the settings module for tests is stored alongside the
tests themselves, importing the settings module can trigger an import
of the tests. Since the settings module isn't fully imported yet,
class-level override_settings statements may store a reference to an
incorrect settings module. Eventually this will result in a crash during
test teardown because the settings module restored by override_settings
won't the one that was active during test setup.

While Django should prevent this situation in the future by failing
loudly in such dubious import sequences, that change won't be backported
to 1.5 and 1.4. However, these versions received the "allowed hosts"
patch and they're prone to "AttributeError: 'Settings' object has no
attribute '_original_allowed_hosts'". To mitigate this regression, this
commits stuffs _original_allowed_hosts on a random module instead of the
settings module.

This problem shouldn't occur in Django 1.6, see #20290, but this patch
will be forward-ported for extra safety.

Also tweaked backup variable names for consistency.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:6>

Django

unread,
Jun 24, 2013, 2:44:35 PM6/24/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------
Reporter: aaugustin | Owner: nobody
Type: Bug | Status: closed

Component: Testing framework | Version: master
Severity: Release blocker | Resolution: fixed

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by Aymeric Augustin <aymeric.augustin@…>):

In [changeset:"5a6f12182ef14883df6534dc3465059b78f54a1c"]:
{{{
#!CommitTicketReference repository=""
revision="5a6f12182ef14883df6534dc3465059b78f54a1c"


Fixed #20636 -- Stopped stuffing values in the settings.

In Django < 1.6, override_settings restores the settings module that was
active when the override_settings call was executed, not when it was
run. This can make a difference when override_settings is applied to a
class, since it's executed when the module is imported, not when the
test case is run.

In addition, if the settings module for tests is stored alongside the
tests themselves, importing the settings module can trigger an import
of the tests. Since the settings module isn't fully imported yet,
class-level override_settings statements may store a reference to an
incorrect settings module. Eventually this will result in a crash during
test teardown because the settings module restored by override_settings
won't the one that was active during test setup.

While Django should prevent this situation in the future by failing
loudly in such dubious import sequences, that change won't be backported
to 1.5 and 1.4. However, these versions received the "allowed hosts"
patch and they're prone to "AttributeError: 'Settings' object has no
attribute '_original_allowed_hosts'". To mitigate this regression, this
commits stuffs _original_allowed_hosts on a random module instead of the
settings module.

This problem shouldn't occur in Django 1.6, see #20290, but this patch
will be forward-ported for extra safety.

Also tweaked backup variable names for consistency.

Forward port of 0261922 from stable/1.5.x.

Conflicts:
django/test/utils.py
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:8>

Django

unread,
Jun 24, 2013, 2:44:35 PM6/24/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------
Reporter: aaugustin | Owner: nobody
Type: Bug | Status: closed

Component: Testing framework | Version: master
Severity: Release blocker | Resolution: fixed

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by Aymeric Augustin <aymeric.augustin@…>):

In [changeset:"e3b6fed320f6e67b7e5dc28bab610f342a81f7d7"]:
{{{
#!CommitTicketReference repository=""
revision="e3b6fed320f6e67b7e5dc28bab610f342a81f7d7"
[1.4.x] Fixed #20636 -- Stopped stuffing values in the settings.

In Django < 1.6, override_settings restores the settings module that was
active when the override_settings call was executed, not when it was
run. This can make a difference when override_settings is applied to a
class, since it's executed when the module is imported, not when the
test case is run.

In addition, if the settings module for tests is stored alongside the
tests themselves, importing the settings module can trigger an import
of the tests. Since the settings module isn't fully imported yet,
class-level override_settings statements may store a reference to an
incorrect settings module. Eventually this will result in a crash during
test teardown because the settings module restored by override_settings
won't the one that was active during test setup.

While Django should prevent this situation in the future by failing
loudly in such dubious import sequences, that change won't be backported
to 1.5 and 1.4. However, these versions received the "allowed hosts"
patch and they're prone to "AttributeError: 'Settings' object has no
attribute '_original_allowed_hosts'". To mitigate this regression, this
commits stuffs _original_allowed_hosts on a random module instead of the
settings module.

This problem shouldn't occur in Django 1.6, see #20290, but this patch
will be forward-ported for extra safety.

Also tweaked backup variable names for consistency.

Backport of 0261922 from stable/1.5.x.

Conflicts:
django/test/utils.py
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:7>

Django

unread,
Jun 24, 2013, 3:01:21 PM6/24/13
to django-...@googlegroups.com
#20636: AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'
-----------------------------------+--------------------------------------
Reporter: aaugustin | Owner: nobody
Type: Bug | Status: closed

Component: Testing framework | Version: master
Severity: Release blocker | Resolution: fixed

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

Comment (by Aymeric Augustin <aymeric.augustin@…>):

In [changeset:"e2b86571bfa3503fe43adfa92e9c9f4271a7a135"]:
{{{
#!CommitTicketReference repository=""
revision="e2b86571bfa3503fe43adfa92e9c9f4271a7a135"
[1.4.x] Fixed oversight in e3b6fed3. Refs #20636.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/20636#comment:9>

Reply all
Reply to author
Forward
0 new messages