1 from migration_loader
import load_migration_from_path
2 from exceptions
import *
7 class MigrationDb(object):
9 def __init__(self
, directory
=None, migrations
=None):
10 self
.directory
= directory
11 self
._migrations
= migrations
16 Lazy migrations property. Mostly to make overriding in tests easier.
18 if self
._migrations
== None:
19 self
.populate_migrations_from_directory()
21 return self
._migrations
23 def populate_migrations_from_ls(self
, ls
):
25 Populate a list of migrations based on directory listing.
26 Separate from populate_migrations_from_directory for easier testing.
29 re
.sub(r
'\.py$', '', file_name
)
31 if re
.search(r
'^\d+_.*\.py$', file_name
)
33 self
.warn_if_duplicate_migration_numbers()
35 def migration_number(self
, migration
):
37 Return migration number based on migration name like <int>_<anything>
39 m
= re
.search(r
'(\d+)_', migration
)
41 return int(m
.group(1))
43 raise Exception(u
"%s is not a valid migration name" % migration
)
45 def migration_sort_key(self
, migration
):
47 Return sort key for migrations - by number first, then by ascii.
49 return (self
.migration_number(migration
), migration
)
51 def sort_migrations(self
, migration_list
):
53 Return sorted list of migrations.
55 return sorted(migration_list
, key
=self
.migration_sort_key
)
57 def warn_if_duplicate_migration_numbers(self
):
59 Warn if there are multiple migrations with the same number.
60 This situation is explicitly SUPPORTED, so it's not an error,
61 but more likely than not it's not what you want to do.
65 for migration_name
in self
.migrations
:
66 i
= self
.migration_number(migration_name
)
67 by_number
.setdefault(i
, [])
68 by_number
[i
].append(migration_name
)
70 for number
in sorted(by_number
.keys()):
71 if len(by_number
[number
]) > 1:
73 u
"There are multiple migrations with the same number "
74 "%d: %s" % (number
, ", ".join(
75 sorted(by_number
[number
])
79 def warn(self
, warning
):
82 Separate function for easy testing.
84 print >>sys
.stderr
, u
"Warning: %s" % warning
86 def populate_migrations_from_directory(self
):
88 Populate list of migrations based on self.directory.
90 if self
.directory
== None:
91 self
.populate_migrations_from_ls([])
93 self
.populate_migrations_from_ls(os
.listdir(self
.directory
))
97 Return ordered list of migrations in the database.
99 return self
.sort_migrations(self
.migrations
)
101 def is_dev_migration(self
, name
):
103 Migration is a DEV migration if it has string "_DEV_" in its name.
105 return bool(re
.search(r
'_DEV_', name
))
107 def find_unique_migration_by_number(self
, number
):
108 matching_migrations
= [
109 name
for name
in self
.migrations
110 if self
.migration_number(name
) == number
113 if len(matching_migrations
) == 0:
115 elif len(matching_migrations
) == 1:
116 return matching_migrations
[0]
118 raise AmbiguousMigrationNameError(number
)
120 def force_resolve_migration_name(self
, name
):
122 Take either full name or a number, and return full name for an
125 if name
in self
.migrations
:
127 elif re
.search(r
'^\d+$', str(name
)):
128 resolved_migration_name
= self
.find_unique_migration_by_number(
131 if resolved_migration_name
is None:
132 raise NoSuchMigrationError(name
)
134 return resolved_migration_name
136 raise NoSuchMigrationError(name
)
138 def resolve_migration_path(self
, name
):
140 Return path for existing migration name.
142 name
= self
.force_resolve_migration_name(name
)
143 return os
.path
.join(self
.directory
, name
+ ".py")
145 def migration_path(self
, name
):
147 Return path for new migration name.
149 number
= 1 + max([0] + [
150 self
.migration_number(migration
) for migration
in self
.migrations
153 return u
"%s/%03d_%s.py" % (self
.directory
, number
, name
)
155 def load_migration_object(self
, name
):
157 Get migration with given name or number.
160 name
= self
.force_resolve_migration_name(name
)
161 full_path
= self
.resolve_migration_path(name
)
162 dev
= self
.is_dev_migration(name
)
164 return load_migration_from_path(full_path
, dev
=dev
)