pgmini-migrate 0.1.0__tar.gz → 0.1.1__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.
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/PKG-INFO +1 -1
- pgmini_migrate-0.1.1/pgmini_migrate/__init__.py +0 -0
- pgmini_migrate-0.1.1/pgmini_migrate/cli.py +30 -0
- pgmini_migrate-0.1.1/pgmini_migrate/generator.py +37 -0
- pgmini_migrate-0.1.1/pgmini_migrate/runner.py +156 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pgmini_migrate.egg-info/PKG-INFO +1 -1
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pgmini_migrate.egg-info/SOURCES.txt +4 -0
- pgmini_migrate-0.1.1/pgmini_migrate.egg-info/top_level.txt +1 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pyproject.toml +1 -1
- pgmini_migrate-0.1.0/pgmini_migrate.egg-info/top_level.txt +0 -1
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/LICENSE +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/README.md +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pgmini_migrate.egg-info/dependency_links.txt +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pgmini_migrate.egg-info/entry_points.txt +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/pgmini_migrate.egg-info/requires.txt +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/setup.cfg +0 -0
- {pgmini_migrate-0.1.0 → pgmini_migrate-0.1.1}/setup.py +0 -0
File without changes
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import argparse
|
2
|
+
from pgmini_migrate.generator import create_migration
|
3
|
+
from pgmini_migrate.runner import upgrade, downgrade
|
4
|
+
|
5
|
+
def main():
|
6
|
+
parser = argparse.ArgumentParser(prog="pgmigrate", description="Mini migration tool for PostgreSQL")
|
7
|
+
sub = parser.add_subparsers(dest="command")
|
8
|
+
|
9
|
+
# create
|
10
|
+
create_cmd = sub.add_parser("create", help="Create new migration")
|
11
|
+
create_cmd.add_argument("description", nargs="+", help="Migration description")
|
12
|
+
|
13
|
+
# upgrade
|
14
|
+
upgrade_cmd = sub.add_parser("upgrade", help="Apply migrations")
|
15
|
+
upgrade_cmd.add_argument("target", nargs="?", help="Target version (default: latest)")
|
16
|
+
|
17
|
+
# downgrade
|
18
|
+
downgrade_cmd = sub.add_parser("downgrade", help="Rollback migrations")
|
19
|
+
downgrade_cmd.add_argument("target", nargs="?", help="Target version")
|
20
|
+
|
21
|
+
args = parser.parse_args()
|
22
|
+
|
23
|
+
if args.command == "create":
|
24
|
+
create_migration(" ".join(args.description))
|
25
|
+
elif args.command == "upgrade":
|
26
|
+
upgrade(args.target)
|
27
|
+
elif args.command == "downgrade":
|
28
|
+
downgrade(args.target)
|
29
|
+
else:
|
30
|
+
parser.print_help()
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
|
4
|
+
MIGRATIONS_DIR = os.getenv("MIGRATIONS_DIR", "migrations")
|
5
|
+
UPGRADE_DIR = os.path.join(MIGRATIONS_DIR, "upgrade")
|
6
|
+
DOWNGRADE_DIR = os.path.join(MIGRATIONS_DIR, "downgrade")
|
7
|
+
|
8
|
+
|
9
|
+
def ensure_dirs():
|
10
|
+
os.makedirs(UPGRADE_DIR, exist_ok=True)
|
11
|
+
os.makedirs(DOWNGRADE_DIR, exist_ok=True)
|
12
|
+
|
13
|
+
def get_next_version():
|
14
|
+
ensure_dirs()
|
15
|
+
files = os.listdir(UPGRADE_DIR)
|
16
|
+
versions = []
|
17
|
+
for f in files:
|
18
|
+
if f[0:3].isdigit():
|
19
|
+
versions.append(int(f[0:3]))
|
20
|
+
return max(versions) + 1 if versions else 1
|
21
|
+
|
22
|
+
|
23
|
+
def create_migration(description: str):
|
24
|
+
version = get_next_version()
|
25
|
+
version_str = f"{version:03d}"
|
26
|
+
# format description thành tên file đẹp
|
27
|
+
desc_clean = description.lower().replace(" ", "_")
|
28
|
+
|
29
|
+
upgrade_file = os.path.join(UPGRADE_DIR, f"{version_str}.{desc_clean}.upgrade.sql")
|
30
|
+
downgrade_file = os.path.join(DOWNGRADE_DIR, f"{version_str}.{desc_clean}.downgrade.sql")
|
31
|
+
|
32
|
+
with open(upgrade_file, "w") as f:
|
33
|
+
f.write(f"-- Migration {version_str}: {description}\n")
|
34
|
+
with open(downgrade_file, "w") as f:
|
35
|
+
f.write(f"-- Rollback {version_str}: {description}\n")
|
36
|
+
|
37
|
+
print(f"✅ Created migration files:\n {upgrade_file}\n {downgrade_file}")
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import os
|
2
|
+
from contextlib import contextmanager
|
3
|
+
|
4
|
+
import psycopg
|
5
|
+
from dotenv import load_dotenv
|
6
|
+
|
7
|
+
load_dotenv('.env')
|
8
|
+
|
9
|
+
DB_CONFIG = {
|
10
|
+
"dbname": os.getenv("POSTGRES_DB", "postgres"),
|
11
|
+
"user": os.getenv("POSTGRES_USER", "postgres"),
|
12
|
+
"password": os.getenv("POSTGRES_PASSWORD", "postgres"),
|
13
|
+
"host": os.getenv("POSTGRES_HOST", "localhost"),
|
14
|
+
"port": os.getenv("POSTGRES_PORT", "5432"),
|
15
|
+
}
|
16
|
+
|
17
|
+
MIGRATIONS_DIR = os.getenv("MIGRATIONS_DIR", "migrations")
|
18
|
+
UPGRADE_DIR = os.path.join(MIGRATIONS_DIR, "upgrade")
|
19
|
+
DOWNGRADE_DIR = os.path.join(MIGRATIONS_DIR, "downgrade")
|
20
|
+
|
21
|
+
|
22
|
+
@contextmanager
|
23
|
+
def get_connection():
|
24
|
+
conn = psycopg.connect(**DB_CONFIG)
|
25
|
+
try:
|
26
|
+
yield conn
|
27
|
+
finally:
|
28
|
+
conn.close()
|
29
|
+
|
30
|
+
|
31
|
+
def ensure_migration_table(conn):
|
32
|
+
with conn.cursor() as cur:
|
33
|
+
cur.execute("""
|
34
|
+
CREATE TABLE IF NOT EXISTS schema_migrations
|
35
|
+
(
|
36
|
+
version
|
37
|
+
VARCHAR
|
38
|
+
(
|
39
|
+
255
|
40
|
+
) PRIMARY KEY,
|
41
|
+
applied_at TIMESTAMP DEFAULT NOW
|
42
|
+
(
|
43
|
+
)
|
44
|
+
)
|
45
|
+
""")
|
46
|
+
conn.commit()
|
47
|
+
|
48
|
+
|
49
|
+
def get_applied_migrations(conn):
|
50
|
+
with conn.cursor() as cur:
|
51
|
+
cur.execute("SELECT version FROM schema_migrations ORDER BY version")
|
52
|
+
rows = cur.fetchall()
|
53
|
+
return [row[0] for row in rows]
|
54
|
+
|
55
|
+
|
56
|
+
def apply_migration(conn, version, sql_path):
|
57
|
+
with open(sql_path, "r") as f:
|
58
|
+
sql = f.read()
|
59
|
+
|
60
|
+
try:
|
61
|
+
with conn.cursor() as cur:
|
62
|
+
print(f"⬆️ Applying migration {version} ...")
|
63
|
+
cur.execute(sql)
|
64
|
+
cur.execute(
|
65
|
+
"INSERT INTO schema_migrations (version) VALUES (%s)", (version,)
|
66
|
+
)
|
67
|
+
conn.commit()
|
68
|
+
print(f"✅ Migration {version} applied.")
|
69
|
+
except Exception as e:
|
70
|
+
conn.rollback()
|
71
|
+
print(f"❌ Migration {version} failed: {e}")
|
72
|
+
raise
|
73
|
+
|
74
|
+
|
75
|
+
def rollback_migration(conn, version, sql_file):
|
76
|
+
down_file = f"{version}.{sql_file.split('.', 1)[1].replace('upgrade', 'downgrade')}"
|
77
|
+
sql_path = os.path.join(DOWNGRADE_DIR, down_file)
|
78
|
+
|
79
|
+
if not os.path.exists(sql_path):
|
80
|
+
print(f"⚠️ No rollback file for {version}")
|
81
|
+
return
|
82
|
+
|
83
|
+
with open(sql_path, "r") as f:
|
84
|
+
sql = f.read()
|
85
|
+
|
86
|
+
try:
|
87
|
+
with conn.cursor() as cur:
|
88
|
+
print(f"⬇️ Rolling back migration {version} ...")
|
89
|
+
cur.execute(sql)
|
90
|
+
cur.execute("DELETE FROM schema_migrations WHERE version = %s", (version,))
|
91
|
+
conn.commit()
|
92
|
+
print(f"✅ Migration {version} rolled back.")
|
93
|
+
except Exception as e:
|
94
|
+
conn.rollback()
|
95
|
+
print(f"❌ Rollback {version} failed: {e}")
|
96
|
+
raise
|
97
|
+
|
98
|
+
|
99
|
+
def upgrade():
|
100
|
+
with get_connection() as conn:
|
101
|
+
ensure_migration_table(conn)
|
102
|
+
applied = set(get_applied_migrations(conn))
|
103
|
+
|
104
|
+
migration_files = sorted(os.listdir(UPGRADE_DIR))
|
105
|
+
for filename in migration_files:
|
106
|
+
version = filename.split(".")[0] # 001_init.sql -> 001
|
107
|
+
if version in applied or not filename.endswith(".sql"):
|
108
|
+
continue
|
109
|
+
sql_path = os.path.join(UPGRADE_DIR, filename)
|
110
|
+
apply_migration(conn, version, sql_path)
|
111
|
+
|
112
|
+
|
113
|
+
def downgrade(target_version=None):
|
114
|
+
with get_connection() as conn:
|
115
|
+
ensure_migration_table(conn)
|
116
|
+
applied = get_applied_migrations(conn)
|
117
|
+
|
118
|
+
if not applied:
|
119
|
+
print("⚠️ No migrations to rollback.")
|
120
|
+
return
|
121
|
+
|
122
|
+
if target_version == "base":
|
123
|
+
target_index = -1
|
124
|
+
elif target_version:
|
125
|
+
if target_version not in applied:
|
126
|
+
print(f"⚠️ Target version {target_version} not found in applied migrations.")
|
127
|
+
return
|
128
|
+
target_index = applied.index(target_version)
|
129
|
+
else:
|
130
|
+
# Nếu không truyền version → rollback 1 bước
|
131
|
+
target_index = len(applied) - 2
|
132
|
+
|
133
|
+
to_rollback = applied[target_index + 1:] # những migration mới hơn target
|
134
|
+
if not to_rollback:
|
135
|
+
print("⚠️ Nothing to rollback.")
|
136
|
+
return
|
137
|
+
|
138
|
+
# rollback ngược thứ tự
|
139
|
+
for version in reversed(to_rollback):
|
140
|
+
sql_file = next(
|
141
|
+
(f for f in os.listdir(UPGRADE_DIR) if f.startswith(version)), None
|
142
|
+
)
|
143
|
+
if not sql_file:
|
144
|
+
print(f"⚠️ No migration file found for {version}")
|
145
|
+
continue
|
146
|
+
sql_path = os.path.join(UPGRADE_DIR, sql_file)
|
147
|
+
rollback_migration(conn, version, sql_path)
|
148
|
+
|
149
|
+
|
150
|
+
def history():
|
151
|
+
with get_connection() as conn:
|
152
|
+
ensure_migration_table(conn)
|
153
|
+
applied = get_applied_migrations(conn)
|
154
|
+
print("📜 Applied migrations:")
|
155
|
+
for v in applied:
|
156
|
+
print(f" - {v}")
|
@@ -2,6 +2,10 @@ LICENSE
|
|
2
2
|
README.md
|
3
3
|
pyproject.toml
|
4
4
|
setup.py
|
5
|
+
pgmini_migrate/__init__.py
|
6
|
+
pgmini_migrate/cli.py
|
7
|
+
pgmini_migrate/generator.py
|
8
|
+
pgmini_migrate/runner.py
|
5
9
|
pgmini_migrate.egg-info/PKG-INFO
|
6
10
|
pgmini_migrate.egg-info/SOURCES.txt
|
7
11
|
pgmini_migrate.egg-info/dependency_links.txt
|
@@ -0,0 +1 @@
|
|
1
|
+
pgmini_migrate
|
@@ -1 +0,0 @@
|
|
1
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|