csrd-migration 0.3.89__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.
@@ -0,0 +1,11 @@
1
+ """Versioned schema migrations for csrd database adapters.
2
+
3
+ Public API::
4
+
5
+ from csrd.migration import Migration, MigrationRunner
6
+ """
7
+
8
+ from ._migration import Migration
9
+ from ._runner import MigrationRunner
10
+
11
+ __all__ = ("Migration", "MigrationRunner")
@@ -0,0 +1,29 @@
1
+ """Frozen dataclass representing a single versioned schema change."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Migration:
8
+ """A single versioned database schema change.
9
+
10
+ Migrations are applied in list order by :class:`MigrationRunner`.
11
+ Each migration must have a unique ``version`` string.
12
+
13
+ Parameters
14
+ ----------
15
+ version
16
+ Unique version identifier (e.g. ``"001"``, ``"002"``).
17
+ Compared lexicographically — use zero-padded numbers.
18
+ description
19
+ Human-readable description of the change.
20
+ up
21
+ SQL statement(s) to apply the migration.
22
+ down
23
+ SQL statement(s) to revert the migration.
24
+ """
25
+
26
+ version: str
27
+ description: str
28
+ up: str
29
+ down: str
@@ -0,0 +1,98 @@
1
+ """Migration runner — applies and tracks schema migrations."""
2
+
3
+ from csrd.repository import ABCDatabaseAdapter
4
+
5
+ from ._migration import Migration
6
+
7
+ _MIGRATIONS_TABLE = "_csrd_migrations"
8
+
9
+ _CREATE_TABLE_SQL = f"""
10
+ CREATE TABLE IF NOT EXISTS {_MIGRATIONS_TABLE} (
11
+ version TEXT PRIMARY KEY,
12
+ description TEXT NOT NULL,
13
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
14
+ )
15
+ """
16
+
17
+ # Postgres/MariaDB variant — uses CURRENT_TIMESTAMP instead of datetime('now')
18
+ _CREATE_TABLE_SQL_STANDARD = f"""
19
+ CREATE TABLE IF NOT EXISTS {_MIGRATIONS_TABLE} (
20
+ version VARCHAR(255) PRIMARY KEY,
21
+ description VARCHAR(1024) NOT NULL,
22
+ applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
23
+ )
24
+ """
25
+
26
+
27
+ class MigrationRunner:
28
+ """Apply and track versioned schema migrations.
29
+
30
+ The runner creates a ``_csrd_migrations`` tracking table automatically.
31
+ Already-applied migrations are skipped (idempotent).
32
+
33
+ Usage::
34
+
35
+ runner = MigrationRunner()
36
+ await runner.apply_all(adapter, migrations)
37
+ """
38
+
39
+ async def _ensure_table(self, adapter: ABCDatabaseAdapter) -> None:
40
+ """Create the migrations tracking table if it doesn't exist."""
41
+ # Try SQLite-style first; fall back to standard SQL on failure
42
+ try:
43
+ await adapter.execute(_CREATE_TABLE_SQL)
44
+ except Exception:
45
+ await adapter.execute(_CREATE_TABLE_SQL_STANDARD)
46
+
47
+ async def _applied_versions(self, adapter: ABCDatabaseAdapter) -> set[str]:
48
+ """Return the set of already-applied migration versions."""
49
+ rows = await adapter.fetch_all(f"SELECT version FROM {_MIGRATIONS_TABLE}")
50
+ return {row["version"] for row in rows}
51
+
52
+ async def apply_all(
53
+ self,
54
+ adapter: ABCDatabaseAdapter,
55
+ migrations: list[Migration],
56
+ ) -> list[Migration]:
57
+ """Apply all pending migrations in list order.
58
+
59
+ Returns the list of migrations that were actually applied
60
+ (empty if everything was already up-to-date).
61
+ """
62
+ await self._ensure_table(adapter)
63
+ applied = await self._applied_versions(adapter)
64
+
65
+ newly_applied: list[Migration] = []
66
+ for migration in migrations:
67
+ if migration.version in applied:
68
+ continue
69
+ await adapter.execute(migration.up)
70
+ await adapter.execute(
71
+ f"INSERT INTO {_MIGRATIONS_TABLE} (version, description) "
72
+ "VALUES (:version, :description)",
73
+ {"version": migration.version, "description": migration.description},
74
+ )
75
+ newly_applied.append(migration)
76
+
77
+ return newly_applied
78
+
79
+ async def current_version(
80
+ self,
81
+ adapter: ABCDatabaseAdapter,
82
+ ) -> str | None:
83
+ """Return the latest applied migration version, or ``None``."""
84
+ await self._ensure_table(adapter)
85
+ row = await adapter.fetch_one(
86
+ f"SELECT version FROM {_MIGRATIONS_TABLE} ORDER BY version DESC LIMIT 1"
87
+ )
88
+ return row["version"] if row else None
89
+
90
+ async def pending(
91
+ self,
92
+ adapter: ABCDatabaseAdapter,
93
+ migrations: list[Migration],
94
+ ) -> list[Migration]:
95
+ """Return migrations not yet applied, preserving list order."""
96
+ await self._ensure_table(adapter)
97
+ applied = await self._applied_versions(adapter)
98
+ return [m for m in migrations if m.version not in applied]
File without changes
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: csrd-migration
3
+ Version: 0.3.89
4
+ Summary: Versioned schema migrations for csrd database adapters
5
+ Project-URL: Repository, https://github.com/csrd-api/fastapi-common
6
+ Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/migration
7
+ Project-URL: Changelog, https://github.com/csrd-api/fastapi-common/blob/main/CHANGELOG.md
8
+ License: MIT
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: csrd-repository
11
+ Description-Content-Type: text/markdown
12
+
13
+ # csrd-migration
14
+
15
+ Versioned, idempotent schema migrations for `csrd.repository` database adapters.
16
+
17
+ ## Quick start
18
+
19
+ ```python
20
+ from csrd.migration import Migration, MigrationRunner
21
+ from csrd.repository import SQLiteAdapter
22
+
23
+ MIGRATIONS = [
24
+ Migration(
25
+ version="001",
26
+ description="create users table",
27
+ up="CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT NOT NULL)",
28
+ down="DROP TABLE users",
29
+ ),
30
+ ]
31
+
32
+ async def setup_db():
33
+ adapter = SQLiteAdapter("app.db")
34
+ await adapter.connect()
35
+ runner = MigrationRunner()
36
+ await runner.apply_all(adapter, MIGRATIONS)
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### `Migration`
42
+
43
+ Frozen dataclass representing a single schema change.
44
+
45
+ | Field | Type | Description |
46
+ |---|---|---|
47
+ | `version` | `str` | Unique version identifier (e.g. `"001"`, `"002"`) |
48
+ | `description` | `str` | Human-readable description |
49
+ | `up` | `str` | SQL to apply the migration |
50
+ | `down` | `str` | SQL to revert the migration |
51
+
52
+ ### `MigrationRunner`
53
+
54
+ Applies and tracks migrations against any `ABCDatabaseAdapter`.
55
+
56
+ | Method | Description |
57
+ |---|---|
58
+ | `apply_all(adapter, migrations)` | Apply all pending migrations in order |
59
+ | `current_version(adapter)` | Return the latest applied version, or `None` |
60
+ | `pending(adapter, migrations)` | Return migrations not yet applied |
61
+
62
+ The runner creates a `_csrd_migrations` table automatically to track
63
+ applied versions. Re-running is idempotent — already-applied migrations
64
+ are skipped.
65
+
66
+ ## Design
67
+
68
+ - **Adapter-agnostic** — works with `SQLiteAdapter`, `PGAdapter`, `MariaAdapter`
69
+ - **Idempotent** — safe to call on every startup
70
+ - **Ordered** — migrations are applied in list order
71
+ - **Future-ready** — the `Migration` dataclass is the stable interface for
72
+ a future model→SQL translation layer (see `docs/AUGMENT_PLAN.md`)
@@ -0,0 +1,7 @@
1
+ csrd/migration/__init__.py,sha256=vVcEZPkwOf8DLCC-aCrZ61r3hKTg54F4anCasqhHR1A,252
2
+ csrd/migration/_migration.py,sha256=eW6XTH0qY25KaBD0od5NtaIJDpEWVCWmwqBSaUzktt8,751
3
+ csrd/migration/_runner.py,sha256=nEQKntr7yhbRVAWhLWDQrXMPYrH4vVM3nkpy_m0ni6I,3401
4
+ csrd/migration/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ csrd_migration-0.3.89.dist-info/METADATA,sha256=skttyCbqPxHwEhgkb2JW9ekxT2CT0fSweeIlQ5aL8rM,2309
6
+ csrd_migration-0.3.89.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ csrd_migration-0.3.89.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any