Skip to content

Commit 971e0cf

Browse files
committed
custom alembic cli for upgrade/downgrade
1 parent cf23e08 commit 971e0cf

File tree

5 files changed

+43
-30
lines changed

5 files changed

+43
-30
lines changed

Diff for: alembic.ini

+1-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[alembic]
44
# path to migration scripts
5-
script_location = alembic
5+
script_location = %(here)s/alembic
66

77
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
88
# Uncomment the line below if you want the files to be prepended with date and time
@@ -60,12 +60,6 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
6060
# are written from script.py.mako
6161
# output_encoding = utf-8
6262

63-
drivername =
64-
username =
65-
password =
66-
host =
67-
port =
68-
database =
6963
sqlalchemy.url = %(drivername)s://%(username)s:%(password)s@%(host)s:%(port)s/%(database)s
7064

7165
[post_write_hooks]

Diff for: alembic/env.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import alembic.context
2020
import cads_broker
2121

22-
config = alembic.context.config
23-
2422

2523
def run_migrations_offline() -> None:
2624
"""Run migrations in 'offline' mode.
@@ -33,11 +31,7 @@ def run_migrations_offline() -> None:
3331
Calls to alembic.context.execute() here emit the given string to the
3432
script output.
3533
"""
36-
url_props = dict()
37-
for prop in ["drivername", "username", "password", "host", "port", "database"]:
38-
url_props[prop] = config.get_main_option(prop)
39-
url_props["port"] = url_props["port"] and int(url_props["port"]) or None # type: ignore
40-
url = sa.engine.URL.create(**url_props) # type: ignore
34+
url = alembic.context.config.get_main_option("sqlalchemy.url")
4135
alembic.context.configure(
4236
url=url,
4337
target_metadata=cads_broker.database.BaseModel.metadata,
@@ -54,12 +48,8 @@ def run_migrations_online() -> None:
5448
In this scenario we need to create an Engine
5549
and associate a connection with the alembic.context.
5650
"""
57-
url_props = dict()
58-
for prop in ["drivername", "username", "password", "host", "port", "database"]:
59-
url_props[prop] = config.get_main_option(prop)
60-
url_props["port"] = url_props["port"] and int(url_props["port"]) or None # type: ignore
61-
url = sa.engine.URL.create(**url_props) # type: ignore
62-
engine = sa.create_engine(url, poolclass=sa.pool.NullPool)
51+
url = alembic.context.config.get_main_option("sqlalchemy.url")
52+
engine = sa.create_engine(url, poolclass=sa.pool.NullPool) # type: ignore
6353
with engine.connect() as connection:
6454
alembic.context.configure(
6555
connection=connection,

Diff for: cads_broker/alembic_cli.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Custom alembic CLI with new default config path + db url by environment."""
2+
3+
import os
4+
5+
import cads_broker.config
6+
from alembic.config import CommandLine, Config
7+
8+
alembic_ini_path = os.path.abspath(os.path.join(__file__, "..", "..", "alembic.ini"))
9+
10+
11+
class MyCommandLine(CommandLine):
12+
def main(self, argv=None):
13+
options = self.parser.parse_args(argv)
14+
if not hasattr(options, "cmd"):
15+
self.parser.error("too few arguments")
16+
else:
17+
cfg = Config(
18+
file_=options.config,
19+
ini_section=options.name,
20+
cmd_opts=options,
21+
)
22+
url = cads_broker.config.ensure_settings().connection_string.replace(
23+
"%", "%%"
24+
)
25+
cfg.set_main_option("sqlalchemy.url", url)
26+
self.run_cmd(cfg, options)
27+
28+
29+
def main():
30+
cli = MyCommandLine(prog="alembic-cli")
31+
config_in_parser = [p for p in cli.parser._actions if p.dest == "config"][0]
32+
config_in_parser.default = alembic_ini_path
33+
config_in_parser.help = f'Alternate config file; defaults to "{alembic_ini_path}"'
34+
cli.main()

Diff for: cads_broker/database.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import alembic.command
2121
import alembic.config
22-
from cads_broker import config
22+
from cads_broker import alembic_cli, config
2323

2424
BaseModel = sa.orm.declarative_base()
2525

@@ -932,15 +932,9 @@ def init_database(connection_string: str, force: bool = False) -> sa.engine.Engi
932932
:param force: if True, drop the database structure and build again from scratch
933933
"""
934934
engine = sa.create_engine(connection_string)
935-
migration_directory = os.path.abspath(os.path.join(__file__, "..", ".."))
936-
os.chdir(migration_directory)
937-
alembic_config_path = os.path.join(migration_directory, "alembic.ini")
938-
alembic_cfg = alembic.config.Config(alembic_config_path)
939-
for option in ["drivername", "username", "password", "host", "port", "database"]:
940-
value = getattr(engine.url, option)
941-
if value is None:
942-
value = ""
943-
alembic_cfg.set_main_option(option, str(value))
935+
alembic_cfg = alembic.config.Config(alembic_cli.alembic_ini_path)
936+
# passwords with special chars are urlencoded, but '%' must be escaped in ini files
937+
alembic_cfg.set_main_option("sqlalchemy.url", connection_string.replace("%", "%%"))
944938
if not sqlalchemy_utils.database_exists(engine.url):
945939
sqlalchemy_utils.create_database(engine.url)
946940
# cleanup and create the schema

Diff for: pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ readme = "README.md"
3131

3232
[project.scripts]
3333
broker = "cads_broker.entry_points:main"
34+
broker-alembic-cli = "cads_broker.alembic_cli:main"
3435

3536
[tool.coverage.run]
3637
branch = true

0 commit comments

Comments
 (0)