loader.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. from __future__ import unicode_literals
  2. from importlib import import_module
  3. import os
  4. import sys
  5. from django.apps import apps
  6. from django.db.migrations.recorder import MigrationRecorder
  7. from django.db.migrations.graph import MigrationGraph
  8. from django.utils import six
  9. from django.conf import settings
  10. MIGRATIONS_MODULE_NAME = 'migrations'
  11. class MigrationLoader(object):
  12. """
  13. Loads migration files from disk, and their status from the database.
  14. Migration files are expected to live in the "migrations" directory of
  15. an app. Their names are entirely unimportant from a code perspective,
  16. but will probably follow the 1234_name.py convention.
  17. On initialization, this class will scan those directories, and open and
  18. read the python files, looking for a class called Migration, which should
  19. inherit from django.db.migrations.Migration. See
  20. django.db.migrations.migration for what that looks like.
  21. Some migrations will be marked as "replacing" another set of migrations.
  22. These are loaded into a separate set of migrations away from the main ones.
  23. If all the migrations they replace are either unapplied or missing from
  24. disk, then they are injected into the main set, replacing the named migrations.
  25. Any dependency pointers to the replaced migrations are re-pointed to the
  26. new migration.
  27. This does mean that this class MUST also talk to the database as well as
  28. to disk, but this is probably fine. We're already not just operating
  29. in memory.
  30. """
  31. def __init__(self, connection, load=True, ignore_no_migrations=False):
  32. self.connection = connection
  33. self.disk_migrations = None
  34. self.applied_migrations = None
  35. self.ignore_no_migrations = ignore_no_migrations
  36. if load:
  37. self.build_graph()
  38. @classmethod
  39. def migrations_module(cls, app_label):
  40. if app_label in settings.MIGRATION_MODULES:
  41. return settings.MIGRATION_MODULES[app_label]
  42. else:
  43. app_package_name = apps.get_app_config(app_label).name
  44. return '%s.%s' % (app_package_name, MIGRATIONS_MODULE_NAME)
  45. def load_disk(self):
  46. """
  47. Loads the migrations from all INSTALLED_APPS from disk.
  48. """
  49. self.disk_migrations = {}
  50. self.unmigrated_apps = set()
  51. self.migrated_apps = set()
  52. for app_config in apps.get_app_configs():
  53. if app_config.models_module is None:
  54. continue
  55. # Get the migrations module directory
  56. module_name = self.migrations_module(app_config.label)
  57. was_loaded = module_name in sys.modules
  58. try:
  59. module = import_module(module_name)
  60. except ImportError as e:
  61. # I hate doing this, but I don't want to squash other import errors.
  62. # Might be better to try a directory check directly.
  63. if "No module named" in str(e) and MIGRATIONS_MODULE_NAME in str(e):
  64. self.unmigrated_apps.add(app_config.label)
  65. continue
  66. raise
  67. else:
  68. # PY3 will happily import empty dirs as namespaces.
  69. if not hasattr(module, '__file__'):
  70. continue
  71. # Module is not a package (e.g. migrations.py).
  72. if not hasattr(module, '__path__'):
  73. continue
  74. # Force a reload if it's already loaded (tests need this)
  75. if was_loaded:
  76. six.moves.reload_module(module)
  77. self.migrated_apps.add(app_config.label)
  78. directory = os.path.dirname(module.__file__)
  79. # Scan for .py files
  80. migration_names = set()
  81. for name in os.listdir(directory):
  82. if name.endswith(".py"):
  83. import_name = name.rsplit(".", 1)[0]
  84. if import_name[0] not in "_.~":
  85. migration_names.add(import_name)
  86. # Load them
  87. south_style_migrations = False
  88. for migration_name in migration_names:
  89. try:
  90. migration_module = import_module("%s.%s" % (module_name, migration_name))
  91. except ImportError as e:
  92. # Ignore South import errors, as we're triggering them
  93. if "south" in str(e).lower():
  94. south_style_migrations = True
  95. break
  96. raise
  97. if not hasattr(migration_module, "Migration"):
  98. raise BadMigrationError("Migration %s in app %s has no Migration class" % (migration_name, app_config.label))
  99. # Ignore South-style migrations
  100. if hasattr(migration_module.Migration, "forwards"):
  101. south_style_migrations = True
  102. break
  103. self.disk_migrations[app_config.label, migration_name] = migration_module.Migration(migration_name, app_config.label)
  104. if south_style_migrations:
  105. self.unmigrated_apps.add(app_config.label)
  106. def get_migration(self, app_label, name_prefix):
  107. "Gets the migration exactly named, or raises KeyError"
  108. return self.graph.nodes[app_label, name_prefix]
  109. def get_migration_by_prefix(self, app_label, name_prefix):
  110. "Returns the migration(s) which match the given app label and name _prefix_"
  111. # Do the search
  112. results = []
  113. for l, n in self.disk_migrations:
  114. if l == app_label and n.startswith(name_prefix):
  115. results.append((l, n))
  116. if len(results) > 1:
  117. raise AmbiguityError("There is more than one migration for '%s' with the prefix '%s'" % (app_label, name_prefix))
  118. elif len(results) == 0:
  119. raise KeyError("There no migrations for '%s' with the prefix '%s'" % (app_label, name_prefix))
  120. else:
  121. return self.disk_migrations[results[0]]
  122. def check_key(self, key, current_app):
  123. if (key[1] != "__first__" and key[1] != "__latest__") or key in self.graph:
  124. return key
  125. # Special-case __first__, which means "the first migration" for
  126. # migrated apps, and is ignored for unmigrated apps. It allows
  127. # makemigrations to declare dependencies on apps before they even have
  128. # migrations.
  129. if key[0] == current_app:
  130. # Ignore __first__ references to the same app (#22325)
  131. return
  132. if key[0] in self.unmigrated_apps:
  133. # This app isn't migrated, but something depends on it.
  134. # The models will get auto-added into the state, though
  135. # so we're fine.
  136. return
  137. if key[0] in self.migrated_apps:
  138. try:
  139. if key[1] == "__first__":
  140. return list(self.graph.root_nodes(key[0]))[0]
  141. else:
  142. return list(self.graph.leaf_nodes(key[0]))[0]
  143. except IndexError:
  144. if self.ignore_no_migrations:
  145. return None
  146. else:
  147. raise ValueError("Dependency on app with no migrations: %s" % key[0])
  148. raise ValueError("Dependency on unknown app: %s" % key[0])
  149. def build_graph(self):
  150. """
  151. Builds a migration dependency graph using both the disk and database.
  152. You'll need to rebuild the graph if you apply migrations. This isn't
  153. usually a problem as generally migration stuff runs in a one-shot process.
  154. """
  155. # Load disk data
  156. self.load_disk()
  157. # Load database data
  158. if self.connection is None:
  159. self.applied_migrations = set()
  160. else:
  161. recorder = MigrationRecorder(self.connection)
  162. self.applied_migrations = recorder.applied_migrations()
  163. # Do a first pass to separate out replacing and non-replacing migrations
  164. normal = {}
  165. replacing = {}
  166. for key, migration in self.disk_migrations.items():
  167. if migration.replaces:
  168. replacing[key] = migration
  169. else:
  170. normal[key] = migration
  171. # Calculate reverse dependencies - i.e., for each migration, what depends on it?
  172. # This is just for dependency re-pointing when applying replacements,
  173. # so we ignore run_before here.
  174. reverse_dependencies = {}
  175. for key, migration in normal.items():
  176. for parent in migration.dependencies:
  177. reverse_dependencies.setdefault(parent, set()).add(key)
  178. # Carry out replacements if we can - that is, if all replaced migrations
  179. # are either unapplied or missing.
  180. for key, migration in replacing.items():
  181. # Ensure this replacement migration is not in applied_migrations
  182. self.applied_migrations.discard(key)
  183. # Do the check. We can replace if all our replace targets are
  184. # applied, or if all of them are unapplied.
  185. applied_statuses = [(target in self.applied_migrations) for target in migration.replaces]
  186. can_replace = all(applied_statuses) or (not any(applied_statuses))
  187. if not can_replace:
  188. continue
  189. # Alright, time to replace. Step through the replaced migrations
  190. # and remove, repointing dependencies if needs be.
  191. for replaced in migration.replaces:
  192. if replaced in normal:
  193. # We don't care if the replaced migration doesn't exist;
  194. # the usage pattern here is to delete things after a while.
  195. del normal[replaced]
  196. for child_key in reverse_dependencies.get(replaced, set()):
  197. if child_key in migration.replaces:
  198. continue
  199. normal[child_key].dependencies.remove(replaced)
  200. normal[child_key].dependencies.append(key)
  201. normal[key] = migration
  202. # Mark the replacement as applied if all its replaced ones are
  203. if all(applied_statuses):
  204. self.applied_migrations.add(key)
  205. # Finally, make a graph and load everything into it
  206. self.graph = MigrationGraph()
  207. for key, migration in normal.items():
  208. self.graph.add_node(key, migration)
  209. # Add all internal dependencies first to ensure __first__ dependencies
  210. # find the correct root node.
  211. for key, migration in normal.items():
  212. for parent in migration.dependencies:
  213. if parent[0] != key[0] or parent[1] == '__first__':
  214. # Ignore __first__ references to the same app (#22325)
  215. continue
  216. self.graph.add_dependency(migration, key, parent)
  217. for key, migration in normal.items():
  218. for parent in migration.dependencies:
  219. if parent[0] == key[0]:
  220. # Internal dependencies already added.
  221. continue
  222. parent = self.check_key(parent, key[0])
  223. if parent is not None:
  224. self.graph.add_dependency(migration, key, parent)
  225. for child in migration.run_before:
  226. child = self.check_key(child, key[0])
  227. if child is not None:
  228. self.graph.add_dependency(migration, child, key)
  229. def detect_conflicts(self):
  230. """
  231. Looks through the loaded graph and detects any conflicts - apps
  232. with more than one leaf migration. Returns a dict of the app labels
  233. that conflict with the migration names that conflict.
  234. """
  235. seen_apps = {}
  236. conflicting_apps = set()
  237. for app_label, migration_name in self.graph.leaf_nodes():
  238. if app_label in seen_apps:
  239. conflicting_apps.add(app_label)
  240. seen_apps.setdefault(app_label, set()).add(migration_name)
  241. return dict((app_label, seen_apps[app_label]) for app_label in conflicting_apps)
  242. def project_state(self, nodes=None, at_end=True):
  243. """
  244. Returns a ProjectState object representing the most recent state
  245. that the migrations we loaded represent.
  246. See graph.make_state for the meaning of "nodes" and "at_end"
  247. """
  248. return self.graph.make_state(nodes=nodes, at_end=at_end, real_apps=list(self.unmigrated_apps))
  249. class BadMigrationError(Exception):
  250. """
  251. Raised when there's a bad migration (unreadable/bad format/etc.)
  252. """
  253. pass
  254. class AmbiguityError(Exception):
  255. """
  256. Raised when more than one migration matches a name prefix
  257. """
  258. pass