1- import asyncio
21from collections .abc import Callable
3- from typing import Literal
2+ from typing import Any , Literal , cast
43
5- # Import required for alembic postgresql enum support
64import alembic_postgresql_enum # noqa: F401 # pyright: ignore[reportUnusedImport]
75from alembic import context
86from sqlalchemy import MetaData
1311
1412# Import models to populate metadata
1513# We need to import the actual model classes, not just the modules
16- from tux .database .models .content import Reminder , Snippet
17- from tux .database .models .guild import Guild , GuildConfig
18- from tux .database .models .moderation import Case , CaseType , CustomCaseType , Note
19- from tux .database .models .permissions import AccessType , GuildPermission , PermissionType
20- from tux .database .models .social import AFK , Levels
21- from tux .database .models .starboard import Starboard , StarboardMessage
14+ from tux .database .models import (
15+ AccessType ,
16+ AFK ,
17+ Case ,
18+ CaseType ,
19+ Guild ,
20+ GuildConfig ,
21+ GuildPermission ,
22+ Levels ,
23+ Note ,
24+ PermissionType ,
25+ Reminder ,
26+ Snippet ,
27+ Starboard ,
28+ StarboardMessage ,
29+ )
2230from tux .shared .config .env import get_database_url
2331
24- config = context .config
25-
26- if not config .get_main_option ("sqlalchemy.url" ):
32+ # Get config from context if available, otherwise create a minimal one
33+ try :
34+ config = context .config
35+ except AttributeError :
36+ # Not in an Alembic context, create a minimal config for testing
37+ from alembic .config import Config
38+ config = Config ()
2739 config .set_main_option ("sqlalchemy.url" , get_database_url ())
2840
2941naming_convention = {
30- "ix" : "ix_%(column_0_label) s" ,
31- "uq" : "uq_%(table_name)s_%(column_0_name )s" ,
42+ "ix" : "ix_%(table_name)s_%(column_0_N_name) s" , # More specific index naming
43+ "uq" : "uq_%(table_name)s_%(column_0_N_name )s" , # Support for multi-column constraints
3244 "ck" : "ck_%(table_name)s_%(constraint_name)s" ,
3345 "fk" : "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s" ,
3446 "pk" : "pk_%(table_name)s" ,
4759 GuildConfig ,
4860 Case ,
4961 CaseType ,
50- CustomCaseType ,
5162 Note ,
5263 GuildPermission ,
5364 PermissionType ,
@@ -81,19 +92,55 @@ def run_migrations_offline() -> None:
8192 dialect_opts = {"paramstyle" : "named" },
8293 render_as_batch = True ,
8394 include_object = include_object ,
95+ # Match online configuration for consistency
96+ include_schemas = False ,
97+ upgrade_token = "upgrades" ,
98+ downgrade_token = "downgrades" ,
99+ alembic_module_prefix = "op." ,
100+ sqlalchemy_module_prefix = "sa." ,
101+ transaction_per_migration = True ,
84102 )
85103
86104 with context .begin_transaction ():
87105 context .run_migrations ()
88106
89107
90- async def run_async_migrations () -> None :
91- connectable = async_engine_from_config (
92- config .get_section (config .config_ini_section , {}),
93- prefix = "sqlalchemy." ,
94- pool_pre_ping = True ,
95- )
96-
108+ def run_migrations_online () -> None :
109+ """Run migrations in 'online' mode - handles both sync and async."""
110+ # Check if pytest-alembic has provided a connection
111+ connectable = context .config .attributes .get ("connection" , None )
112+
113+ if connectable is None :
114+ # Get configuration section, providing default URL if not found
115+ config_section = config .get_section (config .config_ini_section , {})
116+
117+ # If URL is not in the config section, get it from our environment function
118+ if "sqlalchemy.url" not in config_section :
119+ from tux .shared .config .env import get_database_url
120+ config_section ["sqlalchemy.url" ] = get_database_url ()
121+
122+ connectable = async_engine_from_config (
123+ config_section ,
124+ prefix = "sqlalchemy." ,
125+ pool_pre_ping = True ,
126+ )
127+
128+ # Handle both sync and async connections
129+ if hasattr (connectable , 'connect' ) and hasattr (connectable , 'dispose' ) and hasattr (connectable , '_is_asyncio' ):
130+ # This is an async engine - run async migrations
131+ import asyncio
132+ asyncio .run (run_async_migrations (connectable ))
133+ elif hasattr (connectable , 'connect' ):
134+ # It's a sync engine, get connection from it
135+ with cast (Connection , connectable .connect ()) as connection :
136+ do_run_migrations (connection )
137+ else :
138+ # It's already a connection
139+ do_run_migrations (connectable ) # type: ignore[arg-type]
140+
141+
142+ async def run_async_migrations (connectable : Any ) -> None :
143+ """Run async migrations when we have an async engine."""
97144 async with connectable .connect () as connection :
98145 callback : Callable [[Connection ], None ] = do_run_migrations
99146 await connection .run_sync (callback )
@@ -109,19 +156,30 @@ def do_run_migrations(connection: Connection) -> None:
109156 compare_server_default = True ,
110157 render_as_batch = True ,
111158 include_object = include_object ,
112- # Enhanced configuration for better timezone handling
159+ # Enhanced configuration for better migration generation
113160 process_revision_directives = None ,
161+ # Additional options for better migration quality
162+ include_schemas = False , # Focus on public schema
163+ upgrade_token = "upgrades" ,
164+ downgrade_token = "downgrades" ,
165+ alembic_module_prefix = "op." ,
166+ sqlalchemy_module_prefix = "sa." ,
167+ # Enable transaction per migration for safety
168+ transaction_per_migration = True ,
114169 )
115170
116171 with context .begin_transaction ():
117172 context .run_migrations ()
118173
119174
120- def run_migrations_online () -> None :
121- asyncio .run (run_async_migrations ())
122-
175+ # Only run migrations if we're in an Alembic context
123176
124- if context .is_offline_mode ():
125- run_migrations_offline ()
126- else :
127- run_migrations_online ()
177+ # sourcery skip: use-contextlib-suppress
178+ import contextlib
179+ with contextlib .suppress (NameError ):
180+ try :
181+ if hasattr (context , 'is_offline_mode' ) and context .is_offline_mode ():
182+ run_migrations_offline ()
183+ except (AttributeError , NameError ):
184+ # Context is not available or not properly initialized
185+ pass
0 commit comments