writer.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. from __future__ import unicode_literals
  2. import datetime
  3. import inspect
  4. import decimal
  5. import collections
  6. from importlib import import_module
  7. import os
  8. import re
  9. import sys
  10. import types
  11. from django.apps import apps
  12. from django.db import models, migrations
  13. from django.db.migrations.loader import MigrationLoader
  14. from django.utils import datetime_safe, six
  15. from django.utils.encoding import force_text
  16. from django.utils.functional import Promise
  17. COMPILED_REGEX_TYPE = type(re.compile(''))
  18. class SettingsReference(str):
  19. """
  20. Special subclass of string which actually references a current settings
  21. value. It's treated as the value in memory, but serializes out to a
  22. settings.NAME attribute reference.
  23. """
  24. def __new__(self, value, setting_name):
  25. return str.__new__(self, value)
  26. def __init__(self, value, setting_name):
  27. self.setting_name = setting_name
  28. class OperationWriter(object):
  29. indentation = 2
  30. def __init__(self, operation):
  31. self.operation = operation
  32. self.buff = []
  33. def serialize(self):
  34. imports = set()
  35. name, args, kwargs = self.operation.deconstruct()
  36. argspec = inspect.getargspec(self.operation.__init__)
  37. normalized_kwargs = inspect.getcallargs(self.operation.__init__, *args, **kwargs)
  38. # See if this operation is in django.db.migrations. If it is,
  39. # We can just use the fact we already have that imported,
  40. # otherwise, we need to add an import for the operation class.
  41. if getattr(migrations, name, None) == self.operation.__class__:
  42. self.feed('migrations.%s(' % name)
  43. else:
  44. imports.add('import %s' % (self.operation.__class__.__module__))
  45. self.feed('%s.%s(' % (self.operation.__class__.__module__, name))
  46. self.indent()
  47. for arg_name in argspec.args[1:]:
  48. arg_value = normalized_kwargs[arg_name]
  49. if (arg_name in self.operation.serialization_expand_args and
  50. isinstance(arg_value, (list, tuple, dict))):
  51. if isinstance(arg_value, dict):
  52. self.feed('%s={' % arg_name)
  53. self.indent()
  54. for key, value in arg_value.items():
  55. key_string, key_imports = MigrationWriter.serialize(key)
  56. arg_string, arg_imports = MigrationWriter.serialize(value)
  57. self.feed('%s: %s,' % (key_string, arg_string))
  58. imports.update(key_imports)
  59. imports.update(arg_imports)
  60. self.unindent()
  61. self.feed('},')
  62. else:
  63. self.feed('%s=[' % arg_name)
  64. self.indent()
  65. for item in arg_value:
  66. arg_string, arg_imports = MigrationWriter.serialize(item)
  67. self.feed('%s,' % arg_string)
  68. imports.update(arg_imports)
  69. self.unindent()
  70. self.feed('],')
  71. else:
  72. arg_string, arg_imports = MigrationWriter.serialize(arg_value)
  73. self.feed('%s=%s,' % (arg_name, arg_string))
  74. imports.update(arg_imports)
  75. self.unindent()
  76. self.feed('),')
  77. return self.render(), imports
  78. def indent(self):
  79. self.indentation += 1
  80. def unindent(self):
  81. self.indentation -= 1
  82. def feed(self, line):
  83. self.buff.append(' ' * (self.indentation * 4) + line)
  84. def render(self):
  85. return '\n'.join(self.buff)
  86. class MigrationWriter(object):
  87. """
  88. Takes a Migration instance and is able to produce the contents
  89. of the migration file from it.
  90. """
  91. def __init__(self, migration):
  92. self.migration = migration
  93. self.needs_manual_porting = False
  94. def as_string(self):
  95. """
  96. Returns a string of the file contents.
  97. """
  98. items = {
  99. "replaces_str": "",
  100. }
  101. imports = set()
  102. # Deconstruct operations
  103. operations = []
  104. for operation in self.migration.operations:
  105. operation_string, operation_imports = OperationWriter(operation).serialize()
  106. imports.update(operation_imports)
  107. operations.append(operation_string)
  108. items["operations"] = "\n".join(operations) + "\n" if operations else ""
  109. # Format dependencies and write out swappable dependencies right
  110. dependencies = []
  111. for dependency in self.migration.dependencies:
  112. if dependency[0] == "__setting__":
  113. dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1])
  114. imports.add("from django.conf import settings")
  115. else:
  116. # No need to output bytestrings for dependencies
  117. dependency = tuple([force_text(s) for s in dependency])
  118. dependencies.append(" %s," % self.serialize(dependency)[0])
  119. items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else ""
  120. # Format imports nicely, swapping imports of functions from migration files
  121. # for comments
  122. migration_imports = set()
  123. for line in list(imports):
  124. if re.match("^import (.*)\.\d+[^\s]*$", line):
  125. migration_imports.add(line.split("import")[1].strip())
  126. imports.remove(line)
  127. self.needs_manual_porting = True
  128. imports.discard("from django.db import models")
  129. items["imports"] = "\n".join(imports) + "\n" if imports else ""
  130. if migration_imports:
  131. items["imports"] += "\n\n# Functions from the following migrations need manual copying.\n# Move them and any dependencies into this file, then update the\n# RunPython operations to refer to the local versions:\n# %s" % (
  132. "\n# ".join(migration_imports)
  133. )
  134. # If there's a replaces, make a string for it
  135. if self.migration.replaces:
  136. items['replaces_str'] = "\n replaces = %s\n" % self.serialize(self.migration.replaces)[0]
  137. return (MIGRATION_TEMPLATE % items).encode("utf8")
  138. @property
  139. def filename(self):
  140. return "%s.py" % self.migration.name
  141. @property
  142. def path(self):
  143. migrations_package_name = MigrationLoader.migrations_module(self.migration.app_label)
  144. # See if we can import the migrations module directly
  145. try:
  146. migrations_module = import_module(migrations_package_name)
  147. # Python 3 fails when the migrations directory does not have a
  148. # __init__.py file
  149. if not hasattr(migrations_module, '__file__'):
  150. raise ImportError
  151. basedir = os.path.dirname(migrations_module.__file__)
  152. except ImportError:
  153. app_config = apps.get_app_config(self.migration.app_label)
  154. migrations_package_basename = migrations_package_name.split(".")[-1]
  155. # Alright, see if it's a direct submodule of the app
  156. if '%s.%s' % (app_config.name, migrations_package_basename) == migrations_package_name:
  157. basedir = os.path.join(app_config.path, migrations_package_basename)
  158. else:
  159. # In case of using MIGRATION_MODULES setting and the custom
  160. # package doesn't exist, create one.
  161. package_dirs = migrations_package_name.split(".")
  162. create_path = os.path.join(sys.path[0], *package_dirs)
  163. if not os.path.isdir(create_path):
  164. os.makedirs(create_path)
  165. for i in range(1, len(package_dirs) + 1):
  166. init_dir = os.path.join(sys.path[0], *package_dirs[:i])
  167. init_path = os.path.join(init_dir, "__init__.py")
  168. if not os.path.isfile(init_path):
  169. open(init_path, "w").close()
  170. return os.path.join(create_path, self.filename)
  171. return os.path.join(basedir, self.filename)
  172. @classmethod
  173. def serialize_deconstructed(cls, path, args, kwargs):
  174. module, name = path.rsplit(".", 1)
  175. if module == "django.db.models":
  176. imports = set(["from django.db import models"])
  177. name = "models.%s" % name
  178. else:
  179. imports = set(["import %s" % module])
  180. name = path
  181. strings = []
  182. for arg in args:
  183. arg_string, arg_imports = cls.serialize(arg)
  184. strings.append(arg_string)
  185. imports.update(arg_imports)
  186. for kw, arg in kwargs.items():
  187. arg_string, arg_imports = cls.serialize(arg)
  188. imports.update(arg_imports)
  189. strings.append("%s=%s" % (kw, arg_string))
  190. return "%s(%s)" % (name, ", ".join(strings)), imports
  191. @classmethod
  192. def serialize(cls, value):
  193. """
  194. Serializes the value to a string that's parsable by Python, along
  195. with any needed imports to make that string work.
  196. More advanced than repr() as it can encode things
  197. like datetime.datetime.now.
  198. """
  199. # FIXME: Ideally Promise would be reconstructible, but for now we
  200. # use force_text on them and defer to the normal string serialization
  201. # process.
  202. if isinstance(value, Promise):
  203. value = force_text(value)
  204. # Sequences
  205. if isinstance(value, (list, set, tuple)):
  206. imports = set()
  207. strings = []
  208. for item in value:
  209. item_string, item_imports = cls.serialize(item)
  210. imports.update(item_imports)
  211. strings.append(item_string)
  212. if isinstance(value, set):
  213. format = "set([%s])"
  214. elif isinstance(value, tuple):
  215. # When len(value)==0, the empty tuple should be serialized as
  216. # "()", not "(,)" because (,) is invalid Python syntax.
  217. format = "(%s)" if len(value) != 1 else "(%s,)"
  218. else:
  219. format = "[%s]"
  220. return format % (", ".join(strings)), imports
  221. # Dictionaries
  222. elif isinstance(value, dict):
  223. imports = set()
  224. strings = []
  225. for k, v in value.items():
  226. k_string, k_imports = cls.serialize(k)
  227. v_string, v_imports = cls.serialize(v)
  228. imports.update(k_imports)
  229. imports.update(v_imports)
  230. strings.append((k_string, v_string))
  231. return "{%s}" % (", ".join("%s: %s" % (k, v) for k, v in strings)), imports
  232. # Datetimes
  233. elif isinstance(value, datetime.datetime):
  234. if value.tzinfo is not None:
  235. raise ValueError("Cannot serialize datetime values with timezones. Either use a callable value for default or remove the timezone.")
  236. value_repr = repr(value)
  237. if isinstance(value, datetime_safe.datetime):
  238. value_repr = "datetime.%s" % value_repr
  239. return value_repr, set(["import datetime"])
  240. # Dates
  241. elif isinstance(value, datetime.date):
  242. value_repr = repr(value)
  243. if isinstance(value, datetime_safe.date):
  244. value_repr = "datetime.%s" % value_repr
  245. return value_repr, set(["import datetime"])
  246. # Times
  247. elif isinstance(value, datetime.time):
  248. value_repr = repr(value)
  249. return value_repr, set(["import datetime"])
  250. # Settings references
  251. elif isinstance(value, SettingsReference):
  252. return "settings.%s" % value.setting_name, set(["from django.conf import settings"])
  253. # Simple types
  254. elif isinstance(value, six.integer_types + (float, bool, type(None))):
  255. return repr(value), set()
  256. elif isinstance(value, six.binary_type):
  257. value_repr = repr(value)
  258. if six.PY2:
  259. # Prepend the `b` prefix since we're importing unicode_literals
  260. value_repr = 'b' + value_repr
  261. return value_repr, set()
  262. elif isinstance(value, six.text_type):
  263. value_repr = repr(value)
  264. if six.PY2:
  265. # Strip the `u` prefix since we're importing unicode_literals
  266. value_repr = value_repr[1:]
  267. return value_repr, set()
  268. # Decimal
  269. elif isinstance(value, decimal.Decimal):
  270. return repr(value), set(["from decimal import Decimal"])
  271. # Django fields
  272. elif isinstance(value, models.Field):
  273. attr_name, path, args, kwargs = value.deconstruct()
  274. return cls.serialize_deconstructed(path, args, kwargs)
  275. # Anything that knows how to deconstruct itself.
  276. elif hasattr(value, 'deconstruct'):
  277. return cls.serialize_deconstructed(*value.deconstruct())
  278. # Functions
  279. elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
  280. # @classmethod?
  281. if getattr(value, "__self__", None) and isinstance(value.__self__, type):
  282. klass = value.__self__
  283. module = klass.__module__
  284. return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
  285. # Further error checking
  286. if value.__name__ == '<lambda>':
  287. raise ValueError("Cannot serialize function: lambda")
  288. if value.__module__ is None:
  289. raise ValueError("Cannot serialize function %r: No module" % value)
  290. # Python 3 is a lot easier, and only uses this branch if it's not local.
  291. if getattr(value, "__qualname__", None) and getattr(value, "__module__", None):
  292. if "<" not in value.__qualname__: # Qualname can include <locals>
  293. return "%s.%s" % (value.__module__, value.__qualname__), set(["import %s" % value.__module__])
  294. # Python 2/fallback version
  295. module_name = value.__module__
  296. # Make sure it's actually there and not an unbound method
  297. module = import_module(module_name)
  298. if not hasattr(module, value.__name__):
  299. raise ValueError(
  300. "Could not find function %s in %s.\nPlease note that "
  301. "due to Python 2 limitations, you cannot serialize "
  302. "unbound method functions (e.g. a method declared\n"
  303. "and used in the same class body). Please move the "
  304. "function into the main module body to use migrations.\n"
  305. "For more information, see https://docs.djangoproject.com/en/1.7/topics/migrations/#serializing-values"
  306. % (value.__name__, module_name))
  307. return "%s.%s" % (module_name, value.__name__), set(["import %s" % module_name])
  308. # Classes
  309. elif isinstance(value, type):
  310. special_cases = [
  311. (models.Model, "models.Model", []),
  312. ]
  313. for case, string, imports in special_cases:
  314. if case is value:
  315. return string, set(imports)
  316. if hasattr(value, "__module__"):
  317. module = value.__module__
  318. return "%s.%s" % (module, value.__name__), set(["import %s" % module])
  319. # Other iterables
  320. elif isinstance(value, collections.Iterable):
  321. imports = set()
  322. strings = []
  323. for item in value:
  324. item_string, item_imports = cls.serialize(item)
  325. imports.update(item_imports)
  326. strings.append(item_string)
  327. # When len(strings)==0, the empty iterable should be serialized as
  328. # "()", not "(,)" because (,) is invalid Python syntax.
  329. format = "(%s)" if len(strings) != 1 else "(%s,)"
  330. return format % (", ".join(strings)), imports
  331. # Compiled regex
  332. elif isinstance(value, COMPILED_REGEX_TYPE):
  333. imports = set(["import re"])
  334. regex_pattern, pattern_imports = cls.serialize(value.pattern)
  335. regex_flags, flag_imports = cls.serialize(value.flags)
  336. imports.update(pattern_imports)
  337. imports.update(flag_imports)
  338. args = [regex_pattern]
  339. if value.flags:
  340. args.append(regex_flags)
  341. return "re.compile(%s)" % ', '.join(args), imports
  342. # Uh oh.
  343. else:
  344. raise ValueError("Cannot serialize: %r\nThere are some values Django cannot serialize into migration files.\nFor more, see https://docs.djangoproject.com/en/dev/topics/migrations/#migration-serializing" % value)
  345. MIGRATION_TEMPLATE = """\
  346. # -*- coding: utf-8 -*-
  347. from __future__ import unicode_literals
  348. from django.db import models, migrations
  349. %(imports)s
  350. class Migration(migrations.Migration):
  351. %(replaces_str)s
  352. dependencies = [
  353. %(dependencies)s\
  354. ]
  355. operations = [
  356. %(operations)s\
  357. ]
  358. """