pgmini-migrate 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
File without changes
pgmini_migrate/cli.py ADDED
@@ -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}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgmini-migrate
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: A minimal PostgreSQL migration tool for Python
5
5
  Author-email: Nguyen Vu Duy Luan <nvdluan@gmail.com>
6
6
  License: Copyright (c) 2025 Nguyen Vu Duy Luan
@@ -0,0 +1,10 @@
1
+ pgmini_migrate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pgmini_migrate/cli.py,sha256=8bfm5BDWiTgiIk-RCySKHhtLoLRd7b12W40MMRbsPH0,1092
3
+ pgmini_migrate/generator.py,sha256=-AO2AQ5rqBz6n1ydutJG4oYNJZGBRuBuklKI4u4QhR4,1215
4
+ pgmini_migrate/runner.py,sha256=4LvJqwrT4p_i_z5LxPmFh6-OJw9xOE2gxGXcLnSSSbI,4859
5
+ pgmini_migrate-0.1.1.dist-info/licenses/LICENSE,sha256=n8ph1nmbOC4LqVLKRE-5SrUxzlv71A4saf-2_iNWR2Y,37
6
+ pgmini_migrate-0.1.1.dist-info/METADATA,sha256=dhj1ESyg7CxugVhMNQuuo-U-J1-QYX_t-clndcAbdWg,649
7
+ pgmini_migrate-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ pgmini_migrate-0.1.1.dist-info/entry_points.txt,sha256=Zy6EN7sJuzTPbMhyoFjri1OdQ2iX1HuPw4UcSi4G6OY,54
9
+ pgmini_migrate-0.1.1.dist-info/top_level.txt,sha256=ok556JVjy6jjrWTHi9d_lE4TteBXjQIoLOoJFD7_yhg,15
10
+ pgmini_migrate-0.1.1.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ pgmini_migrate
@@ -1,6 +0,0 @@
1
- pgmini_migrate-0.1.0.dist-info/licenses/LICENSE,sha256=n8ph1nmbOC4LqVLKRE-5SrUxzlv71A4saf-2_iNWR2Y,37
2
- pgmini_migrate-0.1.0.dist-info/METADATA,sha256=WSE1F2hQk8NAT-l4K365L-r6tpCD4o1J3uG1-D9SGQ0,649
3
- pgmini_migrate-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
- pgmini_migrate-0.1.0.dist-info/entry_points.txt,sha256=Zy6EN7sJuzTPbMhyoFjri1OdQ2iX1HuPw4UcSi4G6OY,54
5
- pgmini_migrate-0.1.0.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
- pgmini_migrate-0.1.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
-