Change reusable apps naming recommendation

The docs page Advanced tutorial: How to write reusable apps covers packaging a Django app for PyPI. The “Choosing a name for your app” admonition recommends using an example module name polls with a package name django-polls.

I propose changing this page to recommend a module name of django_polls and making the admonition warn against not using the django_ prefix. The current approach causes name clashes which prevents using packages together. Here are the examples I know of:

I’m sure there are more.

18 Likes

Just to clarify, you’re still recommending keeping the PyPI package name as django-polls, right?

If so, hard +1 here. I find it annoying when the PyPI package name doesn’t match the name of the Python module/package I’m importing.

2 Likes

I’m +1 on this - making people avoid a django_ prefix on module names made more sense a decade or so ago when Python packages were rarer and Django was a bigger percentage of the ecosystem, but now it just seems like a silly recommendation.

1 Like

Replies on Mastodon and Twitter have been overwhelmingly positive.

1 Like

Yes, I’m proposing changing the module name to match it.

2 Likes

This means that the project structure would then look something like

django-polls
  + -- django_polls
      models.py
      setup.py
      urls.py
      ...
  setup.py
  MANIFEST.in
  README.md
  ...

In such a case every database table would be named django_polls_modelname. In my opinion this is too verbose and makes it harder to find the proper table. Moreover, static URL would start with /static/django_pools/... exposing the framework.

Would it be possible to create an empty folder inside the django packages, say django/ext where all Django third party apps would be installed? Then one would have to change their settings to

INSTALLED_APPS = [
    "django.contrib....",
    "django.ext.polls",
    ...
]

but otherwise all the naming conventions would remain the same.

I think the database table name is better as it’s more explicit. Meta.db_table can always be used in packages to disambiguate.

Static files are not restricted to the module name. They can use anything. But I’m also not concerned about framework name exposure since there are so many ways to fingerprint a site as using Django: find the admin, hit a 404 or CSRF error, check employees on GitHub, open positions on LinkedIn, …

Proposing an “ext” namespace is a bit beyond the current discussion. I’m not a fan of PEP 420 style namespace packages as I found they break many tools, hence: flake8-no-pep420 · PyPI

2 Likes

I think @jrief has a point about the prefixes, and that point is even slightly wider than statics and database tables – e.g. it affects management commands that would need to refer to the app by its label.

Luckily, the app label doesn’t really have to match the module name; in fact, I’d even consider “having that cake while eating it” by changing the default label from last component of name to last component of name, with a prefix django_ removed if found (of course, this requires taking care of backwards compatibility).

1 Like

When you register a new python-package on Pypi, it is registered by name as defined in the package-metadata, and then this name is guaranteed to be unique across the entire repository.
The name for the django-package is “Django”:

pip install Django

but the names on Pypi are case-insensitive, so this also works:

pip install django|DJANGO

Besides the case-insensitivity, Pypi also introduces an equivalence between hyphens and underscores
because names with hypens are not valid python-modules.

so this is equivalent:

pip install django-foo
pip install Django_FOO

But the whole idea of this unique namespace on Pypi, is meant that the toplevel python-module provided by this package is unique, because that is what we use in code.

A proper name for a python-package is lowercased using dashes, eg “django-foo”
with a corresponding module-name “django_foo” belonging to this same unique namespace. In short:

module_name = package_name.lower().replace(‘-’, ‘_’)

This convention has probably been too implicit in the python-community.
But a lot of code is using this convention when they have a soft-dependency on another package,
they will try to import the module, and then assume the corresponding package has been installed.
For example Flask tries to “import dotenv” and then assumes its soft-dependency “python-dotenv” is installed, but Flask will break when you have install “django-dotenv” installed.

Unfortunately the django-community has been promoting for a long time
a different naming-pattern:

module_name = package_name.lower().replace(‘-’, ‘').replace('django’, ‘’)

Now we have thousands of published python-packages, installing a python-module
belonging to the unique namespace of ANOTHER PACKAGE.

You can install both packages without noticeable problems:

pip install django-foo
pip install foo # completely independent project

pip freeze
django-foo==…
foo==…

Pip will happily install both packages, and report both as being installed successfully.
but the second one has DELETED & REPLACED the source-code of the first package.

AND NO TOOL WILL WARN YOU ABOUT THIS.

As a consequence, you can never use such packages in the same project.
Or packages that depend on one of those.

This is not “shadowing”, it’s worse.
This is not “dependency-hell”, it’s worse.
Because you are silently deleting another package (but keeping its metadata intact).

Your quickfix-solution is to privately fork it, change the name of the module, and re-publish privately.

Publishing a python-package were the name of the module belongs to the namespace of different project is probably the most diabolical evil anti-pattern in the entire python-community.

Thanks to Adam for tackling this.

3 Likes

Following the positive feedback here, I have written a PR with the proposed changes: Fixed #35084 -- Recommended 'django_' prefix for reusable app modules. by adamchainz · Pull Request #17677 · django/django · GitHub.

2 Likes

There is a problem with changing polls to Django-polls which n=then becomes “django_polls” for the name of the package with the label of “polls”. In the tutorial, we were told to call the app polls. And the name of the project is django_polls. This results in a directory name clash when I try and install the django_polls app. Then I tried to fall back on polls. Which is fine, BUT I labelled it polls as well. And I can’t do that. So what label should I apply then? This problem actually is originally caused by the structure of the project. Basically, there is:

- django-polls/ # top level directory
- django_polls/ # name of the project
- templates/
- manage.py
- requirements.txt

So calling the polls app django_polls for the sake of “convention” is problematic. Thanks!

Was a problem waiting to happen. In the beginning, when I followed the app tutorial, I didn’t know that I was going to do advanced one until I found out about it. And then the name of the app is autogenerated based on what we give it. if we give it according to “convention” using django-* for the name, and django_* for the name of the app used in the project itself, it WILL cause an issue. They should change the iinitiial naming of the app to fit the structure of the whole project taking into account the advanced tutorial! Thanks!

I just wanted to clarify. I think they should not use dummy names for the project and the top level directory in the polls app tutorial. It should be more realistic. And if changes have to be made to make it work, then it should be done. Most people (including myself) don’t want to use dummy names like that. Sorry for any confusion!

1 Like