Skip to content

Commit bea0442

Browse files
authored
Merge pull request IBM#184 from IBM/alembic-scaffolding-test
Add alembic initial setup Signed-off-by: Vicky Kuo <vicky.kuo@ibm.com>
2 parents 6887ed4 + 1d6f17b commit bea0442

File tree

8 files changed

+512
-2
lines changed

8 files changed

+512
-2
lines changed

Makefile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,3 +2126,50 @@ shfmt-fix: shell-linters-install ## 🎨 Auto-format *.sh in place
21262126
@echo "🎨 Formatting shell scripts with shfmt -w…"
21272127
@shfmt -w -i 4 -ci $(SHELL_SCRIPTS)
21282128
@echo "✅ shfmt formatting done."
2129+
2130+
2131+
# 🛢️ ALEMBIC DATABASE MIGRATIONS
2132+
# =============================================================================
2133+
# help: 🛢️ ALEMBIC DATABASE MIGRATIONS
2134+
# help: alembic-install - Install Alembic CLI (and SQLAlchemy) in the current env
2135+
# help: db-new - Create a new migration (override with MSG="your title")
2136+
# help: db-up - Upgrade DB to the latest revision (head)
2137+
# help: db-down - Downgrade one revision (override with REV=<id|steps>)
2138+
# help: db-current - Show the current head revision for the database
2139+
# help: db-history - Show the full migration graph / history
2140+
# help: db-revision-id - Echo just the current revision id (handy for scripting)
2141+
# -----------------------------------------------------------------------------
2142+
2143+
# ──────────────────────────
2144+
# Internals & defaults
2145+
# ──────────────────────────
2146+
ALEMBIC ?= alembic # Override to e.g. `poetry run alembic`
2147+
MSG ?= "auto migration"
2148+
REV ?= -1 # Default: one step down; can be hash, -n, +n, etc.
2149+
2150+
.PHONY: alembic-install db-new db-up db-down db-current db-history db-revision-id
2151+
2152+
alembic-install:
2153+
@echo "➜ Installing Alembic …"
2154+
pip install --quiet alembic sqlalchemy
2155+
2156+
db-new:
2157+
@echo "➜ Generating revision: $(MSG)"
2158+
$(ALEMBIC) revision --autogenerate -m $(MSG)
2159+
2160+
db-up:
2161+
@echo "➜ Upgrading database to head …"
2162+
$(ALEMBIC) upgrade head
2163+
2164+
db-down:
2165+
@echo "➜ Downgrading database → $(REV)"
2166+
$(ALEMBIC) downgrade $(REV)
2167+
2168+
db-current:
2169+
$(ALEMBIC) current
2170+
2171+
db-history:
2172+
$(ALEMBIC) history --verbose
2173+
2174+
db-revision-id:
2175+
@$(ALEMBIC) current --verbose | awk '/Current revision/ {print $$3}'

alembic.ini

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts.
5+
# this is typically a path given in POSIX (e.g. forward slashes)
6+
# format, relative to the token %(here)s which refers to the location of this
7+
# ini file
8+
script_location = %(here)s/alembic
9+
10+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11+
# Uncomment the line below if you want the files to be prepended with date and time
12+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13+
# for all available tokens
14+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15+
16+
# sys.path path, will be prepended to sys.path if present.
17+
# defaults to the current working directory. for multiple paths, the path separator
18+
# is defined by "path_separator" below.
19+
prepend_sys_path = .
20+
21+
22+
# timezone to use when rendering the date within the migration file
23+
# as well as the filename.
24+
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
25+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
26+
# string value is passed to ZoneInfo()
27+
# leave blank for localtime
28+
# timezone =
29+
30+
# max length of characters to apply to the "slug" field
31+
# truncate_slug_length = 40
32+
33+
# set to 'true' to run the environment during
34+
# the 'revision' command, regardless of autogenerate
35+
# revision_environment = false
36+
37+
# set to 'true' to allow .pyc and .pyo files without
38+
# a source .py file to be detected as revisions in the
39+
# versions/ directory
40+
# sourceless = false
41+
42+
# version location specification; This defaults
43+
# to <script_location>/versions. When using multiple version
44+
# directories, initial revisions must be specified with --version-path.
45+
# The path separator used here should be the separator specified by "path_separator"
46+
# below.
47+
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
48+
49+
# path_separator; This indicates what character is used to split lists of file
50+
# paths, including version_locations and prepend_sys_path within configparser
51+
# files such as alembic.ini.
52+
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
53+
# to provide os-dependent path splitting.
54+
#
55+
# Note that in order to support legacy alembic.ini files, this default does NOT
56+
# take place if path_separator is not present in alembic.ini. If this
57+
# option is omitted entirely, fallback logic is as follows:
58+
#
59+
# 1. Parsing of the version_locations option falls back to using the legacy
60+
# "version_path_separator" key, which if absent then falls back to the legacy
61+
# behavior of splitting on spaces and/or commas.
62+
# 2. Parsing of the prepend_sys_path option falls back to the legacy
63+
# behavior of splitting on spaces, commas, or colons.
64+
#
65+
# Valid values for path_separator are:
66+
#
67+
# path_separator = :
68+
# path_separator = ;
69+
# path_separator = space
70+
# path_separator = newline
71+
#
72+
# Use os.pathsep. Default configuration used for new projects.
73+
path_separator = os
74+
75+
# set to 'true' to search source files recursively
76+
# in each "version_locations" directory
77+
# new in Alembic version 1.10
78+
# recursive_version_locations = false
79+
80+
# the output encoding used when revision files
81+
# are written from script.py.mako
82+
# output_encoding = utf-8
83+
84+
# database URL. This is consumed by the user-maintained env.py script only.
85+
# other means of configuring database URLs may be customized within the env.py
86+
# file.
87+
sqlalchemy.url = driver://user:pass@localhost/dbname
88+
89+
90+
[post_write_hooks]
91+
# post_write_hooks defines scripts or Python functions that are run
92+
# on newly generated revision scripts. See the documentation for further
93+
# detail and examples
94+
95+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
96+
# hooks = black
97+
# black.type = console_scripts
98+
# black.entrypoint = black
99+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
100+
101+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
102+
# hooks = ruff
103+
# ruff.type = exec
104+
# ruff.executable = %(here)s/.venv/bin/ruff
105+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
106+
107+
# Logging configuration. This is also consumed by the user-maintained
108+
# env.py script only.
109+
[loggers]
110+
keys = root,sqlalchemy,alembic
111+
112+
[handlers]
113+
keys = console
114+
115+
[formatters]
116+
keys = generic
117+
118+
[logger_root]
119+
level = WARNING
120+
handlers = console
121+
qualname =
122+
123+
[logger_sqlalchemy]
124+
level = WARNING
125+
handlers =
126+
qualname = sqlalchemy.engine
127+
128+
[logger_alembic]
129+
level = INFO
130+
handlers =
131+
qualname = alembic
132+
133+
[handler_console]
134+
class = StreamHandler
135+
args = (sys.stderr,)
136+
level = NOTSET
137+
formatter = generic
138+
139+
[formatter_generic]
140+
format = %(levelname)-5.5s [%(name)s] %(message)s
141+
datefmt = %H:%M:%S

alembic/README.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Alembic Migration Guide for `mcpgateway`
2+
3+
> Creating, applying, and managing schema migrations with Alembic.
4+
5+
---
6+
7+
## Table of Contents
8+
9+
1. [Why Alembic?](#why-alembic)
10+
2. [Prerequisites](#prerequisites)
11+
3. [Directory Layout](#directory-layout)
12+
4. [Everyday Workflow](#everyday-workflow)
13+
5. [Helpful Make Targets](#helpful-make-targets)
14+
6. [Troubleshooting](#troubleshooting)
15+
7. [Further Reading](#further-reading)
16+
17+
---
18+
19+
## Why Alembic?
20+
21+
- **Versioned DDL** — Revisions are timestamped, diff-able, and reversible.
22+
- **Autogeneration** — Detects model vs. DB drift and writes `op.create_table`, `op.add_column`, etc.
23+
- **Multi-DB Support** — Works with SQLite, PostgreSQL, MySQL—anything SQLAlchemy supports.
24+
- **Zero Runtime Cost** — Only runs when you call it (dev, CI, deploy).
25+
26+
---
27+
28+
## Prerequisites
29+
30+
```bash
31+
# Activate your virtual environment first
32+
pip install --upgrade alembic
33+
```
34+
35+
You do not need to set up `alembic.ini`, `env.py`, or metadata wiring - they're already configured.
36+
37+
---
38+
39+
## Directory Layout
40+
41+
```
42+
alembic.ini
43+
alembic/
44+
├── env.py
45+
├── script.py.mako
46+
└── versions/
47+
├── 20250626235501_initial_schema.py
48+
└── ...
49+
```
50+
51+
* `alembic.ini`: Configuration file
52+
* `env.py`: Connects Alembic to your models and DB settings
53+
* `script.py.mako`: Template for new revisions (keep this!)
54+
* `versions/`: Contains all migration scripts
55+
56+
---
57+
58+
## Everyday Workflow
59+
60+
> **1 Edit → 2 Revision → 3 Upgrade**
61+
62+
| Step | What you do |
63+
| ------------------------ | ----------------------------------------------------------------------------- |
64+
| **1. Change models** | Modify SQLAlchemy models in `mcpgateway.db` or its submodules. |
65+
| **2. Generate revision** | Run: `MSG="add users table"` then `alembic revision --autogenerate -m "$MSG"` |
66+
| **3. Review** | Open the new file in `alembic/versions/`. Verify the operations are correct. |
67+
| **4. Upgrade DB** | Run: `alembic upgrade head` |
68+
| **5. Commit** | Run: `git add alembic/versions/*.py` |
69+
70+
### Other Common Commands
71+
72+
```bash
73+
alembic current # Show current DB revision
74+
alembic history --verbose # Show all migrations and their order
75+
alembic downgrade -1 # Roll back one revision
76+
alembic downgrade <rev> # Roll back to a specific revision hash
77+
```
78+
79+
---
80+
81+
## ✅ Make Targets: Alembic Migration Commands
82+
83+
These targets help you manage database schema migrations using Alembic.
84+
85+
> You must have a valid `alembic/` setup and a working SQLAlchemy model base (`Base.metadata`).
86+
87+
---
88+
89+
### 💡 List all available targets (with help)
90+
91+
```bash
92+
make help
93+
```
94+
95+
This will include the Alembic section:
96+
97+
```
98+
# 🛢️ Alembic tasks
99+
db-new Autogenerate revision (MSG="title")
100+
db-up Upgrade DB to head
101+
db-down Downgrade one step (REV=-1 or hash)
102+
db-current Show current DB revision
103+
db-history List the migration graph
104+
```
105+
106+
---
107+
108+
### 🔨 Commands
109+
110+
| Command | Description |
111+
| -------------------------- | ------------------------------------------------------ |
112+
| `make db-new MSG="..."` | Generate a new migration based on model changes. |
113+
| `make db-up` | Apply all unapplied migrations. |
114+
| `make db-down` | Roll back the latest migration (`REV=-1` by default). |
115+
| `make db-down REV=abc1234` | Roll back to a specific revision by hash. |
116+
| `make db-current` | Print the current revision ID applied to the database. |
117+
| `make db-history` | Show the full migration history and graph. |
118+
119+
---
120+
121+
### 📌 Examples
122+
123+
```bash
124+
# Create a new migration with a custom message
125+
make db-new MSG="add users table"
126+
127+
# Apply it to the database
128+
make db-up
129+
130+
# Downgrade the last migration
131+
make db-down
132+
133+
# Downgrade to a specific revision
134+
make db-down REV=cf1283d7fa92
135+
136+
# Show the current applied revision
137+
make db-current
138+
139+
# Show all migration history
140+
make db-history
141+
```
142+
143+
---
144+
145+
### 🛑 Notes
146+
147+
* You must **edit models first** before `make db-new` generates anything useful.
148+
* Always **review generated migration files** before committing.
149+
* Don't forget to run `make db-up` on CI or deploy if using migrations to manage schema.
150+
151+
---
152+
153+
## Troubleshooting
154+
155+
| Symptom | Cause / Fix |
156+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
157+
| **Empty migration (`pass`)** | Alembic couldn't detect models. Make sure all model classes are imported before `Base.metadata` is used (already handled in your `env.py`). |
158+
| **`Can't locate revision ...`** | You deleted or renamed a revision file that the DB is pointing to. Either restore it or run `alembic stamp base` and recreate the revision. |
159+
| **`script.py.mako` missing** | This file is required. Run `alembic init alembic` in a temp folder and copy the missing template into your project. |
160+
| **SQLite foreign key limitations** | SQLite doesn't allow dropping constraints. Use `create table → copy → drop` flow manually, or plan around it. |
161+
| **DB not updating** | Did you forget to run `alembic upgrade head`? Check with `alembic current`. |
162+
| **Wrong DB URL or config errors** | Confirm `settings.database_url` is valid. Check `env.py` and your `.env`/config settings. Alembic ignores `alembic.ini` for URLs in your setup. |
163+
| **Model changes not detected** | Alembic only picks up declarative models in `Base.metadata`. Ensure all models are imported and not behind `if TYPE_CHECKING:` or other lazy imports. |
164+
165+
---
166+
167+
## Further Reading
168+
169+
* Official docs: [https://alembic.sqlalchemy.org](https://alembic.sqlalchemy.org)
170+
* Autogenerate docs: [https://alembic.sqlalchemy.org/en/latest/autogenerate.html](https://alembic.sqlalchemy.org/en/latest/autogenerate.html)
171+
172+
---

0 commit comments

Comments
 (0)