Skip to content

Commit a245c1d

Browse files
committed
Use alembic instead of sqlalchemy-migrate for migrations
Existing databases are assumed to have all existing migrations applied. On upgrade, we'll detect the presence of the `migrate_version` table, and set the alembic version to the initial/current schema, `0001`. Also remove the setuptools pin since it was there for sqlalchemy-migrate's benefit.
1 parent 9ebf0d8 commit a245c1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1114
-2177
lines changed

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ readme = "README.rst"
1010
requires-python = ">=3.13"
1111
dependencies = [
1212
"aiohttp",
13+
"alembic",
1314
"arrow",
1415
"auth0-python>=5",
1516
"connexion<3",
@@ -31,10 +32,8 @@ dependencies = [
3132
"requests",
3233
"requests-hawk",
3334
"sentry-sdk[flask]",
34-
# sqlalchemy-migrate tries to import pkg_resources which doesn't exist anymore after setuptools 82
35-
"setuptools<82",
35+
"setuptools",
3636
"spec-synthase",
37-
"sqlalchemy-migrate",
3837
"sqlalchemy<2",
3938
"statsd",
4039
"swagger-spec-validator",

scripts/manage-db.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def extract_active_data(trans, url, dump_location="dump.sql"):
157157

158158
# Now extract the data we actually want....
159159
# We always want all the data from a few tables...
160-
run(mysql_data_only_command(host, user, password, db, "dockerflow rules rules_history migrate_version").split(), stdout=dump_file, check=True)
160+
run(mysql_data_only_command(host, user, password, db, "dockerflow rules rules_history alembic_version").split(), stdout=dump_file, check=True)
161161

162162
release_names = get_active_release_names(trans)
163163
extract_releases(release_names, url, dump_file)
@@ -195,7 +195,7 @@ def _strip_multiple_spaces(string):
195195
usage += " cleanup-dryrun: Show what would be removed if 'cleanup' is run."
196196
parser = OptionParser(usage=usage)
197197
parser.add_option("-d", "--db", dest="db", default=None, help="database to manage, in URI format")
198-
parser.add_option("--version", dest="version", default=None, type="int", help="Create/upgrade to this specific schema version rather than the latest.")
198+
parser.add_option("--version", dest="version", default=None, type="string", help="Create/upgrade to this specific schema version rather than the latest.")
199199
options, args = parser.parse_args()
200200

201201
if not options.db:

src/auslib/alembic/env.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""Alembic environment configuration.
2+
3+
Supports both programmatic use (engine passed via config.attributes["connection"])
4+
and CLI use (for developers running `alembic revision --autogenerate`).
5+
"""
6+
7+
from logging.config import fileConfig
8+
9+
from alembic import context
10+
from sqlalchemy import engine_from_config, pool
11+
12+
# this is the Alembic Config object, which provides
13+
# access to the values within the .ini file in use.
14+
config = context.config
15+
16+
# Interpret the config file for Python logging.
17+
# This line sets up loggers basically.
18+
if config.config_file_name is not None:
19+
# disable_existing_loggers=False is required to preserve loggers set up before
20+
# migrations run
21+
fileConfig(config.config_file_name, disable_existing_loggers=False)
22+
23+
# Get target_metadata from config attributes (set programmatically)
24+
# or create an AUSDatabase instance for CLI/autogenerate use
25+
target_metadata = config.attributes.get("target_metadata")
26+
if target_metadata is None:
27+
# CLI mode - create a database instance to get metadata
28+
from auslib.db import AUSDatabase
29+
30+
db = AUSDatabase()
31+
# Get database URI from alembic.ini or environment
32+
db_uri = config.get_main_option("sqlalchemy.url")
33+
if db_uri:
34+
db.setDburi(db_uri)
35+
target_metadata = db.metadata
36+
else:
37+
raise ValueError(
38+
"No target_metadata in config.attributes and no sqlalchemy.url in config. "
39+
"Either pass metadata programmatically or set sqlalchemy.url in alembic.ini"
40+
)
41+
42+
43+
def run_migrations_offline() -> None:
44+
"""Run migrations in 'offline' mode.
45+
46+
This configures the context with just a URL
47+
and not an Engine, though an Engine is acceptable
48+
here as well. By skipping the Engine creation
49+
we don't even need a DBAPI to be available.
50+
51+
Calls to context.execute() here emit the given string to the
52+
script output.
53+
54+
"""
55+
url = config.get_main_option("sqlalchemy.url")
56+
context.configure(
57+
url=url,
58+
target_metadata=target_metadata,
59+
literal_binds=True,
60+
dialect_opts={"paramstyle": "named"},
61+
)
62+
63+
with context.begin_transaction():
64+
context.run_migrations()
65+
66+
67+
def run_migrations_online() -> None:
68+
"""Run migrations in 'online' mode.
69+
70+
In this scenario we need to create an Engine
71+
and associate a connection with the context.
72+
73+
"""
74+
# Check if a connection was passed programmatically
75+
connectable = config.attributes.get("connection")
76+
77+
if connectable is None:
78+
# CLI mode - create engine from config
79+
connectable = engine_from_config(
80+
config.get_section(config.config_ini_section, {}),
81+
prefix="sqlalchemy.",
82+
poolclass=pool.NullPool,
83+
)
84+
85+
with connectable.connect() as connection:
86+
context.configure(connection=connection, target_metadata=target_metadata)
87+
88+
with context.begin_transaction():
89+
context.run_migrations()
90+
91+
92+
if context.is_offline_mode():
93+
run_migrations_offline()
94+
else:
95+
run_migrations_online()

src/auslib/alembic/script.py.mako

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
${imports if imports else ""}
11+
12+
# revision identifiers, used by Alembic.
13+
revision = ${repr(up_revision)}
14+
down_revision = ${repr(down_revision)}
15+
branch_labels = ${repr(branch_labels)}
16+
depends_on = ${repr(depends_on)}
17+
18+
19+
def upgrade() -> None:
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade() -> None:
24+
${downgrades if downgrades else "pass"}

0 commit comments

Comments
 (0)