I now see that my original post was too vague. I will more thoroughly 
explain myself below, including the actual patch (because it's that 
short!). *My proposal will not change Django's behavior at all for anyone 
not using "freezers" *such as PyOxidizer, cx_Freeze, Nuitka, PyInstaller, 
and the like. No changes to namespace package support. No changes to 
dependencies. In particular I mentioned #30950 
<https://code.djangoproject.com/ticket/30950> only in its capacity as a 
tracking ticket, but my proposal supports a *very narrow* subset of that 
ticket's goals.

To filter out namespace packages, 
django.db.migrations.loader.MigrationLoader.load_disk 
<https://github.com/django/django/blob/8b2a30f6f16cb1f3538847954030d69da005bc7f/django/db/migrations/loader.py#L68-L128>
 
currently rejects any module missing a __file__ attribute (including __file__ 
is None). Because __file__ is an *optional* 
<https://docs.python.org/3/reference/import.html#__file__> attribute of 
modules not provided by some freezers 
<https://pyoxidizer.readthedocs.io/en/stable/oxidized_importer_behavior_and_compliance.html#file-and-cached-module-attributes>,
 
*just testing for __file__ casts too wide a net*. Fortunately, namespace 
packages have other differences with regular packages that can be detected 
at runtime. One of those differences is that their __loader__ is 
importlib._bootstrap_external._NamespaceLoader, but that is undocumented 
and specific to CPython. However, a difference that *is* documented in the 
Python language specification 
<https://docs.python.org/3/reference/import.html#namespace-packages> is the 
type of __path__:

> Namespace packages do not use an ordinary list for their __path__ 
attribute. They instead use a custom iterable type....

So here is the patch that I propose. For brevity, I am excluding the test, 
but it's only another 17 lines.





*diff --git a/django/db/migrations/loader.py 
b/django/db/migrations/loader.pyindex 95a5062ec9..fd3a92f779 100644--- 
a/django/db/migrations/loader.py+++ b/django/db/migrations/loader.py*@@ 
-88,15 +88,19 @@ *class* MigrationLoader:
                     *continue*
                 *raise*
             *else*:
-                # Empty directories are namespaces.
-                # getattr() needed on PY36 and older (replace w/attribute 
access).
-                *if* getattr(module, '__file__', None) *is* None:
-                    self.unmigrated_apps.add(app_config.label)
-                    *continue*
                 # Module is not a package (e.g. migrations.py).
                 *if* *not* hasattr(module, '__path__'):
                     self.unmigrated_apps.add(app_config.label)
                     *continue*
+                # Empty directories are namespaces. Namespace packages 
have no
+                # __file__ and don't use a list for __path__. See
+                # 
https://docs.python.org/3/reference/import.html#namespace-packages
+                *if* (
+                    getattr(module, '__file__', None) *is* None *and*
+                    *not* isinstance(module.__path__, list)
+                ):
+                    self.unmigrated_apps.add(app_config.label)
+                    *continue*
                 # Force a reload if it's already loaded (tests need this)
                 *if* was_loaded:
                     reload(module) 

I hope the narrow nature of this patch is more acceptable than what you 
initially thought I was proposing. I am happy to answer further questions.

Thanks for taking the time to engage with me about this!

On Tuesday, December 22, 2020 at 11:23:19 AM UTC-6 Mariusz Felisiak wrote:

> Hi,
>
>     I can only repeat my arguments 
> <https://code.djangoproject.com/ticket/30950#comment:3> from the ticket. 
> I don't see a strong reason to add an extra dependency for this. Django 4.0 
> will drop support for Python 3.6 and Python 3.7, so we can reconsider this 
> ticket when developing Django 4.0+.
>
> Best,
> Mariusz
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/1cc41b79-3bd2-4bbf-9882-0c8d9075b78bn%40googlegroups.com.

Reply via email to