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.

17 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

1 Like

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.

2 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