@@ -21,9 +21,7 @@ def exists(schema: str, *table_names: str) -> DbEvaluator:
2121
2222def not_exists (schema : str , * table_names : str ) -> DbEvaluator :
2323 """Return a function that evaluates to true when every given table in the given schema doesn't exist"""
24- return lambda db : all (
25- not db .inspector .has_table (t , schema = schema ) for t in table_names
26- )
24+ return _not (exists (schema , * table_names ))
2725
2826
2927def schema_exists (schema : str ) -> DbEvaluator :
@@ -50,6 +48,11 @@ def custom_type_exists(schema: str, *type_names: str) -> DbEvaluator:
5048 return lambda db : all (db .inspector .has_type (t , schema = schema ) for t in type_names )
5149
5250
51+ def _not (f : DbEvaluator ) -> DbEvaluator :
52+ """Return a function that evaluates to true when the given function evaluates to false"""
53+ return lambda db : not f (db )
54+
55+
5356class ApplicationStatus (Enum ):
5457 """Enum for the possible"""
5558
@@ -109,6 +112,16 @@ def apply(self, database: Database):
109112 database .run_fixtures (child_cls_dir )
110113
111114
115+ class MigrationState (Enum ):
116+ """Enum for the possible states of a migration before application"""
117+
118+ COMPLETE = "complete"
119+ UNMET_DEPENDENCIES = "unmet_dependencies"
120+ CANNOT_APPLY = "cannot_apply"
121+ SHOULD_APPLY = "should_apply"
122+ DISALLOWED = "disallowed"
123+
124+
112125def run_migrations (
113126 apply : bool = False ,
114127 name : str = None ,
@@ -136,6 +149,11 @@ def run_migrations(
136149 # While iterating over migrations, keep track of which have already applied
137150 completed_migrations = []
138151
152+ # Get max width of migration names for formatting
153+ name_max_width = max (len (m .name ) for m in instances )
154+
155+ print ("Migrations:" )
156+
139157 for _migration in instances :
140158 _name = _migration .name
141159 _subsystem = getattr (_migration , "subsystem" , None )
@@ -153,39 +171,34 @@ def run_migrations(
153171 if subsystem is not None and subsystem != _subsystem :
154172 continue
155173
174+ _status = _get_status (_migration , completed_migrations )
175+
176+ _print_status (_name , _status , name_max_width = name_max_width )
177+
156178 # By default, don't run migrations that depend on other non-applied migrations
157179 dependencies_met = all (d in completed_migrations for d in _migration .depends_on )
158180 if not dependencies_met and not force :
159- print (f"Dependencies not met for migration [cyan]{ _name } [/cyan]" )
160181 continue
161182
162- if force or apply_status == ApplicationStatus .CAN_APPLY :
163- if not apply :
164- print (f"Would apply migration [cyan]{ _name } [/cyan]" )
165- else :
166- if _migration .destructive and not data_changes and not force :
167- print (
168- f"Migration [cyan]{ _name } [/cyan] would alter data in the database. Run with --force or --data-changes"
169- )
170- return
171-
172- print (f"Applying migration [cyan]{ _name } [/cyan]" )
173- _migration .apply (db )
174- # After running migration, reload the database and confirm that application was sucessful
175- db = refresh_database ()
176- if _migration .should_apply (db ) == ApplicationStatus .APPLIED :
177- completed_migrations .append (_migration .name )
178- elif apply_status == ApplicationStatus .APPLIED :
179- print (f"Migration [cyan]{ _name } [/cyan] already applied" )
180- else :
181- print (f"Migration [cyan]{ _name } [/cyan] cannot apply" )
183+ if (force or apply_status == ApplicationStatus .CAN_APPLY ) and apply :
184+ if _migration .destructive and not data_changes and not force :
185+ return
186+
187+ _migration .apply (db )
188+ # After running migration, reload the database and confirm that application was sucessful
189+ db = refresh_database ()
190+ if _migration .should_apply (db ) == ApplicationStatus .APPLIED :
191+ completed_migrations .append (_migration .name )
182192
183193 # Short circuit after applying the migration specified by --name
184194 if name is not None and name == _name :
185195 break
186196
187- # Notify PostgREST to reload the schema cache
188- db .run_sql ("NOTIFY pgrst, 'reload schema';" )
197+ if apply :
198+ # Notify PostgREST to reload the schema cache
199+ db .run_sql ("NOTIFY pgrst, 'reload schema';" )
200+ else :
201+ print ("\n [dim]To apply the migrations, run with --apply" )
189202
190203
191204def migration_has_been_run (* names : str ):
@@ -202,3 +215,45 @@ def migration_has_been_run(*names: str):
202215 if apply_status != ApplicationStatus .APPLIED :
203216 return True
204217 return False
218+
219+
220+ def _get_status (
221+ _migration : Migration , completed_migrations : set [str ]
222+ ) -> MigrationState :
223+ """Get the status of a migration"""
224+ name = _migration .name
225+
226+ # By default, don't run migrations that depend on other non-applied migrations
227+ dependencies_met = all (d in completed_migrations for d in _migration .depends_on )
228+ if not dependencies_met and not force :
229+ return MigrationState .UNMET_DEPENDENCIES
230+
231+ if name in completed_migrations :
232+ return MigrationState .COMPLETE
233+
234+ if force or apply_status == ApplicationStatus .CAN_APPLY :
235+ if not apply :
236+ return MigrationState .SHOULD_APPLY
237+ else :
238+ if _migration .destructive and not data_changes and not force :
239+ return MigrationState .DISALLOWED
240+ return MigrationState .SHOULD_APPLY
241+
242+ return MigrationState .CANNOT_APPLY
243+
244+
245+ def _print_status (name , status : MigrationState , * , name_max_width = 40 ):
246+ padding = " " * (name_max_width - len (name ))
247+ print (f"- [bold cyan]{ name } [/]: " + padding , end = "" )
248+ if status == MigrationState .COMPLETE :
249+ print ("[green]already applied[/green]" )
250+ elif status == MigrationState .UNMET_DEPENDENCIES :
251+ print ("[yellow]has unmet dependencies[/yellow]" )
252+ elif status == MigrationState .CANNOT_APPLY :
253+ print ("[red]cannot be applied[/red]" )
254+ elif status == MigrationState .SHOULD_APPLY :
255+ print ("[yellow]should be applied[/yellow]" )
256+ elif status == MigrationState .DISALLOWED :
257+ print ("[red]cannot be applied without --force or --data-changes[/red]" )
258+ else :
259+ raise ValueError (f"Unknown migration status: { status } " )
0 commit comments