1
+ # -*- coding: utf-8 -*-
2
+ """Alembic environment configuration for database migrations.
3
+
4
+ Copyright 2025
5
+ SPDX-License-Identifier: Apache-2.0
6
+ Authors: Mihai Criveti, Madhav Kandukuri
7
+
8
+ This module configures the Alembic migration environment for the MCP Gateway
9
+ application. It sets up both offline and online migration modes, configures
10
+ logging, and establishes the database connection parameters.
11
+
12
+ The module performs the following key functions:
13
+ - Configures Alembic to locate migration scripts in the mcpgateway package
14
+ - Sets up Python logging based on the alembic.ini configuration
15
+ - Imports the SQLAlchemy metadata from the application models
16
+ - Configures the database URL from application settings
17
+ - Provides functions for running migrations in both offline and online modes
18
+
19
+ Offline mode generates SQL scripts without connecting to the database, while
20
+ online mode executes migrations directly against a live database connection.
21
+
22
+ Attributes:
23
+ config (Config): The Alembic configuration object loaded from alembic.ini.
24
+ target_metadata (MetaData): SQLAlchemy metadata object containing all
25
+ table definitions from the application models.
26
+
27
+ Examples:
28
+ Running migrations in offline mode::
29
+
30
+ alembic upgrade head --sql
31
+
32
+ Running migrations in online mode::
33
+
34
+ alembic upgrade head
35
+
36
+ The module is typically not imported directly but is used by Alembic
37
+ when executing migration commands.
38
+
39
+ Note:
40
+ This file is automatically executed by Alembic and should not be
41
+ imported or run directly by application code.
42
+ """
43
+
44
+ # Standard
45
+ from importlib .resources import files
46
+ from logging .config import fileConfig
47
+
48
+ # Third-Party
49
+ from alembic import context
50
+
51
+ # this is the Alembic Config object, which provides
52
+ # access to the values within the .ini file in use.
53
+ from alembic .config import Config
54
+ from sqlalchemy import engine_from_config , pool
55
+
56
+ # First-Party
57
+ from mcpgateway .config import settings
58
+ from mcpgateway .db import Base
59
+
60
+ # from mcpgateway.db import get_metadata
61
+ # target_metadata = get_metadata()
62
+
63
+
64
+ # Create config object - this is the standard way in Alembic
65
+ config = getattr (context , "config" , None ) or Config ()
66
+
67
+
68
+ def _inside_alembic () -> bool :
69
+ """Detect if this module is being executed by the Alembic CLI.
70
+
71
+ This function checks whether the current execution context is within
72
+ an Alembic migration environment. It's used to prevent migration code
73
+ from running when this module is imported for other purposes (e.g.,
74
+ during testing or when importing models).
75
+
76
+ The detection works by checking for the presence of the '_proxy' attribute
77
+ on the alembic.context object. This attribute is set internally by Alembic
78
+ when it loads and executes the env.py file during migration operations.
79
+
80
+ Returns:
81
+ bool: True if running under Alembic CLI (e.g., during 'alembic upgrade',
82
+ 'alembic downgrade', etc.), False if imported normally by Python
83
+ code or during testing.
84
+
85
+ Examples:
86
+ When running migrations::
87
+
88
+ $ alembic upgrade head
89
+ # _inside_alembic() returns True
90
+
91
+ When importing in tests or application code::
92
+
93
+ from mcpgateway.alembic.env import target_metadata
94
+ # _inside_alembic() returns False
95
+
96
+ Note:
97
+ This guard is crucial to prevent the migration execution code at the
98
+ bottom of this module from running during normal imports. Without it,
99
+ importing this module would attempt to run migrations every time.
100
+ """
101
+ return getattr (context , "_proxy" , None ) is not None
102
+
103
+
104
+ config .set_main_option ("script_location" , str (files ("mcpgateway" ).joinpath ("alembic" )))
105
+
106
+ # Interpret the config file for Python logging.
107
+ # This line sets up loggers basically.
108
+ if config .config_file_name is not None :
109
+ fileConfig (
110
+ config .config_file_name ,
111
+ disable_existing_loggers = False ,
112
+ )
113
+
114
+ # First-Party
115
+ # add your model's MetaData object here
116
+ # for 'autogenerate' support
117
+ # from myapp import mymodel
118
+
119
+ target_metadata = Base .metadata
120
+
121
+ # other values from the config, defined by the needs of env.py,
122
+ # can be acquired:
123
+ # my_important_option = config.get_main_option("my_important_option")
124
+ # ... etc.
125
+
126
+ config .set_main_option (
127
+ "sqlalchemy.url" ,
128
+ settings .database_url ,
129
+ )
130
+
131
+
132
+ def run_migrations_offline () -> None :
133
+ """Run migrations in 'offline' mode.
134
+
135
+ This configures the context with just a URL
136
+ and not an Engine, though an Engine is acceptable
137
+ here as well. By skipping the Engine creation
138
+ we don't even need a DBAPI to be available.
139
+
140
+ Calls to context.execute() here emit the given string to the
141
+ script output.
142
+
143
+ """
144
+ url = config .get_main_option ("sqlalchemy.url" )
145
+ context .configure (
146
+ url = url ,
147
+ target_metadata = target_metadata ,
148
+ literal_binds = True ,
149
+ dialect_opts = {"paramstyle" : "named" },
150
+ )
151
+
152
+ with context .begin_transaction ():
153
+ context .run_migrations ()
154
+
155
+
156
+ def run_migrations_online () -> None :
157
+ """Run migrations in 'online' mode.
158
+
159
+ In this scenario we need to create an Engine
160
+ and associate a connection with the context.
161
+
162
+ """
163
+ connection = config .attributes .get ("connection" )
164
+ if connection is None :
165
+ connectable = engine_from_config (
166
+ config .get_section (config .config_ini_section , {}),
167
+ prefix = "sqlalchemy." ,
168
+ poolclass = pool .NullPool ,
169
+ )
170
+
171
+ with connectable .connect () as connection :
172
+ context .configure (connection = connection , target_metadata = target_metadata )
173
+
174
+ with context .begin_transaction ():
175
+ context .run_migrations ()
176
+ else :
177
+ context .configure (connection = connection , target_metadata = target_metadata )
178
+
179
+ with context .begin_transaction ():
180
+ context .run_migrations ()
181
+
182
+
183
+ if _inside_alembic ():
184
+ if context .is_offline_mode ():
185
+ run_migrations_offline ()
186
+ else :
187
+ run_migrations_online ()
0 commit comments