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