joanbm joanbm - 1 month ago 6
Python Question

Package's namespace polluted by Django?

Django 1.8

For no obvious reasons, global variable defined in package's module is replaced between its initial assignment and deferred function call.

Minimal Django project is created with

django-admin startproject
. New empty application added with
django-admin startapp simplelib
. New app
simplelib
added to
INSTALLED_APPS
of project's
settings.py
.

Bellow is the only added code:

# content of myproject.simplelib.__init__.py
from django.db import models
from django.db.models.signals import class_prepared


def myhandler(sender, **kwargs):
print 'models from myhandler: {}'.format(models)


def direct_call():
print 'models from direct_call: {}'.format(models)


class_prepared.connect(myhandler)

print 'models from top namespace: {}'.format(models)

direct_call()


When project is run with
manage.py runserver
, following output is produced:

models from top namespace: <module 'django.db.models' from '/home/<snip>/Projects/Python/django-projects/lib/python2.7/site-packages/django/db/models/__init__.pyc'>
models from direct_all: <module 'django.db.models' from '/home/<snip>/Projects/Python/django-projects/lib/python2.7/site-packages/django/db/models/__init__.pyc'>
models from myhandler: <module 'simplelib.models' from '/home/<snip>/Projects/Python/django-projects/myproject/simplelib/models.pyc'>
^^^^^^^^^^^^^^^^


See, when signal handler function is invoked,
modules
global variable is changed.
There is no other project's code. It has to be altered by Django itself.

Note: above described effect applies only when
simplelib
is placed at the start of
INSTALLED_APPS
tuple. When added at the end,
models
still points to
django.db.models
, as expected.

Any idea what's going here ?

Answer

This is normal Python behaviour.

When you import a submodule, that submodule is set as an attribute on the parent module. In this case, when simplelib.models is imported, the models submodule is set on the parent module simplelib. The parent module namespace is the same as that module's __init__.py global namespace. This will overwrite the old value.

If you put simplelib as the first entry in INSTALLED_APPS, its models submodule will be the first models module imported by Django. This will replace the simplelib.models attribute before any model fires the class_prepared signal. On the other hand, if you put simplelib at the end of INSTALLED_APPS, Django will load simpellib.models as the last models module. Any models that are imported before that will fire the class_prepared signal before simplelib.models is imported, and before the models attribute is replaced with the submodule.