@@ -53,72 +53,17 @@ async def migrate(
5353 direction : str = "both" ,
5454 progress : Callable [[str , str , str ], object ] | None = None ,
5555 ) -> None :
56- self ._logger .debug ("Building migration graph" )
57- await self .loader .build_graph ()
58-
59- # Create a non-atomic schema editor for recorder operations only
60- recorder_schema_editor = self ._schema_editor (atomic = False )
61- self ._logger .debug ("Ensuring migration schema" )
62- await self .recorder .ensure_schema (recorder_schema_editor )
63-
64- self ._logger .debug ("Loading applied migrations" )
65- applied = set (await self .recorder .applied_migrations ())
66-
67- self ._logger .debug ("Building migration plan" )
68- plan = self ._migration_plan (targets , applied , self .loader .graph )
69- self ._validate_plan_direction (plan , direction )
70-
71- state_cache_by_key : dict [MigrationKey , State ] | None = None
72- if any (step .backward for step in plan ):
73- self ._logger .debug ("Building rollback state cache" )
74- state_cache_by_key = await self ._project_state_cache (applied )
75-
76- state_cache : State | None = None
77- for step in plan :
78- key = MigrationKey (app_label = step .migration .app_label , name = step .migration .name )
79- if step .backward :
80- if state_cache_by_key is not None :
81- state_before = state_cache_by_key [key ]
82- else :
83- state_before = await self ._project_state (applied , upto = key )
84- if not fake :
85- self ._emit (progress , "rollback_start" , key )
86- schema_editor = self ._schema_editor (atomic = step .migration .atomic )
87- if schema_editor .atomic_migration :
88- async with in_transaction (self .connection .connection_name ) as txn_client :
89- schema_editor .client = txn_client
90- await step .migration .unapply (
91- state_before , dry_run = dry_run , schema_editor = schema_editor
92- )
93- else :
94- await step .migration .unapply (
95- state_before , dry_run = dry_run , schema_editor = schema_editor
96- )
97- self ._emit (progress , "rollback_done" , key )
98- if not dry_run :
99- await self .recorder .record_unapplied (key .app_label , key .name )
100- applied .discard (key )
101- state_cache = None
102- else :
103- if state_cache is None :
104- state_cache = await self ._project_state (applied )
105- if not fake :
106- self ._emit (progress , "apply_start" , key )
107- schema_editor = self ._schema_editor (atomic = step .migration .atomic )
108- if schema_editor .atomic_migration :
109- async with in_transaction (self .connection .connection_name ) as txn_client :
110- schema_editor .client = txn_client
111- await step .migration .apply (
112- state_cache , dry_run = dry_run , schema_editor = schema_editor
113- )
114- else :
115- await step .migration .apply (
116- state_cache , dry_run = dry_run , schema_editor = schema_editor
117- )
118- self ._emit (progress , "apply_done" , key )
119- if not dry_run :
120- await self .recorder .record_applied (key .app_label , key .name )
121- applied .add (key )
56+ plan , applied , state_cache_by_key = await self ._prepare_migration_run (
57+ targets , direction
58+ )
59+ await self ._run_plan (
60+ plan ,
61+ applied = applied ,
62+ state_cache_by_key = state_cache_by_key ,
63+ fake = fake ,
64+ dry_run = dry_run ,
65+ progress = progress ,
66+ )
12267
12368 async def plan (self , targets : Iterable [MigrationTarget ] | None = None ) -> list [PlanStep ]:
12469 await self .loader .build_graph ()
@@ -193,6 +138,112 @@ async def collect_sql(
193138
194139 return editor .collected_sql
195140
141+ async def _execute_plan_step (
142+ self ,
143+ step : PlanStep ,
144+ * ,
145+ applied : set [MigrationKey ],
146+ state_cache : State | None ,
147+ state_cache_by_key : dict [MigrationKey , State ] | None ,
148+ fake : bool ,
149+ dry_run : bool ,
150+ progress : Callable [[str , str , str ], object ] | None ,
151+ ) -> State | None :
152+ key = MigrationKey (app_label = step .migration .app_label , name = step .migration .name )
153+ if step .backward :
154+ if state_cache_by_key is not None :
155+ state_before = state_cache_by_key [key ]
156+ else :
157+ state_before = await self ._project_state (applied , upto = key )
158+ if not fake :
159+ self ._emit (progress , "rollback_start" , key )
160+ schema_editor = self ._schema_editor (atomic = step .migration .atomic )
161+ if schema_editor .atomic_migration :
162+ async with in_transaction (self .connection .connection_name ) as txn_client :
163+ schema_editor .client = txn_client
164+ await step .migration .unapply (
165+ state_before , dry_run = dry_run , schema_editor = schema_editor
166+ )
167+ else :
168+ await step .migration .unapply (
169+ state_before , dry_run = dry_run , schema_editor = schema_editor
170+ )
171+ self ._emit (progress , "rollback_done" , key )
172+ if not dry_run :
173+ await self .recorder .record_unapplied (key .app_label , key .name )
174+ applied .discard (key )
175+ return None
176+
177+ if state_cache is None :
178+ state_cache = await self ._project_state (applied )
179+ if not fake :
180+ self ._emit (progress , "apply_start" , key )
181+ schema_editor = self ._schema_editor (atomic = step .migration .atomic )
182+ if schema_editor .atomic_migration :
183+ async with in_transaction (self .connection .connection_name ) as txn_client :
184+ schema_editor .client = txn_client
185+ await step .migration .apply (
186+ state_cache , dry_run = dry_run , schema_editor = schema_editor
187+ )
188+ else :
189+ await step .migration .apply (
190+ state_cache , dry_run = dry_run , schema_editor = schema_editor
191+ )
192+ self ._emit (progress , "apply_done" , key )
193+ if not dry_run :
194+ await self .recorder .record_applied (key .app_label , key .name )
195+ applied .add (key )
196+ return state_cache
197+
198+ async def _prepare_migration_run (
199+ self ,
200+ targets : Iterable [MigrationTarget ] | None ,
201+ direction : str ,
202+ ) -> tuple [list [PlanStep ], set [MigrationKey ], dict [MigrationKey , State ] | None ]:
203+ self ._logger .debug ("Building migration graph" )
204+ await self .loader .build_graph ()
205+
206+ # Create a non-atomic schema editor for recorder operations only
207+ recorder_schema_editor = self ._schema_editor (atomic = False )
208+ self ._logger .debug ("Ensuring migration schema" )
209+ await self .recorder .ensure_schema (recorder_schema_editor )
210+
211+ self ._logger .debug ("Loading applied migrations" )
212+ applied = set (await self .recorder .applied_migrations ())
213+
214+ self ._logger .debug ("Building migration plan" )
215+ plan = self ._migration_plan (targets , applied , self .loader .graph )
216+ self ._validate_plan_direction (plan , direction )
217+
218+ state_cache_by_key : dict [MigrationKey , State ] | None = None
219+ if any (step .backward for step in plan ):
220+ self ._logger .debug ("Building rollback state cache" )
221+ state_cache_by_key = await self ._project_state_cache (applied )
222+
223+ return plan , applied , state_cache_by_key
224+
225+ async def _run_plan (
226+ self ,
227+ plan : list [PlanStep ],
228+ * ,
229+ applied : set [MigrationKey ],
230+ state_cache_by_key : dict [MigrationKey , State ] | None ,
231+ fake : bool ,
232+ dry_run : bool ,
233+ progress : Callable [[str , str , str ], object ] | None ,
234+ ) -> None :
235+ state_cache : State | None = None
236+ for step in plan :
237+ state_cache = await self ._execute_plan_step (
238+ step ,
239+ applied = applied ,
240+ state_cache = state_cache ,
241+ state_cache_by_key = state_cache_by_key ,
242+ fake = fake ,
243+ dry_run = dry_run ,
244+ progress = progress ,
245+ )
246+
196247 def _resolve_migration_key (self , app_label : str , migration_name : str ) -> MigrationKey :
197248 """Resolve a migration name to a MigrationKey, supporting prefix matching."""
198249 exact_key = MigrationKey (app_label = app_label , name = migration_name )
0 commit comments