delembic 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- delembic-0.2.0/.bumpversion.cfg +13 -0
- delembic-0.2.0/.github/workflows/release.yml +88 -0
- delembic-0.2.0/.gitignore +32 -0
- delembic-0.2.0/.readthedocs.yaml +17 -0
- delembic-0.2.0/PKG-INFO +155 -0
- delembic-0.2.0/README.md +137 -0
- delembic-0.2.0/docs/_build/html/.buildinfo +4 -0
- delembic-0.2.0/docs/_build/html/.doctrees/alembic-integration.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/changelog.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/cli-reference.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/configuration.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/dependency-tracking.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/environment.pickle +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/getting-started.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/index.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/internals.doctree +0 -0
- delembic-0.2.0/docs/_build/html/.doctrees/writing-migrations.doctree +0 -0
- delembic-0.2.0/docs/_build/html/_sources/alembic-integration.md.txt +112 -0
- delembic-0.2.0/docs/_build/html/_sources/changelog.md.txt +11 -0
- delembic-0.2.0/docs/_build/html/_sources/cli-reference.md.txt +169 -0
- delembic-0.2.0/docs/_build/html/_sources/configuration.md.txt +98 -0
- delembic-0.2.0/docs/_build/html/_sources/dependency-tracking.md.txt +125 -0
- delembic-0.2.0/docs/_build/html/_sources/getting-started.md.txt +133 -0
- delembic-0.2.0/docs/_build/html/_sources/index.md.txt +45 -0
- delembic-0.2.0/docs/_build/html/_sources/internals.md.txt +130 -0
- delembic-0.2.0/docs/_build/html/_sources/writing-migrations.md.txt +137 -0
- delembic-0.2.0/docs/_build/html/_static/base-stemmer.js +476 -0
- delembic-0.2.0/docs/_build/html/_static/basic.css +906 -0
- delembic-0.2.0/docs/_build/html/_static/check-solid.svg +4 -0
- delembic-0.2.0/docs/_build/html/_static/clipboard.min.js +7 -0
- delembic-0.2.0/docs/_build/html/_static/copy-button.svg +5 -0
- delembic-0.2.0/docs/_build/html/_static/copybutton.css +94 -0
- delembic-0.2.0/docs/_build/html/_static/copybutton.js +248 -0
- delembic-0.2.0/docs/_build/html/_static/copybutton_funcs.js +73 -0
- delembic-0.2.0/docs/_build/html/_static/debug.css +69 -0
- delembic-0.2.0/docs/_build/html/_static/doctools.js +150 -0
- delembic-0.2.0/docs/_build/html/_static/documentation_options.js +13 -0
- delembic-0.2.0/docs/_build/html/_static/english-stemmer.js +1066 -0
- delembic-0.2.0/docs/_build/html/_static/file.png +0 -0
- delembic-0.2.0/docs/_build/html/_static/language_data.js +13 -0
- delembic-0.2.0/docs/_build/html/_static/minus.png +0 -0
- delembic-0.2.0/docs/_build/html/_static/plus.png +0 -0
- delembic-0.2.0/docs/_build/html/_static/pygments.css +250 -0
- delembic-0.2.0/docs/_build/html/_static/scripts/furo-extensions.js +0 -0
- delembic-0.2.0/docs/_build/html/_static/scripts/furo.js +3 -0
- delembic-0.2.0/docs/_build/html/_static/scripts/furo.js.LICENSE.txt +7 -0
- delembic-0.2.0/docs/_build/html/_static/scripts/furo.js.map +1 -0
- delembic-0.2.0/docs/_build/html/_static/searchtools.js +693 -0
- delembic-0.2.0/docs/_build/html/_static/skeleton.css +296 -0
- delembic-0.2.0/docs/_build/html/_static/sphinx_highlight.js +159 -0
- delembic-0.2.0/docs/_build/html/_static/styles/furo-extensions.css +2 -0
- delembic-0.2.0/docs/_build/html/_static/styles/furo-extensions.css.map +1 -0
- delembic-0.2.0/docs/_build/html/_static/styles/furo.css +2 -0
- delembic-0.2.0/docs/_build/html/_static/styles/furo.css.map +1 -0
- delembic-0.2.0/docs/_build/html/alembic-integration.html +423 -0
- delembic-0.2.0/docs/_build/html/changelog.html +328 -0
- delembic-0.2.0/docs/_build/html/cli-reference.html +499 -0
- delembic-0.2.0/docs/_build/html/configuration.html +491 -0
- delembic-0.2.0/docs/_build/html/dependency-tracking.html +446 -0
- delembic-0.2.0/docs/_build/html/genindex.html +281 -0
- delembic-0.2.0/docs/_build/html/getting-started.html +462 -0
- delembic-0.2.0/docs/_build/html/index.html +408 -0
- delembic-0.2.0/docs/_build/html/internals.html +458 -0
- delembic-0.2.0/docs/_build/html/objects.inv +0 -0
- delembic-0.2.0/docs/_build/html/search.html +292 -0
- delembic-0.2.0/docs/_build/html/searchindex.js +1 -0
- delembic-0.2.0/docs/_build/html/writing-migrations.html +466 -0
- delembic-0.2.0/docs/_static/.gitkeep +0 -0
- delembic-0.2.0/docs/alembic-integration.md +112 -0
- delembic-0.2.0/docs/changelog.md +11 -0
- delembic-0.2.0/docs/cli-reference.md +169 -0
- delembic-0.2.0/docs/conf.py +40 -0
- delembic-0.2.0/docs/configuration.md +98 -0
- delembic-0.2.0/docs/dependency-tracking.md +125 -0
- delembic-0.2.0/docs/getting-started.md +133 -0
- delembic-0.2.0/docs/index.md +45 -0
- delembic-0.2.0/docs/internals.md +130 -0
- delembic-0.2.0/docs/requirements.txt +4 -0
- delembic-0.2.0/docs/writing-migrations.md +137 -0
- delembic-0.2.0/pyproject.toml +33 -0
- delembic-0.2.0/src/delembic/__init__.py +4 -0
- delembic-0.2.0/src/delembic/alembic_compat.py +65 -0
- delembic-0.2.0/src/delembic/cli.py +199 -0
- delembic-0.2.0/src/delembic/config.py +38 -0
- delembic-0.2.0/src/delembic/dag.py +37 -0
- delembic-0.2.0/src/delembic/db.py +75 -0
- delembic-0.2.0/src/delembic/executor.py +115 -0
- delembic-0.2.0/src/delembic/migration.py +14 -0
- delembic-0.2.0/src/delembic/registry.py +34 -0
- delembic-0.2.0/tests/__init__.py +0 -0
- delembic-0.2.0/tests/test_alembic_compat.py +97 -0
- delembic-0.2.0/tests/test_cli.py +176 -0
- delembic-0.2.0/tests/test_dag.py +74 -0
- delembic-0.2.0/tests/test_db.py +83 -0
- delembic-0.2.0/tests/test_executor.py +165 -0
- delembic-0.2.0/tests/test_registry.py +63 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[bumpversion]
|
|
2
|
+
current_version = 0.2.0
|
|
3
|
+
commit = True
|
|
4
|
+
tag = True
|
|
5
|
+
tag_name = v{new_version}
|
|
6
|
+
|
|
7
|
+
[bumpversion:file:pyproject.toml]
|
|
8
|
+
search = version = "{current_version}"
|
|
9
|
+
replace = version = "{new_version}"
|
|
10
|
+
|
|
11
|
+
[bumpversion:file:src/delembic/__init__.py]
|
|
12
|
+
search = __version__ = "{current_version}"
|
|
13
|
+
replace = __version__ = "{new_version}"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Build and Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main # only trigger on main branch
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # required to fetch tags
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.10"
|
|
25
|
+
|
|
26
|
+
- name: Install bump2version & build tools
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install bump2version build wheel
|
|
30
|
+
|
|
31
|
+
- name: Configure git user
|
|
32
|
+
run: |
|
|
33
|
+
git config --global user.name "github-actions[bot]"
|
|
34
|
+
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
35
|
+
|
|
36
|
+
- name: Fetch tags
|
|
37
|
+
run: git fetch --tags
|
|
38
|
+
|
|
39
|
+
- name: Determine bump type
|
|
40
|
+
id: bump_type
|
|
41
|
+
run: |
|
|
42
|
+
msg="${{ github.event.head_commit.message }}"
|
|
43
|
+
if [[ "$msg" == *"bump: major"* ]]; then
|
|
44
|
+
echo "value=major" >> $GITHUB_OUTPUT
|
|
45
|
+
elif [[ "$msg" == *"bump: minor"* ]]; then
|
|
46
|
+
echo "value=minor" >> $GITHUB_OUTPUT
|
|
47
|
+
elif [[ "$msg" == *"bump: patch"* ]]; then
|
|
48
|
+
echo "value=patch" >> $GITHUB_OUTPUT
|
|
49
|
+
else
|
|
50
|
+
echo "ERROR: Commit must include 'bump: patch|minor|major'"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Bump version
|
|
55
|
+
run: bump2version ${{ steps.bump_type.outputs.value }}
|
|
56
|
+
|
|
57
|
+
- name: Push new version and tag
|
|
58
|
+
run: |
|
|
59
|
+
git push origin HEAD --tags
|
|
60
|
+
|
|
61
|
+
- name: Get new version tag
|
|
62
|
+
id: new_tag
|
|
63
|
+
run: |
|
|
64
|
+
new_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
|
65
|
+
echo "new_tag=$new_tag" >> $GITHUB_ENV
|
|
66
|
+
|
|
67
|
+
- name: Build package
|
|
68
|
+
run: python -m build
|
|
69
|
+
|
|
70
|
+
- name: Publish to PyPI
|
|
71
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
72
|
+
|
|
73
|
+
- name: Upload release assets
|
|
74
|
+
uses: softprops/action-gh-release@v1
|
|
75
|
+
with:
|
|
76
|
+
tag_name: ${{ env.new_tag }}
|
|
77
|
+
name: "Delembic ${{ env.new_tag }}"
|
|
78
|
+
body: |
|
|
79
|
+
Release ${{ env.new_tag }}
|
|
80
|
+
|
|
81
|
+
Changes in this release:
|
|
82
|
+
- Auto-bumped version via GitHub Actions
|
|
83
|
+
- See commit history for details
|
|
84
|
+
files: |
|
|
85
|
+
dist/*.whl
|
|
86
|
+
dist/*.tar.gz
|
|
87
|
+
env:
|
|
88
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
*.pyo
|
|
6
|
+
*.pyd
|
|
7
|
+
*.egg-info/
|
|
8
|
+
*.egg
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Build
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
|
|
15
|
+
# Test / coverage
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
*.db
|
|
20
|
+
|
|
21
|
+
# Type checkers / linters
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
|
|
25
|
+
# Env
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
|
|
29
|
+
# IDE
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
*.swp
|
delembic-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: delembic
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Data migration framework for versioning ETL and data operations alongside Alembic
|
|
5
|
+
Author-email: htshpradhan5@gmail.com
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: click>=8.0
|
|
9
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
12
|
+
Provides-Extra: docs
|
|
13
|
+
Requires-Dist: furo>=2024.0; extra == 'docs'
|
|
14
|
+
Requires-Dist: myst-parser>=3.0; extra == 'docs'
|
|
15
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
|
|
16
|
+
Requires-Dist: sphinx>=7.0; extra == 'docs'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Delembic
|
|
20
|
+
|
|
21
|
+
Data migration framework for Python — Alembic for ETL and data operations.
|
|
22
|
+
|
|
23
|
+
Alembic versions schema changes. Delembic versions **data** changes: vocabulary loads, ETL runs, reference data inserts, corrections. Together they describe the complete database state.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install delembic
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 1. Initialize in your project
|
|
35
|
+
delembic init
|
|
36
|
+
|
|
37
|
+
# 2. Edit delembic.ini — set your database URL
|
|
38
|
+
# sqlalchemy.url = postgresql+psycopg://user:pass@host/dbname
|
|
39
|
+
|
|
40
|
+
# 3. Create a migration
|
|
41
|
+
delembic revision -m "load vocabulary"
|
|
42
|
+
|
|
43
|
+
# 4. Fill in the generated file, then run
|
|
44
|
+
delembic upgrade head
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
`delembic init` creates:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
your-project/
|
|
53
|
+
├── delembic.ini # config — commit this
|
|
54
|
+
└── delembic/
|
|
55
|
+
├── env.py # optional connection helpers
|
|
56
|
+
└── versions/ # migration files live here
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`delembic.ini`:**
|
|
60
|
+
|
|
61
|
+
```ini
|
|
62
|
+
[delembic]
|
|
63
|
+
script_location = delembic
|
|
64
|
+
sqlalchemy.url = postgresql+psycopg://user:pass@localhost/mydb
|
|
65
|
+
alembic_config = alembic.ini
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can name the script folder anything:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
delembic init data-migrations
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Writing Migrations
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from delembic import DataMigration
|
|
78
|
+
|
|
79
|
+
class LoadVocabulary(DataMigration):
|
|
80
|
+
|
|
81
|
+
revision = "D001"
|
|
82
|
+
depends_on = []
|
|
83
|
+
description = "Load OMOP vocabulary tables"
|
|
84
|
+
|
|
85
|
+
def upgrade(self, conn):
|
|
86
|
+
conn.execute(...)
|
|
87
|
+
|
|
88
|
+
def validate(self, conn):
|
|
89
|
+
count = conn.execute("SELECT COUNT(*) FROM concept").scalar()
|
|
90
|
+
assert count > 0, "vocabulary load produced no rows"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`conn` is a SQLAlchemy `Connection`. `validate` is optional — migration is marked failed if it raises.
|
|
94
|
+
|
|
95
|
+
## Dependency Tracking
|
|
96
|
+
|
|
97
|
+
Migrations declare explicit dependencies. Delembic builds a DAG and runs them in topological order.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
class LoadPerson(DataMigration):
|
|
101
|
+
revision = "D002"
|
|
102
|
+
depends_on = ["D001"] # waits for D001
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Alembic Integration
|
|
106
|
+
|
|
107
|
+
Point `alembic_config` at your `alembic.ini`:
|
|
108
|
+
|
|
109
|
+
```ini
|
|
110
|
+
[delembic]
|
|
111
|
+
alembic_config = alembic.ini
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
When you create a migration, Delembic automatically captures the current Alembic head:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
delembic revision -m "load person"
|
|
118
|
+
# → depends_on = ['3d1e3e6abc12'] (current alembic head)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
At upgrade time, Delembic verifies the required Alembic revision has been applied before running:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
BLOCKED: Migration D002 requires Alembic revision(s) 3d1e3e6abc12 to be applied first.
|
|
125
|
+
Run 'alembic upgrade head' before retrying.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## CLI Reference
|
|
129
|
+
|
|
130
|
+
| Command | Description |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `delembic init [DIR]` | Initialize project. DIR defaults to `delembic` |
|
|
133
|
+
| `delembic revision -m "msg"` | Generate new migration file |
|
|
134
|
+
| `delembic upgrade head` | Run all pending migrations |
|
|
135
|
+
| `delembic upgrade D003` | Run migrations up to D003 |
|
|
136
|
+
| `delembic current` | Show last applied revision |
|
|
137
|
+
| `delembic history` | List all migrations with status |
|
|
138
|
+
|
|
139
|
+
## Metadata Tables
|
|
140
|
+
|
|
141
|
+
Delembic creates two tables:
|
|
142
|
+
|
|
143
|
+
```sql
|
|
144
|
+
delembic_version -- current status per revision
|
|
145
|
+
delembic_run_history -- full audit log (start/end time, duration, exception, user, host)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Failed migration work is rolled back. The failure record is always committed — audit trail survives transaction failures.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[dev]"
|
|
154
|
+
pytest
|
|
155
|
+
```
|
delembic-0.2.0/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Delembic
|
|
2
|
+
|
|
3
|
+
Data migration framework for Python — Alembic for ETL and data operations.
|
|
4
|
+
|
|
5
|
+
Alembic versions schema changes. Delembic versions **data** changes: vocabulary loads, ETL runs, reference data inserts, corrections. Together they describe the complete database state.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install delembic
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Initialize in your project
|
|
17
|
+
delembic init
|
|
18
|
+
|
|
19
|
+
# 2. Edit delembic.ini — set your database URL
|
|
20
|
+
# sqlalchemy.url = postgresql+psycopg://user:pass@host/dbname
|
|
21
|
+
|
|
22
|
+
# 3. Create a migration
|
|
23
|
+
delembic revision -m "load vocabulary"
|
|
24
|
+
|
|
25
|
+
# 4. Fill in the generated file, then run
|
|
26
|
+
delembic upgrade head
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
`delembic init` creates:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
your-project/
|
|
35
|
+
├── delembic.ini # config — commit this
|
|
36
|
+
└── delembic/
|
|
37
|
+
├── env.py # optional connection helpers
|
|
38
|
+
└── versions/ # migration files live here
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`delembic.ini`:**
|
|
42
|
+
|
|
43
|
+
```ini
|
|
44
|
+
[delembic]
|
|
45
|
+
script_location = delembic
|
|
46
|
+
sqlalchemy.url = postgresql+psycopg://user:pass@localhost/mydb
|
|
47
|
+
alembic_config = alembic.ini
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You can name the script folder anything:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
delembic init data-migrations
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Writing Migrations
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from delembic import DataMigration
|
|
60
|
+
|
|
61
|
+
class LoadVocabulary(DataMigration):
|
|
62
|
+
|
|
63
|
+
revision = "D001"
|
|
64
|
+
depends_on = []
|
|
65
|
+
description = "Load OMOP vocabulary tables"
|
|
66
|
+
|
|
67
|
+
def upgrade(self, conn):
|
|
68
|
+
conn.execute(...)
|
|
69
|
+
|
|
70
|
+
def validate(self, conn):
|
|
71
|
+
count = conn.execute("SELECT COUNT(*) FROM concept").scalar()
|
|
72
|
+
assert count > 0, "vocabulary load produced no rows"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`conn` is a SQLAlchemy `Connection`. `validate` is optional — migration is marked failed if it raises.
|
|
76
|
+
|
|
77
|
+
## Dependency Tracking
|
|
78
|
+
|
|
79
|
+
Migrations declare explicit dependencies. Delembic builds a DAG and runs them in topological order.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
class LoadPerson(DataMigration):
|
|
83
|
+
revision = "D002"
|
|
84
|
+
depends_on = ["D001"] # waits for D001
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Alembic Integration
|
|
88
|
+
|
|
89
|
+
Point `alembic_config` at your `alembic.ini`:
|
|
90
|
+
|
|
91
|
+
```ini
|
|
92
|
+
[delembic]
|
|
93
|
+
alembic_config = alembic.ini
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When you create a migration, Delembic automatically captures the current Alembic head:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
delembic revision -m "load person"
|
|
100
|
+
# → depends_on = ['3d1e3e6abc12'] (current alembic head)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
At upgrade time, Delembic verifies the required Alembic revision has been applied before running:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
BLOCKED: Migration D002 requires Alembic revision(s) 3d1e3e6abc12 to be applied first.
|
|
107
|
+
Run 'alembic upgrade head' before retrying.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## CLI Reference
|
|
111
|
+
|
|
112
|
+
| Command | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `delembic init [DIR]` | Initialize project. DIR defaults to `delembic` |
|
|
115
|
+
| `delembic revision -m "msg"` | Generate new migration file |
|
|
116
|
+
| `delembic upgrade head` | Run all pending migrations |
|
|
117
|
+
| `delembic upgrade D003` | Run migrations up to D003 |
|
|
118
|
+
| `delembic current` | Show last applied revision |
|
|
119
|
+
| `delembic history` | List all migrations with status |
|
|
120
|
+
|
|
121
|
+
## Metadata Tables
|
|
122
|
+
|
|
123
|
+
Delembic creates two tables:
|
|
124
|
+
|
|
125
|
+
```sql
|
|
126
|
+
delembic_version -- current status per revision
|
|
127
|
+
delembic_run_history -- full audit log (start/end time, duration, exception, user, host)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Failed migration work is rolled back. The failure record is always committed — audit trail survives transaction failures.
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install -e ".[dev]"
|
|
136
|
+
pytest
|
|
137
|
+
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Alembic Integration
|
|
2
|
+
|
|
3
|
+
Delembic integrates with Alembic to enforce that schema migrations are applied before data migrations that depend on them.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Point `alembic_config` at your `alembic.ini`:
|
|
8
|
+
|
|
9
|
+
```ini
|
|
10
|
+
[delembic]
|
|
11
|
+
script_location = delembic
|
|
12
|
+
sqlalchemy.url = postgresql+psycopg://user:pass@localhost/mydb
|
|
13
|
+
alembic_config = alembic.ini
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`alembic_config` is resolved relative to `delembic.ini`.
|
|
17
|
+
|
|
18
|
+
Install alembic if not already present:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install alembic
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Auto-Capture on Revision Generation
|
|
25
|
+
|
|
26
|
+
When `alembic_config` is set, `delembic revision` automatically captures the current Alembic head revision(s) into `depends_on`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Alembic is at head: 3d1e3e6abc12
|
|
30
|
+
delembic revision -m "load person"
|
|
31
|
+
# Alembic heads captured: ['3d1e3e6abc12']
|
|
32
|
+
# Created delembic/versions/D001_load_person.py
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Generated file:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
class LoadPerson(DataMigration):
|
|
39
|
+
revision = "D001"
|
|
40
|
+
depends_on = ['3d1e3e6abc12'] # ← captured automatically
|
|
41
|
+
description = "load person"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If the database is unreachable at revision-generation time, Delembic prints a warning and generates the file with `depends_on = []` — you can fill it in manually.
|
|
45
|
+
|
|
46
|
+
## Runtime Verification
|
|
47
|
+
|
|
48
|
+
At `delembic upgrade head`, before running any migration, Delembic checks that all Alembic revisions in `depends_on` are applied:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Running D001: load person
|
|
52
|
+
BLOCKED: Migration D001 requires Alembic revision(s) ['3d1e3e6abc12'] to be applied first.
|
|
53
|
+
Run 'alembic upgrade head' before retrying.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The blocked migration is not attempted. Delembic exits with code 1.
|
|
57
|
+
|
|
58
|
+
## Typical Workflow
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 1. Run schema migrations first
|
|
62
|
+
alembic upgrade head
|
|
63
|
+
|
|
64
|
+
# 2. Run data migrations
|
|
65
|
+
delembic upgrade head
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
In CI/CD pipelines:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- run: alembic upgrade head
|
|
72
|
+
- run: delembic upgrade head
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Multiple Alembic Heads
|
|
76
|
+
|
|
77
|
+
If your Alembic project uses multiple heads (branches), Delembic captures all current heads:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Alembic has two active branches
|
|
81
|
+
delembic revision -m "post-merge data load"
|
|
82
|
+
# Alembic heads captured: ['3d1e3e6abc12', 'a1b2c3d4e5f6']
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
depends_on = ['3d1e3e6abc12', 'a1b2c3d4e5f6']
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Both must be applied before the migration runs.
|
|
90
|
+
|
|
91
|
+
## Without Alembic
|
|
92
|
+
|
|
93
|
+
Alembic integration is entirely optional. If `alembic_config` is not set in `delembic.ini`:
|
|
94
|
+
- `delembic revision` generates files with `depends_on = []`
|
|
95
|
+
- `delembic upgrade` skips Alembic checks
|
|
96
|
+
- No alembic package needed
|
|
97
|
+
|
|
98
|
+
## How It Works Internally
|
|
99
|
+
|
|
100
|
+
Delembic uses two Alembic APIs:
|
|
101
|
+
|
|
102
|
+
**`get_current_heads(conn)`** — reads `alembic_version` table via `MigrationContext`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from alembic.runtime.migration import MigrationContext
|
|
106
|
+
ctx = MigrationContext.configure(conn)
|
|
107
|
+
heads = list(ctx.get_current_heads())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**`get_alembic_applied_revisions(conn, alembic_ini)`** — walks script history from current heads back to base via `ScriptDirectory.iterate_revisions()`. Returns the set of all applied revision IDs (not just heads — ancestors included).
|
|
111
|
+
|
|
112
|
+
This means a migration that depends on `3d1e3e6abc12` will pass verification even if Alembic has since advanced to `a1b2c3d4e5f6`, as long as `3d1e3e6abc12` is an ancestor of the current head.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — Initial Release
|
|
4
|
+
|
|
5
|
+
- `DataMigration` ABC with `upgrade()` and `validate()` hooks
|
|
6
|
+
- DAG-based execution with topological sort (Kahn's algorithm)
|
|
7
|
+
- `delembic init`, `revision`, `upgrade`, `current`, `history` CLI commands
|
|
8
|
+
- `delembic_version` and `delembic_run_history` metadata tables
|
|
9
|
+
- Dual-connection executor: audit records survive transaction rollbacks
|
|
10
|
+
- Alembic integration: auto-capture heads on `revision`, verify deps on `upgrade`
|
|
11
|
+
- `bump2version` + GitHub Actions release workflow
|