/bangkokhotel/lib/python2.5/site-packages/south/migration/base.py

https://bitbucket.org/luisrodriguez/bangkokhotel · Python · 439 lines · 332 code · 40 blank · 67 comment · 46 complexity · 62c69efc9694a55e0e6ae27a1840eecc MD5 · raw file

  1. from collections import deque
  2. import datetime
  3. import os
  4. import re
  5. import sys
  6. from django.core.exceptions import ImproperlyConfigured
  7. from django.db import models
  8. from django.conf import settings
  9. from django.utils import importlib
  10. from south import exceptions
  11. from south.migration.utils import depends, dfs, flatten, get_app_label
  12. from south.orm import FakeORM
  13. from south.utils import memoize, ask_for_it_by_name, datetime_utils
  14. from south.migration.utils import app_label_to_app_module
  15. def all_migrations(applications=None):
  16. """
  17. Returns all Migrations for all `applications` that are migrated.
  18. """
  19. if applications is None:
  20. applications = models.get_apps()
  21. for model_module in applications:
  22. # The app they've passed is the models module - go up one level
  23. app_path = ".".join(model_module.__name__.split(".")[:-1])
  24. app = ask_for_it_by_name(app_path)
  25. try:
  26. yield Migrations(app)
  27. except exceptions.NoMigrations:
  28. pass
  29. def application_to_app_label(application):
  30. "Works out the app label from either the app label, the app name, or the module"
  31. if isinstance(application, basestring):
  32. app_label = application.split('.')[-1]
  33. else:
  34. app_label = application.__name__.split('.')[-1]
  35. return app_label
  36. class MigrationsMetaclass(type):
  37. """
  38. Metaclass which ensures there is only one instance of a Migrations for
  39. any given app.
  40. """
  41. def __init__(self, name, bases, dict):
  42. super(MigrationsMetaclass, self).__init__(name, bases, dict)
  43. self.instances = {}
  44. def __call__(self, application, **kwds):
  45. app_label = application_to_app_label(application)
  46. # If we don't already have an instance, make one
  47. if app_label not in self.instances:
  48. self.instances[app_label] = super(MigrationsMetaclass, self).__call__(app_label_to_app_module(app_label), **kwds)
  49. return self.instances[app_label]
  50. def _clear_cache(self):
  51. "Clears the cache of Migration objects."
  52. self.instances = {}
  53. class Migrations(list):
  54. """
  55. Holds a list of Migration objects for a particular app.
  56. """
  57. __metaclass__ = MigrationsMetaclass
  58. if getattr(settings, "SOUTH_USE_PYC", False):
  59. MIGRATION_FILENAME = re.compile(r'(?!__init__)' # Don't match __init__.py
  60. r'[0-9a-zA-Z_]*' # Don't match dotfiles, or names with dots/invalid chars in them
  61. r'(\.pyc?)?$') # Match .py or .pyc files, or module dirs
  62. else:
  63. MIGRATION_FILENAME = re.compile(r'(?!__init__)' # Don't match __init__.py
  64. r'[0-9a-zA-Z_]*' # Don't match dotfiles, or names with dots/invalid chars in them
  65. r'(\.py)?$') # Match only .py files, or module dirs
  66. def __init__(self, application, force_creation=False, verbose_creation=True):
  67. "Constructor. Takes the module of the app, NOT its models (like get_app returns)"
  68. self._cache = {}
  69. self.set_application(application, force_creation, verbose_creation)
  70. def create_migrations_directory(self, verbose=True):
  71. "Given an application, ensures that the migrations directory is ready."
  72. migrations_dir = self.migrations_dir()
  73. # Make the directory if it's not already there
  74. if not os.path.isdir(migrations_dir):
  75. if verbose:
  76. print "Creating migrations directory at '%s'..." % migrations_dir
  77. os.mkdir(migrations_dir)
  78. # Same for __init__.py
  79. init_path = os.path.join(migrations_dir, "__init__.py")
  80. if not os.path.isfile(init_path):
  81. # Touch the init py file
  82. if verbose:
  83. print "Creating __init__.py in '%s'..." % migrations_dir
  84. open(init_path, "w").close()
  85. def migrations_dir(self):
  86. """
  87. Returns the full path of the migrations directory.
  88. If it doesn't exist yet, returns where it would exist, based on the
  89. app's migrations module (defaults to app.migrations)
  90. """
  91. module_path = self.migrations_module()
  92. try:
  93. module = importlib.import_module(module_path)
  94. except ImportError:
  95. # There's no migrations module made yet; guess!
  96. try:
  97. parent = importlib.import_module(".".join(module_path.split(".")[:-1]))
  98. except ImportError:
  99. # The parent doesn't even exist, that's an issue.
  100. raise exceptions.InvalidMigrationModule(
  101. application = self.application.__name__,
  102. module = module_path,
  103. )
  104. else:
  105. # Good guess.
  106. return os.path.join(os.path.dirname(parent.__file__), module_path.split(".")[-1])
  107. else:
  108. # Get directory directly
  109. return os.path.dirname(module.__file__)
  110. def migrations_module(self):
  111. "Returns the module name of the migrations module for this"
  112. app_label = application_to_app_label(self.application)
  113. if hasattr(settings, "SOUTH_MIGRATION_MODULES"):
  114. if app_label in settings.SOUTH_MIGRATION_MODULES:
  115. # There's an override.
  116. return settings.SOUTH_MIGRATION_MODULES[app_label]
  117. return self._application.__name__ + '.migrations'
  118. def get_application(self):
  119. return self._application
  120. def set_application(self, application, force_creation=False, verbose_creation=True):
  121. """
  122. Called when the application for this Migrations is set.
  123. Imports the migrations module object, and throws a paddy if it can't.
  124. """
  125. self._application = application
  126. if not hasattr(application, 'migrations'):
  127. try:
  128. module = importlib.import_module(self.migrations_module())
  129. self._migrations = application.migrations = module
  130. except ImportError:
  131. if force_creation:
  132. self.create_migrations_directory(verbose_creation)
  133. module = importlib.import_module(self.migrations_module())
  134. self._migrations = application.migrations = module
  135. else:
  136. raise exceptions.NoMigrations(application)
  137. self._load_migrations_module(application.migrations)
  138. application = property(get_application, set_application)
  139. def _load_migrations_module(self, module):
  140. self._migrations = module
  141. filenames = []
  142. dirname = self.migrations_dir()
  143. for f in os.listdir(dirname):
  144. if self.MIGRATION_FILENAME.match(os.path.basename(f)):
  145. full_path = os.path.join(dirname, f)
  146. # If it's a .pyc file, only append if the .py isn't already around
  147. if f.endswith(".pyc") and (os.path.isfile(full_path[:-1])):
  148. continue
  149. # If it's a module directory, only append if it contains __init__.py[c].
  150. if os.path.isdir(full_path):
  151. if not (os.path.isfile(os.path.join(full_path, "__init__.py")) or \
  152. (getattr(settings, "SOUTH_USE_PYC", False) and \
  153. os.path.isfile(os.path.join(full_path, "__init__.pyc")))):
  154. continue
  155. filenames.append(f)
  156. filenames.sort()
  157. self.extend(self.migration(f) for f in filenames)
  158. def migration(self, filename):
  159. name = Migration.strip_filename(filename)
  160. if name not in self._cache:
  161. self._cache[name] = Migration(self, name)
  162. return self._cache[name]
  163. def __getitem__(self, value):
  164. if isinstance(value, basestring):
  165. return self.migration(value)
  166. return super(Migrations, self).__getitem__(value)
  167. def _guess_migration(self, prefix):
  168. prefix = Migration.strip_filename(prefix)
  169. matches = [m for m in self if m.name().startswith(prefix)]
  170. if len(matches) == 1:
  171. return matches[0]
  172. elif len(matches) > 1:
  173. raise exceptions.MultiplePrefixMatches(prefix, matches)
  174. else:
  175. raise exceptions.UnknownMigration(prefix, None)
  176. def guess_migration(self, target_name):
  177. if target_name == 'zero' or not self:
  178. return
  179. elif target_name is None:
  180. return self[-1]
  181. else:
  182. return self._guess_migration(prefix=target_name)
  183. def app_label(self):
  184. return self._application.__name__.split('.')[-1]
  185. def full_name(self):
  186. return self._migrations.__name__
  187. @classmethod
  188. def calculate_dependencies(cls, force=False):
  189. "Goes through all the migrations, and works out the dependencies."
  190. if getattr(cls, "_dependencies_done", False) and not force:
  191. return
  192. for migrations in all_migrations():
  193. for migration in migrations:
  194. migration.calculate_dependencies()
  195. cls._dependencies_done = True
  196. @staticmethod
  197. def invalidate_all_modules():
  198. "Goes through all the migrations, and invalidates all cached modules."
  199. for migrations in all_migrations():
  200. for migration in migrations:
  201. migration.invalidate_module()
  202. def next_filename(self, name):
  203. "Returns the fully-formatted filename of what a new migration 'name' would be"
  204. highest_number = 0
  205. for migration in self:
  206. try:
  207. number = int(migration.name().split("_")[0])
  208. highest_number = max(highest_number, number)
  209. except ValueError:
  210. pass
  211. # Work out the new filename
  212. return "%04i_%s.py" % (
  213. highest_number + 1,
  214. name,
  215. )
  216. class Migration(object):
  217. """
  218. Class which represents a particular migration file on-disk.
  219. """
  220. def __init__(self, migrations, filename):
  221. """
  222. Returns the migration class implied by 'filename'.
  223. """
  224. self.migrations = migrations
  225. self.filename = filename
  226. self.dependencies = set()
  227. self.dependents = set()
  228. def __str__(self):
  229. return self.app_label() + ':' + self.name()
  230. def __repr__(self):
  231. return u'<Migration: %s>' % unicode(self)
  232. def __eq__(self, other):
  233. return self.app_label() == other.app_label() and self.name() == other.name()
  234. def __hash__(self):
  235. return hash(str(self))
  236. def app_label(self):
  237. return self.migrations.app_label()
  238. @staticmethod
  239. def strip_filename(filename):
  240. return os.path.splitext(os.path.basename(filename))[0]
  241. def name(self):
  242. return self.strip_filename(os.path.basename(self.filename))
  243. def full_name(self):
  244. return self.migrations.full_name() + '.' + self.name()
  245. def migration(self):
  246. "Tries to load the actual migration module"
  247. full_name = self.full_name()
  248. try:
  249. migration = sys.modules[full_name]
  250. except KeyError:
  251. try:
  252. migration = __import__(full_name, {}, {}, ['Migration'])
  253. except ImportError, e:
  254. raise exceptions.UnknownMigration(self, sys.exc_info())
  255. except Exception, e:
  256. raise exceptions.BrokenMigration(self, sys.exc_info())
  257. # Override some imports
  258. migration._ = lambda x: x # Fake i18n
  259. migration.datetime = datetime_utils
  260. return migration
  261. migration = memoize(migration)
  262. def migration_class(self):
  263. "Returns the Migration class from the module"
  264. return self.migration().Migration
  265. def migration_instance(self):
  266. "Instantiates the migration_class"
  267. return self.migration_class()()
  268. migration_instance = memoize(migration_instance)
  269. def previous(self):
  270. "Returns the migration that comes before this one in the sequence."
  271. index = self.migrations.index(self) - 1
  272. if index < 0:
  273. return None
  274. return self.migrations[index]
  275. previous = memoize(previous)
  276. def next(self):
  277. "Returns the migration that comes after this one in the sequence."
  278. index = self.migrations.index(self) + 1
  279. if index >= len(self.migrations):
  280. return None
  281. return self.migrations[index]
  282. next = memoize(next)
  283. def _get_dependency_objects(self, attrname):
  284. """
  285. Given the name of an attribute (depends_on or needed_by), either yields
  286. a list of migration objects representing it, or errors out.
  287. """
  288. for app, name in getattr(self.migration_class(), attrname, []):
  289. try:
  290. migrations = Migrations(app)
  291. except ImproperlyConfigured:
  292. raise exceptions.DependsOnUnmigratedApplication(self, app)
  293. migration = migrations.migration(name)
  294. try:
  295. migration.migration()
  296. except exceptions.UnknownMigration:
  297. raise exceptions.DependsOnUnknownMigration(self, migration)
  298. if migration.is_before(self) == False:
  299. raise exceptions.DependsOnHigherMigration(self, migration)
  300. yield migration
  301. def calculate_dependencies(self):
  302. """
  303. Loads dependency info for this migration, and stores it in itself
  304. and any other relevant migrations.
  305. """
  306. # Normal deps first
  307. for migration in self._get_dependency_objects("depends_on"):
  308. self.dependencies.add(migration)
  309. migration.dependents.add(self)
  310. # And reverse deps
  311. for migration in self._get_dependency_objects("needed_by"):
  312. self.dependents.add(migration)
  313. migration.dependencies.add(self)
  314. # And implicit ordering deps
  315. previous = self.previous()
  316. if previous:
  317. self.dependencies.add(previous)
  318. previous.dependents.add(self)
  319. def invalidate_module(self):
  320. """
  321. Removes the cached version of this migration's module import, so we
  322. have to re-import it. Used when south.db.db changes.
  323. """
  324. reload(self.migration())
  325. self.migration._invalidate()
  326. def forwards(self):
  327. return self.migration_instance().forwards
  328. def backwards(self):
  329. return self.migration_instance().backwards
  330. def forwards_plan(self):
  331. """
  332. Returns a list of Migration objects to be applied, in order.
  333. This list includes `self`, which will be applied last.
  334. """
  335. return depends(self, lambda x: x.dependencies)
  336. def _backwards_plan(self):
  337. return depends(self, lambda x: x.dependents)
  338. def backwards_plan(self):
  339. """
  340. Returns a list of Migration objects to be unapplied, in order.
  341. This list includes `self`, which will be unapplied last.
  342. """
  343. return list(self._backwards_plan())
  344. def is_before(self, other):
  345. if self.migrations == other.migrations:
  346. if self.filename < other.filename:
  347. return True
  348. return False
  349. def is_after(self, other):
  350. if self.migrations == other.migrations:
  351. if self.filename > other.filename:
  352. return True
  353. return False
  354. def prev_orm(self):
  355. if getattr(self.migration_class(), 'symmetrical', False):
  356. return self.orm()
  357. previous = self.previous()
  358. if previous is None:
  359. # First migration? The 'previous ORM' is empty.
  360. return FakeORM(None, self.app_label())
  361. return previous.orm()
  362. prev_orm = memoize(prev_orm)
  363. def orm(self):
  364. return FakeORM(self.migration_class(), self.app_label())
  365. orm = memoize(orm)
  366. def no_dry_run(self):
  367. migration_class = self.migration_class()
  368. try:
  369. return migration_class.no_dry_run
  370. except AttributeError:
  371. return False