Fixed #23406 -- Allowed migrations to be loaded from .pyc files.
This commit is contained in:
parent
ee7f51c66d
commit
29150d5da8
|
@ -1,4 +1,4 @@
|
||||||
import os
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
from importlib import import_module, reload
|
from importlib import import_module, reload
|
||||||
|
|
||||||
|
@ -97,17 +97,20 @@ class MigrationLoader:
|
||||||
if was_loaded:
|
if was_loaded:
|
||||||
reload(module)
|
reload(module)
|
||||||
self.migrated_apps.add(app_config.label)
|
self.migrated_apps.add(app_config.label)
|
||||||
directory = os.path.dirname(module.__file__)
|
migration_names = {name for _, name, is_pkg in pkgutil.iter_modules(module.__path__) if not is_pkg}
|
||||||
# Scan for .py files
|
# Load migrations
|
||||||
migration_names = set()
|
|
||||||
for name in os.listdir(directory):
|
|
||||||
if name.endswith(".py"):
|
|
||||||
import_name = name.rsplit(".", 1)[0]
|
|
||||||
if import_name[0] not in "_.~":
|
|
||||||
migration_names.add(import_name)
|
|
||||||
# Load them
|
|
||||||
for migration_name in migration_names:
|
for migration_name in migration_names:
|
||||||
migration_module = import_module("%s.%s" % (module_name, migration_name))
|
migration_path = '%s.%s' % (module_name, migration_name)
|
||||||
|
try:
|
||||||
|
migration_module = import_module(migration_path)
|
||||||
|
except ImportError as e:
|
||||||
|
if 'bad magic number' in str(e):
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import %r as it appears to be a stale "
|
||||||
|
".pyc file." % migration_path
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
raise
|
||||||
if not hasattr(migration_module, "Migration"):
|
if not hasattr(migration_module, "Migration"):
|
||||||
raise BadMigrationError(
|
raise BadMigrationError(
|
||||||
"Migration %s in app %s has no Migration class" % (migration_name, app_config.label)
|
"Migration %s in app %s has no Migration class" % (migration_name, app_config.label)
|
||||||
|
|
|
@ -194,6 +194,8 @@ Migrations
|
||||||
|
|
||||||
* Added support for serialization of ``functools.partialmethod`` objects.
|
* Added support for serialization of ``functools.partialmethod`` objects.
|
||||||
|
|
||||||
|
* To support frozen environments, migrations may be loaded from ``.pyc`` files.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
@ -366,6 +368,9 @@ Miscellaneous
|
||||||
with such passwords from requesting a password reset. Audit your code to
|
with such passwords from requesting a password reset. Audit your code to
|
||||||
confirm that your usage of these APIs don't rely on the old behavior.
|
confirm that your usage of these APIs don't rely on the old behavior.
|
||||||
|
|
||||||
|
* Since migrations are now loaded from ``.pyc`` files, you might need to delete
|
||||||
|
them if you're working in a mixed Python 2 and Python 3 environment.
|
||||||
|
|
||||||
.. _deprecated-features-2.1:
|
.. _deprecated-features-2.1:
|
||||||
|
|
||||||
Features deprecated in 2.1
|
Features deprecated in 2.1
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import compileall
|
||||||
|
import os
|
||||||
|
|
||||||
from django.db import connection, connections
|
from django.db import connection, connections
|
||||||
from django.db.migrations.exceptions import (
|
from django.db.migrations.exceptions import (
|
||||||
AmbiguityError, InconsistentMigrationHistory, NodeNotFoundError,
|
AmbiguityError, InconsistentMigrationHistory, NodeNotFoundError,
|
||||||
|
@ -6,6 +9,8 @@ from django.db.migrations.loader import MigrationLoader
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.test import TestCase, modify_settings, override_settings
|
from django.test import TestCase, modify_settings, override_settings
|
||||||
|
|
||||||
|
from .test_base import MigrationTestBase
|
||||||
|
|
||||||
|
|
||||||
class RecorderTests(TestCase):
|
class RecorderTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -494,3 +499,32 @@ class LoaderTests(TestCase):
|
||||||
('app1', '4_auto'),
|
('app1', '4_auto'),
|
||||||
}
|
}
|
||||||
self.assertEqual(plan, expected_plan)
|
self.assertEqual(plan, expected_plan)
|
||||||
|
|
||||||
|
|
||||||
|
class PycLoaderTests(MigrationTestBase):
|
||||||
|
|
||||||
|
def test_valid(self):
|
||||||
|
"""
|
||||||
|
To support frozen environments, MigrationLoader loads .pyc migrations.
|
||||||
|
"""
|
||||||
|
with self.temporary_migration_module(module='migrations.test_migrations') as migration_dir:
|
||||||
|
# Compile .py files to .pyc files and delete .py files.
|
||||||
|
compileall.compile_dir(migration_dir, force=True, quiet=1, legacy=True)
|
||||||
|
for name in os.listdir(migration_dir):
|
||||||
|
if name.endswith('.py'):
|
||||||
|
os.remove(os.path.join(migration_dir, name))
|
||||||
|
loader = MigrationLoader(connection)
|
||||||
|
self.assertIn(('migrations', '0001_initial'), loader.disk_migrations)
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
"""
|
||||||
|
MigrationLoader reraises ImportErrors caused by "bad magic number" pyc
|
||||||
|
files with a more helpful message.
|
||||||
|
"""
|
||||||
|
with self.temporary_migration_module(module='migrations.test_migrations_bad_pyc'):
|
||||||
|
msg = (
|
||||||
|
"Couldn't import '\w+.migrations.0001_initial' as it appears "
|
||||||
|
"to be a stale .pyc file."
|
||||||
|
)
|
||||||
|
with self.assertRaisesRegex(ImportError, msg):
|
||||||
|
MigrationLoader(connection)
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue