1+ import asyncio
2+ import os
3+ import warnings
4+ from typing import Optional
5+
6+ import click
7+
8+ from ...settings import get_root_migrations_dir
9+ from ..migrations .migrator import Migrator
10+
11+
12+ def run_async (coro ):
13+ """Run an async coroutine in an isolated event loop to avoid interfering with pytest loops."""
14+ import concurrent .futures
15+
16+ with concurrent .futures .ThreadPoolExecutor () as executor :
17+ future = executor .submit (asyncio .run , coro )
18+ return future .result ()
19+
20+
21+ def show_deprecation_warning ():
22+ """Show deprecation warning for the legacy migrate command."""
23+ warnings .warn (
24+ "The 'migrate' command is deprecated. Please use 'om migrate' for the new file-based migration system with rollback support." ,
25+ DeprecationWarning ,
26+ stacklevel = 3 ,
27+ )
28+ click .echo (
29+ click .style (
30+ "⚠️ DEPRECATED: The 'migrate' command uses automatic migrations. "
31+ "Use 'om migrate' for the new file-based system with rollback support." ,
32+ fg = "yellow" ,
33+ ),
34+ err = True ,
35+ )
36+
37+
38+ @click .group ()
39+ def migrate ():
40+ """[DEPRECATED] Automatic schema migrations for Redis OM models. Use 'om migrate' instead."""
41+ show_deprecation_warning ()
42+
43+
44+ @migrate .command ()
45+ @click .option ("--module" , help = "Python module to scan for models" )
46+ def status (module : Optional [str ]):
47+ """Show pending automatic migrations (no file-based tracking)."""
48+ migrator = Migrator (module = module )
49+
50+ async def _status ():
51+ await migrator .detect_migrations ()
52+ return migrator .migrations
53+
54+ migrations = run_async (_status ())
55+
56+ if not migrations :
57+ click .echo ("No pending automatic migrations detected." )
58+ return
59+
60+ click .echo ("Pending Automatic Migrations:" )
61+ for migration in migrations :
62+ action = "CREATE" if migration .action .name == "CREATE" else "DROP"
63+ click .echo (f" { action } : { migration .index_name } (model: { migration .model_name } )" )
64+
65+
66+ @migrate .command ()
67+ @click .option ("--module" , help = "Python module to scan for models" )
68+ @click .option (
69+ "--dry-run" , is_flag = True , help = "Show what would be done without applying changes"
70+ )
71+ @click .option ("--verbose" , "-v" , is_flag = True , help = "Enable verbose output" )
72+ @click .option (
73+ "--yes" ,
74+ "-y" ,
75+ is_flag = True ,
76+ help = "Skip confirmation prompt to run automatic migrations" ,
77+ )
78+ def run (
79+ module : Optional [str ],
80+ dry_run : bool ,
81+ verbose : bool ,
82+ yes : bool ,
83+ ):
84+ """Run automatic schema migrations (immediate DROP+CREATE)."""
85+ migrator = Migrator (module = module )
86+
87+ async def _run ():
88+ await migrator .detect_migrations ()
89+ if not migrator .migrations :
90+ if verbose :
91+ click .echo ("No pending automatic migrations found." )
92+ return 0
93+
94+ if dry_run :
95+ click .echo (f"Would run { len (migrator .migrations )} automatic migration(s):" )
96+ for migration in migrator .migrations :
97+ action = "CREATE" if migration .action .name == "CREATE" else "DROP"
98+ click .echo (f" { action } : { migration .index_name } " )
99+ return len (migrator .migrations )
100+
101+ if not yes :
102+ operations = []
103+ for migration in migrator .migrations :
104+ action = "CREATE" if migration .action .name == "CREATE" else "DROP"
105+ operations .append (f" { action } : { migration .index_name } " )
106+
107+ if not click .confirm (
108+ f"Run { len (migrator .migrations )} automatic migration(s)?\n "
109+ + "\n " .join (operations )
110+ ):
111+ click .echo ("Aborted." )
112+ return 0
113+
114+ await migrator .run ()
115+ if verbose :
116+ click .echo (
117+ f"Successfully applied { len (migrator .migrations )} automatic migration(s)."
118+ )
119+ return len (migrator .migrations )
120+
121+ run_async (_run ())
0 commit comments