Opened 6 years ago

Last modified 4 years ago

#28752 closed Cleanup/optimization

django.setup() should not be runnable multiple times — at Version 12

Reported by: pascal chambon Owned by: nobody
Component: Core (Other) Version: 1.11
Severity: Normal Keywords:
Cc: Aymeric Augustin Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Tim Graham)

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().

Change History (12)

comment:1 by Tim Graham, 6 years ago

Resolution: duplicate
Status: newclosed
Summary: Django.setup() should be idempotentdjango.setup() should be idempotent

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.

comment:2 by pascal chambon, 6 years ago

Description: modified (diff)
Resolution: duplicate
Status: closednew

comment:3 by Tim Graham, 6 years ago

Thanks. Can you explain the use case for calling django.setup() multiple times?

comment:4 by pascal chambon, 6 years ago

Description: modified (diff)

comment:5 by pascal chambon, 6 years ago

Description: modified (diff)

comment:6 by pascal chambon, 6 years ago

Description: modified (diff)

comment:7 by pascal chambon, 6 years ago

My comments get marked as spam, I updated the description above.

comment:8 by Tim Graham, 6 years ago

Cc: Aymeric Augustin added
Description: modified (diff)

Aymeric, any input?

comment:9 by Aymeric Augustin, 6 years ago

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.

comment:10 by pascal chambon, 6 years ago

Summary: django.setup() should be idempotentdjango.setup() should not be runnable multiple times

comment:11 by pascal chambon, 6 years ago

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)

comment:12 by Tim Graham, 6 years ago

Description: modified (diff)

I don't know. Does that change risk breaking working code where multiple calls to django.setup() has an intended effect?

Note: See TracTickets for help on using tickets.
Back to Top