In the old days I had exceptions, now it's mainly subtle breakages of
logging configuration.
I couldn't find, in the issue tracker or the dev mailing list statements
about this subject, others than request from other users encountering the
problem.
For example this ticket concerned script+importable modules :
https://code.djangoproject.com/ticket/26152
The latest case in date for me is pytest-django having troubles with these
multiple setup() calls : https://github.com/pytest-dev/pytest-
django/issues/531 , due to multiple fixtures attempting this auto-setup.
Would it be OK to make django.setup() idempotent, or even expose a
"is_ready" flag for easier introspection ?
--
Ticket URL: <https://code.djangoproject.com/ticket/28752>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* status: new => closed
* resolution: => duplicate
Comment:
I believe this is addressed in Django 2.0 by #27176. If not, please reopen
with a minimal project that reproduces the problem you're encountering.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:1>
* status: closed => new
* resolution: duplicate =>
Old description:
> I've been bitten numerous times by the impredictable behaviour of django
> when django.setup() was called numerous times.
>
> In the old days I had exceptions, now it's mainly subtle breakages of
> logging configuration.
>
> I couldn't find, in the issue tracker or the dev mailing list statements
> about this subject, others than request from other users encountering the
> problem.
>
> For example this ticket concerned script+importable modules :
> https://code.djangoproject.com/ticket/26152
>
> The latest case in date for me is pytest-django having troubles with
> these multiple setup() calls : https://github.com/pytest-dev/pytest-
> django/issues/531 , due to multiple fixtures attempting this auto-setup.
>
> Would it be OK to make django.setup() idempotent, or even expose a
> "is_ready" flag for easier introspection ?
New description:
Alas the ticket #27176 dealt only with the "apps.populate" part, but the
whole setup() must be protected, else we'll always have weird side effects
on duplicate calls.
Here is a testcase showing the reset of the "django" logger level, for
example, when calling setup() multiple times.
Depending on the exact LOGGING dict (with disable_existing_loggers etc.),
even the shape of the logging tree might be changed.
{{{
def test_duplicated_setup_calls(self):
import django, logging
#from django.conf import settings
#print(settings.LOGGING_CONFIG, settings.LOGGING)
django.setup()
logging.getLogger('django').setLevel(logging.DEBUG)
assert logging.getLogger('django').level == logging.DEBUG
django.setup()
assert logging.getLogger('django').level == logging.DEBUG #
raises
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:2>
Comment (by Tim Graham):
Thanks. Can you explain the use case for calling `django.setup()` multiple
times?
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:3>
Old description:
> Alas the ticket #27176 dealt only with the "apps.populate" part, but the
> whole setup() must be protected, else we'll always have weird side
> effects on duplicate calls.
>
> Here is a testcase showing the reset of the "django" logger level, for
> example, when calling setup() multiple times.
>
> Depending on the exact LOGGING dict (with disable_existing_loggers etc.),
> even the shape of the logging tree might be changed.
>
> {{{
>
> def test_duplicated_setup_calls(self):
> import django, logging
> #from django.conf import settings
> #print(settings.LOGGING_CONFIG, settings.LOGGING)
>
> django.setup()
> logging.getLogger('django').setLevel(logging.DEBUG)
> assert logging.getLogger('django').level == logging.DEBUG
>
> django.setup()
> assert logging.getLogger('django').level == logging.DEBUG #
> raises
>
> }}}
New description:
Calling django.setup() multiple times is useless, BUT it can happen in
lots of cases, that's why imho this case should be handled by the
framework to avoid nasty side effects.
These "duplicate calls" often involve the collision between manage.py
commands, tests, custom scripts, and external launchers like pytest-
django. Plus maybe some corner cases when unittest-style TestCases and
pytest-style test functions are mixed in the same project.
Users have to do a real gym to call setup() "at some moment" in all these
use cases, yet try to prevent multiple calls of this initialization step
(like the 'if__name__ == "main"' protection). So far my only way out was
often to check for (not really undocumented) states of the framework
before calling setup().
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:4>
Old description:
> Calling django.setup() multiple times is useless, BUT it can happen in
> lots of cases, that's why imho this case should be handled by the
> framework to avoid nasty side effects.
>
> These "duplicate calls" often involve the collision between manage.py
> commands, tests, custom scripts, and external launchers like pytest-
> django. Plus maybe some corner cases when unittest-style TestCases and
> pytest-style test functions are mixed in the same project.
>
> Users have to do a real gym to call setup() "at some moment" in all these
> use cases, yet try to prevent multiple calls of this initialization step
> (like the 'if__name__ == "main"' protection). So far my only way out was
> often to check for (not really undocumented) states of the framework
> before calling setup().
New description:
I've been bitten numerous times by the impredictable behaviour of django
when django.setup() was called numerous times.
In the old days I had exceptions, now it's mainly subtle breakages of
logging configuration.
I couldn't find, in the issue tracker or the dev mailing list statements
about this subject, others than request from other users encountering the
problem.
For example this ticket concerned script+importable modules :
https://code.djangoproject.com/ticket/26152
The latest case in date for me is pytest-django having troubles with
these multiple setup() calls : https://github.com/pytest-dev/pytest-
django/issues/531 , due to multiple fixtures attempting this auto-setup.
Would it be OK to make django.setup() idempotent, or even expose a
"is_ready" flag for easier introspection ?
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:5>
Old description:
> I've been bitten numerous times by the impredictable behaviour of django
> when django.setup() was called numerous times.
>
> In the old days I had exceptions, now it's mainly subtle breakages of
> logging configuration.
>
> I couldn't find, in the issue tracker or the dev mailing list statements
> about this subject, others than request from other users encountering the
> problem.
>
> For example this ticket concerned script+importable modules :
> https://code.djangoproject.com/ticket/26152
>
> The latest case in date for me is pytest-django having troubles with
> these multiple setup() calls : https://github.com/pytest-dev/pytest-
> django/issues/531 , due to multiple fixtures attempting this auto-setup.
>
> Would it be OK to make django.setup() idempotent, or even expose a
> "is_ready" flag for easier introspection ?
New description:
I've been bitten numerous times by the impredictable behaviour of django
when django.setup() was called numerous times.
In the old days I had exceptions, now it's mainly subtle breakages of
logging configuration.
I couldn't find, in the issue tracker or the dev mailing list statements
about this subject, others than request from other users encountering the
problem.
For example this ticket concerned script+importable modules :
https://code.djangoproject.com/ticket/26152
The latest case in date for me is pytest-django having troubles with
these multiple setup() calls : https://github.com/pytest-dev/pytest-
django/issues/531 , due to multiple fixtures attempting this auto-setup.
Would it be OK to make django.setup() idempotent, or even expose a
"is_ready" flag for easier introspection ?
-- here are some updates, comments get rejected as spam --
Calling django.setup() multiple times is useless, BUT it can happen in
lots of cases, that's why imho this case should be handled by the
framework to avoid nasty side effects.
These "duplicate calls" often involve the collision between manage.py
commands, tests, custom scripts, and external launchers like pytest-
django. Plus maybe some corner cases when unittest-style TestCases and
pytest-style test functions are mixed in the same project.
Users have to do a real gym to call setup() "at some moment" in all these
use cases, yet try to prevent multiple calls of this initialization step
(like the 'if__name__ == "main"' protection). So far my only way out was
often to check for (not really undocumented) states of the framework
before calling setup().
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:6>
Comment (by pascal chambon):
My comments get marked as spam, I updated the description above.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:7>
* cc: Aymeric Augustin (added)
Old description:
> I've been bitten numerous times by the impredictable behaviour of django
> when django.setup() was called numerous times.
>
> In the old days I had exceptions, now it's mainly subtle breakages of
> logging configuration.
>
> I couldn't find, in the issue tracker or the dev mailing list statements
> about this subject, others than request from other users encountering the
> problem.
>
> For example this ticket concerned script+importable modules :
> https://code.djangoproject.com/ticket/26152
>
> The latest case in date for me is pytest-django having troubles with
> these multiple setup() calls : https://github.com/pytest-dev/pytest-
> django/issues/531 , due to multiple fixtures attempting this auto-setup.
>
> Would it be OK to make django.setup() idempotent, or even expose a
> "is_ready" flag for easier introspection ?
>
> -- here are some updates, comments get rejected as spam --
>
>
> Calling django.setup() multiple times is useless, BUT it can happen in
> lots of cases, that's why imho this case should be handled by the
> framework to avoid nasty side effects.
>
> These "duplicate calls" often involve the collision between manage.py
> commands, tests, custom scripts, and external launchers like pytest-
> django. Plus maybe some corner cases when unittest-style TestCases and
> pytest-style test functions are mixed in the same project.
>
> Users have to do a real gym to call setup() "at some moment" in all these
> use cases, yet try to prevent multiple calls of this initialization step
> (like the 'if__name__ == "main"' protection). So far my only way out was
> often to check for (not really undocumented) states of the framework
> before calling setup().
New description:
I've been bitten numerous times by the impredictable behaviour of django
when django.setup() was called numerous times.
In the old days I had exceptions, now it's mainly subtle breakages of
logging configuration.
I couldn't find, in the issue tracker or the dev mailing list statements
about this subject, others than request from other users encountering the
problem.
For example #26152 concerned script+importable modules.
The latest case in date for me is pytest-django having troubles with these
multiple setup() calls : https://github.com/pytest-dev/pytest-
django/issues/531 , due to multiple fixtures attempting this auto-setup.
Would it be OK to make django.setup() idempotent, or even expose an
"is_ready" flag for easier introspection ?
-- here are some updates, comments get rejected as spam --
Calling django.setup() multiple times is useless, BUT it can happen in
lots of cases, that's why imho this case should be handled by the
framework to avoid nasty side effects.
These "duplicate calls" often involve the collision between manage.py
commands, tests, custom scripts, and external launchers like pytest-
django. Plus maybe some corner cases when unittest-style TestCases and
pytest-style test functions are mixed in the same project.
Users have to do a real gym to call setup() "at some moment" in all these
use cases, yet try to prevent multiple calls of this initialization step
(like the 'if__name__ == "main"' protection). So far my only way out was
often to check for (not really undocumented) states of the framework
before calling setup().
--
Comment:
Aymeric, any input?
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:8>
Comment (by Aymeric Augustin):
`django.setup()` is already idempotent. However it isn't reentrant — I
believe that's what you're actually asking for — and it cannot be made
reentrant without breaking its invariants. See #27176 for details.
I don't think we should make changes to Django for accomodating pytest
code that does `django.setup = lambda: None`.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:9>
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:10>
Comment (by pascal chambon):
OK I think my vocabulary was wrong, it's not (really) an idempotence
problem, since django.setup() does more or less the same things on both
calls (just skipping apps population phase on the second).
It's not a reentrancy problem, i.e not a problem with multiple threads (or
signal interrupts) entering django.setup() concurrently.
It's really just a problem of "multiple successive calls of
django.setup()", which are doing silent errors and weird modifications,
simply because only the first call of django.setup() makes sense.
Raising an exception on subsequent calls would be a possibility, but it
would be a useless hassle, since users just want is to ensure that django
was initialized at some point.
That's why I propose we just do a no-op on subsequent calls to
django.setup().
(By teh way I don't understand your statement about the "django.setup =
lambda: None" snippet - it was just a quick and dirty hack to prevent the
multiple runs of django.setup(), which broke the LOGGING config)
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:11>
Old description:
> I've been bitten numerous times by the impredictable behaviour of django
> when django.setup() was called numerous times.
>
> In the old days I had exceptions, now it's mainly subtle breakages of
> logging configuration.
>
> I couldn't find, in the issue tracker or the dev mailing list statements
> about this subject, others than request from other users encountering the
> problem.
>
> For example #26152 concerned script+importable modules.
>
> The latest case in date for me is pytest-django having troubles with
> these multiple setup() calls : https://github.com/pytest-dev/pytest-
> django/issues/531 , due to multiple fixtures attempting this auto-setup.
>
> Would it be OK to make django.setup() idempotent, or even expose an
> "is_ready" flag for easier introspection ?
>
> -- here are some updates, comments get rejected as spam --
>
> Calling django.setup() multiple times is useless, BUT it can happen in
> lots of cases, that's why imho this case should be handled by the
> framework to avoid nasty side effects.
>
> These "duplicate calls" often involve the collision between manage.py
> commands, tests, custom scripts, and external launchers like pytest-
> django. Plus maybe some corner cases when unittest-style TestCases and
> pytest-style test functions are mixed in the same project.
>
> Users have to do a real gym to call setup() "at some moment" in all these
> use cases, yet try to prevent multiple calls of this initialization step
> (like the 'if__name__ == "main"' protection). So far my only way out was
> often to check for (not really undocumented) states of the framework
> before calling setup().
New description:
I've been bitten numerous times by the impredictable behaviour of django
when django.setup() was called numerous times.
In the old days I had exceptions, now it's mainly subtle breakages of
logging configuration.
I couldn't find, in the issue tracker or the dev mailing list statements
about this subject, others than request from other users encountering the
problem.
For example #26152 concerned script+importable modules.
The latest case in date for me is pytest-django having troubles with these
multiple setup() calls : https://github.com/pytest-dev/pytest-
django/issues/531 , due to multiple fixtures attempting this auto-setup.
Would it be OK to make django.setup() idempotent, or even expose an
"is_ready" flag for easier introspection ?
-- here are some updates, comments get rejected as spam --
Calling django.setup() multiple times is useless, BUT it can happen in
lots of cases, that's why imho this case should be handled by the
framework to avoid nasty side effects.
These "duplicate calls" often involve the collision between manage.py
commands, tests, custom scripts, and external launchers like pytest-
django. Plus maybe some corner cases when unittest-style TestCases and
pytest-style test functions are mixed in the same project.
Users have to do a real gym to call setup() "at some moment" in all these
use cases, yet try to prevent multiple calls of this initialization step
(like the `if__name__ == "main"'` protection). So far my only way out was
often to check for (not really undocumented) states of the framework
before calling setup().
--
Comment (by Tim Graham):
I don't know. Does that change risk breaking working code where multiple
calls to `django.setup()` has an intended effect?
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:12>
Comment (by pascal chambon):
Well, I'm usually quite dedicated to retrocompatibility (see django-
compat-patcher package), but for once any breakage would be due to a
strange misuse of django.setup().
In the code below, we see that django.setup() performs 3 steps :
- overridding logging configuration with django settings
- setting the script prefix
- populating the apps registry
The last 2 steps are now idempotent it seems.
Only overridding logging breaks some setups (eg. with pytest fixtures),
and I can't find any use case where it would be a wanted behaviour.
If users want to reset logging several times, they may as well call
configure_logging() by themselves.
Until django.setup() is protected against double executions, we'll have
weird bugs surfacing each time we add new steps to it ''(are these
idempotent, or reentrant, or runnable multiple times...)'', so I'd
advocate fixing this once and for all.
On "how" to do it, I think a threading lock + a boolean guard would be
easy and sufficient, wouldn't they ? With a system to raise an error if
django.setup() ends up being called multiple times by the same thread
(which often smells like missing "if __name__ == '__main'__" conditions in
imported scripts).
{{{
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else
settings.FORCE_SCRIPT_NAME
)
apps.populate(settings.INSTALLED_APPS)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:13>
* type: Bug => Cleanup/optimization
* stage: Unreviewed => Accepted
Comment:
I'm not completely sure if a change is feasible but I guess we could
evaluate a patch.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:14>
Comment (by Daniel Hahler):
This would be nice to have indeed.
Pascal, do you plan to provide a patch for this?
btw: the following can be used as a workaround (also used by pytest-
django):
{{{
import django.apps
if not django.apps.apps.ready:
django.setup()
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:15>
Comment (by pascal chambon):
Thanks for reminding me of this ticket, my latest struggles with pytest-
django made me think a more global solution to the problem of django setup
and complex environments, and this ticket is for me now superseded by
https://code.djangoproject.com/ticket/30536 - hoping that the new setting
I introduce will be OK too.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:16>
Comment (by Carlton Gibson):
Let's keep the discussion from #30536 here. (It's ≈the same ticket AFAICS,
and the history here is informative.)
[https://github.com/django/django/pull/11435 PR there is here].
One additional element added there is to make the the `setup()` code
''pluggable''. (i.e. whilst we make `setup()` exit if run multiple times,
allow user code to be run before/after as well.) A setting is proposed for
this. ''MUST it be a setting…?''
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:17>
* needs_better_patch: 0 => 1
Comment:
Looking at the PR, there are two things going on there:
1. Making `setup()` ''idempotent''. Let's handle that part here.
2. Allowing specifying custom logic. Let's handle that on #30536.
(The one looks less controversial than the other...)
Pascal, if you could split the PR into two, targeting the one as `Fixed
#28752 -- ...` that would be great.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:18>
Comment (by pascal chambon):
OK let's handle 1 feature at a time :)
Here is the PR only for idempotence (and django.is_ready flag) :
https://github.com/django/django/pull/11440
One of the CI checks failed for obscure reasons (package django not
found), any easy way to restart it ?
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:19>
Comment (by pascal chambon):
The latest version protects setup with a threading lock, so that
idempotence gets enforced even in multithreaded lazy-loading contexts.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:20>
* status: new => assigned
* owner: nobody => pascal chambon
Comment:
OK a few tweaks and bugfixes later, this PR seems now ready to me B-) ->
https://github.com/django/django/pull/11440
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:21>
* needs_better_patch: 1 => 0
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:22>
* status: assigned => closed
Comment:
OK, sorry, having looked again at this, I just can't see that the extra
code in `django.setup()` merits the change.
TBH in 10+ years of using Django I've never seen an actual issue here.
Maybe `py.test` runs into them, but it seems to me that it would be easy
enough to put required locking code outside of the `setup()` in whatever
bootstrap script is in play.
As it is, `setup()` is simple and straight-forward. With the proposed
changes it's anything but. Given that, and the lack of consensus as
whether, and even why, we should address this, I'm going to say `wontfix`.
On assessment I think addressing this is putting the cart before the
horse.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:23>
Comment (by Aymeric Augustin):
Thanks Carlton.
--
Ticket URL: <https://code.djangoproject.com/ticket/28752#comment:24>