fraiseql-confiture 0.1.0__cp311-cp311-manylinux_2_34_x86_64.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.
Potentially problematic release.
This version of fraiseql-confiture might be problematic. Click here for more details.
- confiture/__init__.py +45 -0
- confiture/_core.cpython-311-x86_64-linux-gnu.so +0 -0
- confiture/cli/__init__.py +0 -0
- confiture/cli/main.py +720 -0
- confiture/config/__init__.py +0 -0
- confiture/config/environment.py +190 -0
- confiture/core/__init__.py +0 -0
- confiture/core/builder.py +336 -0
- confiture/core/connection.py +120 -0
- confiture/core/differ.py +522 -0
- confiture/core/migration_generator.py +298 -0
- confiture/core/migrator.py +369 -0
- confiture/core/schema_to_schema.py +592 -0
- confiture/core/syncer.py +540 -0
- confiture/exceptions.py +141 -0
- confiture/integrations/__init__.py +0 -0
- confiture/models/__init__.py +0 -0
- confiture/models/migration.py +95 -0
- confiture/models/schema.py +203 -0
- fraiseql_confiture-0.1.0.dist-info/METADATA +350 -0
- fraiseql_confiture-0.1.0.dist-info/RECORD +24 -0
- fraiseql_confiture-0.1.0.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.1.0.dist-info/entry_points.txt +2 -0
- fraiseql_confiture-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Migration base class for database migrations."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import psycopg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(ABC):
|
|
10
|
+
"""Base class for all database migrations.
|
|
11
|
+
|
|
12
|
+
Each migration must:
|
|
13
|
+
- Define a version (e.g., "001", "002")
|
|
14
|
+
- Define a name (e.g., "create_users")
|
|
15
|
+
- Implement up() method for applying the migration
|
|
16
|
+
- Implement down() method for rolling back the migration
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
>>> class CreateUsers(Migration):
|
|
20
|
+
... version = "001"
|
|
21
|
+
... name = "create_users"
|
|
22
|
+
...
|
|
23
|
+
... def up(self):
|
|
24
|
+
... self.execute('''
|
|
25
|
+
... CREATE TABLE users (
|
|
26
|
+
... id SERIAL PRIMARY KEY,
|
|
27
|
+
... username TEXT NOT NULL
|
|
28
|
+
... )
|
|
29
|
+
... ''')
|
|
30
|
+
...
|
|
31
|
+
... def down(self):
|
|
32
|
+
... self.execute('DROP TABLE users')
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Subclasses must define these
|
|
36
|
+
version: str
|
|
37
|
+
name: str
|
|
38
|
+
|
|
39
|
+
def __init__(self, connection: psycopg.Connection):
|
|
40
|
+
"""Initialize migration with database connection.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
connection: psycopg3 database connection
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
TypeError: If version or name not defined in subclass
|
|
47
|
+
"""
|
|
48
|
+
self.connection = connection
|
|
49
|
+
|
|
50
|
+
# Ensure subclass defined version and name
|
|
51
|
+
if not hasattr(self.__class__, "version") or self.__class__.version is None:
|
|
52
|
+
raise TypeError(f"{self.__class__.__name__} must define a 'version' class attribute")
|
|
53
|
+
if not hasattr(self.__class__, "name") or self.__class__.name is None:
|
|
54
|
+
raise TypeError(f"{self.__class__.__name__} must define a 'name' class attribute")
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def up(self) -> None:
|
|
58
|
+
"""Apply the migration.
|
|
59
|
+
|
|
60
|
+
This method must be implemented by subclasses to perform
|
|
61
|
+
the forward migration (e.g., CREATE TABLE, ALTER TABLE).
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
NotImplementedError: If not implemented by subclass
|
|
65
|
+
"""
|
|
66
|
+
raise NotImplementedError(f"{self.__class__.__name__}.up() must be implemented")
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def down(self) -> None:
|
|
70
|
+
"""Rollback the migration.
|
|
71
|
+
|
|
72
|
+
This method must be implemented by subclasses to perform
|
|
73
|
+
the reverse migration (e.g., DROP TABLE, revert ALTER).
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
NotImplementedError: If not implemented by subclass
|
|
77
|
+
"""
|
|
78
|
+
raise NotImplementedError(f"{self.__class__.__name__}.down() must be implemented")
|
|
79
|
+
|
|
80
|
+
def execute(self, sql: str, params: tuple[Any, ...] | None = None) -> None:
|
|
81
|
+
"""Execute a SQL statement.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
sql: SQL statement to execute
|
|
85
|
+
params: Optional query parameters (for parameterized queries)
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> self.execute("CREATE TABLE users (id INT)")
|
|
89
|
+
>>> self.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
|
|
90
|
+
"""
|
|
91
|
+
with self.connection.cursor() as cursor:
|
|
92
|
+
if params:
|
|
93
|
+
cursor.execute(sql, params)
|
|
94
|
+
else:
|
|
95
|
+
cursor.execute(sql)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Data models for schema representation.
|
|
2
|
+
|
|
3
|
+
These models represent database schema objects (tables, columns, indexes, etc.)
|
|
4
|
+
in a structured format for diff detection and comparison.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ColumnType(str, Enum):
|
|
12
|
+
"""PostgreSQL column types."""
|
|
13
|
+
|
|
14
|
+
# Integer types
|
|
15
|
+
SMALLINT = "SMALLINT"
|
|
16
|
+
INTEGER = "INTEGER"
|
|
17
|
+
BIGINT = "BIGINT"
|
|
18
|
+
SERIAL = "SERIAL"
|
|
19
|
+
BIGSERIAL = "BIGSERIAL"
|
|
20
|
+
|
|
21
|
+
# Numeric types
|
|
22
|
+
NUMERIC = "NUMERIC"
|
|
23
|
+
DECIMAL = "DECIMAL"
|
|
24
|
+
REAL = "REAL"
|
|
25
|
+
DOUBLE_PRECISION = "DOUBLE PRECISION"
|
|
26
|
+
|
|
27
|
+
# Text types
|
|
28
|
+
VARCHAR = "VARCHAR"
|
|
29
|
+
CHAR = "CHAR"
|
|
30
|
+
TEXT = "TEXT"
|
|
31
|
+
|
|
32
|
+
# Boolean
|
|
33
|
+
BOOLEAN = "BOOLEAN"
|
|
34
|
+
|
|
35
|
+
# Date/Time
|
|
36
|
+
DATE = "DATE"
|
|
37
|
+
TIME = "TIME"
|
|
38
|
+
TIMESTAMP = "TIMESTAMP"
|
|
39
|
+
TIMESTAMPTZ = "TIMESTAMPTZ"
|
|
40
|
+
|
|
41
|
+
# UUID
|
|
42
|
+
UUID = "UUID"
|
|
43
|
+
|
|
44
|
+
# JSON
|
|
45
|
+
JSON = "JSON"
|
|
46
|
+
JSONB = "JSONB"
|
|
47
|
+
|
|
48
|
+
# Binary
|
|
49
|
+
BYTEA = "BYTEA"
|
|
50
|
+
|
|
51
|
+
# Unknown/Custom
|
|
52
|
+
UNKNOWN = "UNKNOWN"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Column:
|
|
57
|
+
"""Represents a database column."""
|
|
58
|
+
|
|
59
|
+
name: str
|
|
60
|
+
type: ColumnType
|
|
61
|
+
nullable: bool = True
|
|
62
|
+
default: str | None = None
|
|
63
|
+
primary_key: bool = False
|
|
64
|
+
unique: bool = False
|
|
65
|
+
length: int | None = None # For VARCHAR(n), etc.
|
|
66
|
+
|
|
67
|
+
def __eq__(self, other: object) -> bool:
|
|
68
|
+
"""Compare columns for equality."""
|
|
69
|
+
if not isinstance(other, Column):
|
|
70
|
+
return NotImplemented
|
|
71
|
+
return (
|
|
72
|
+
self.name == other.name
|
|
73
|
+
and self.type == other.type
|
|
74
|
+
and self.nullable == other.nullable
|
|
75
|
+
and self.default == other.default
|
|
76
|
+
and self.primary_key == other.primary_key
|
|
77
|
+
and self.unique == other.unique
|
|
78
|
+
and self.length == other.length
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def __hash__(self) -> int:
|
|
82
|
+
"""Make column hashable for use in sets."""
|
|
83
|
+
return hash(
|
|
84
|
+
(
|
|
85
|
+
self.name,
|
|
86
|
+
self.type,
|
|
87
|
+
self.nullable,
|
|
88
|
+
self.default,
|
|
89
|
+
self.primary_key,
|
|
90
|
+
self.unique,
|
|
91
|
+
self.length,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class Table:
|
|
98
|
+
"""Represents a database table."""
|
|
99
|
+
|
|
100
|
+
name: str
|
|
101
|
+
columns: list[Column] = field(default_factory=list)
|
|
102
|
+
indexes: list[str] = field(default_factory=list) # Simplified for MVP
|
|
103
|
+
constraints: list[str] = field(default_factory=list) # Simplified for MVP
|
|
104
|
+
|
|
105
|
+
def get_column(self, name: str) -> Column | None:
|
|
106
|
+
"""Get column by name."""
|
|
107
|
+
for col in self.columns:
|
|
108
|
+
if col.name == name:
|
|
109
|
+
return col
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def has_column(self, name: str) -> bool:
|
|
113
|
+
"""Check if table has column."""
|
|
114
|
+
return self.get_column(name) is not None
|
|
115
|
+
|
|
116
|
+
def __eq__(self, other: object) -> bool:
|
|
117
|
+
"""Compare tables for equality."""
|
|
118
|
+
if not isinstance(other, Table):
|
|
119
|
+
return NotImplemented
|
|
120
|
+
return (
|
|
121
|
+
self.name == other.name
|
|
122
|
+
and self.columns == other.columns
|
|
123
|
+
and self.indexes == other.indexes
|
|
124
|
+
and self.constraints == other.constraints
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class Schema:
|
|
130
|
+
"""Represents a complete database schema."""
|
|
131
|
+
|
|
132
|
+
tables: list[Table] = field(default_factory=list)
|
|
133
|
+
|
|
134
|
+
def get_table(self, name: str) -> Table | None:
|
|
135
|
+
"""Get table by name."""
|
|
136
|
+
for table in self.tables:
|
|
137
|
+
if table.name == name:
|
|
138
|
+
return table
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def has_table(self, name: str) -> bool:
|
|
142
|
+
"""Check if schema has table."""
|
|
143
|
+
return self.get_table(name) is not None
|
|
144
|
+
|
|
145
|
+
def table_names(self) -> list[str]:
|
|
146
|
+
"""Get list of all table names."""
|
|
147
|
+
return [table.name for table in self.tables]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class SchemaChange:
|
|
152
|
+
"""Represents a single change between two schemas."""
|
|
153
|
+
|
|
154
|
+
type: str # ADD_TABLE, DROP_TABLE, ADD_COLUMN, etc.
|
|
155
|
+
table: str | None = None
|
|
156
|
+
column: str | None = None
|
|
157
|
+
old_value: str | None = None
|
|
158
|
+
new_value: str | None = None
|
|
159
|
+
details: dict[str, str] | None = None
|
|
160
|
+
|
|
161
|
+
def __str__(self) -> str:
|
|
162
|
+
"""String representation of change."""
|
|
163
|
+
if self.type == "ADD_TABLE":
|
|
164
|
+
return f"ADD TABLE {self.table}"
|
|
165
|
+
elif self.type == "DROP_TABLE":
|
|
166
|
+
return f"DROP TABLE {self.table}"
|
|
167
|
+
elif self.type == "RENAME_TABLE":
|
|
168
|
+
return f"RENAME TABLE {self.old_value} TO {self.new_value}"
|
|
169
|
+
elif self.type == "ADD_COLUMN":
|
|
170
|
+
return f"ADD COLUMN {self.table}.{self.column}"
|
|
171
|
+
elif self.type == "DROP_COLUMN":
|
|
172
|
+
return f"DROP COLUMN {self.table}.{self.column}"
|
|
173
|
+
elif self.type == "RENAME_COLUMN":
|
|
174
|
+
return f"RENAME COLUMN {self.table}.{self.old_value} TO {self.new_value}"
|
|
175
|
+
elif self.type == "CHANGE_COLUMN_TYPE":
|
|
176
|
+
return f"CHANGE COLUMN TYPE {self.table}.{self.column} FROM {self.old_value} TO {self.new_value}"
|
|
177
|
+
elif self.type == "CHANGE_COLUMN_NULLABLE":
|
|
178
|
+
return f"CHANGE COLUMN NULLABLE {self.table}.{self.column} FROM {self.old_value} TO {self.new_value}"
|
|
179
|
+
elif self.type == "CHANGE_COLUMN_DEFAULT":
|
|
180
|
+
return f"CHANGE COLUMN DEFAULT {self.table}.{self.column}"
|
|
181
|
+
else:
|
|
182
|
+
return f"{self.type}: {self.table}.{self.column if self.column else ''}"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class SchemaDiff:
|
|
187
|
+
"""Represents the difference between two schemas."""
|
|
188
|
+
|
|
189
|
+
changes: list[SchemaChange] = field(default_factory=list)
|
|
190
|
+
|
|
191
|
+
def has_changes(self) -> bool:
|
|
192
|
+
"""Check if there are any changes."""
|
|
193
|
+
return len(self.changes) > 0
|
|
194
|
+
|
|
195
|
+
def count_by_type(self, change_type: str) -> int:
|
|
196
|
+
"""Count changes of a specific type."""
|
|
197
|
+
return sum(1 for c in self.changes if c.type == change_type)
|
|
198
|
+
|
|
199
|
+
def __str__(self) -> str:
|
|
200
|
+
"""String representation of diff."""
|
|
201
|
+
if not self.has_changes():
|
|
202
|
+
return "No changes detected"
|
|
203
|
+
return "\n".join(str(c) for c in self.changes)
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fraiseql-confiture
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Database
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Dist: typer>=0.12.0
|
|
14
|
+
Requires-Dist: rich>=13.7.0
|
|
15
|
+
Requires-Dist: pydantic>=2.5.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
17
|
+
Requires-Dist: psycopg[binary]>=3.1.0
|
|
18
|
+
Requires-Dist: sqlparse>=0.5.0
|
|
19
|
+
Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov>=4.1.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-watch>=4.2.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.6.0 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.11.0 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: pre-commit>=3.5.0 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: types-pyyaml>=6.0.0 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: maturin>=1.7.0 ; extra == 'dev'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Provides-Extra: fraiseql
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Summary: PostgreSQL migrations, sweetly done ๐
|
|
32
|
+
Keywords: postgresql,migration,database,schema,ddl
|
|
33
|
+
Home-Page: https://github.com/fraiseql/confiture
|
|
34
|
+
Author-email: evoludigit <lionel@fraiseql.com>
|
|
35
|
+
License: MIT
|
|
36
|
+
Requires-Python: >=3.11
|
|
37
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
38
|
+
Project-URL: Homepage, https://github.com/fraiseql/confiture
|
|
39
|
+
Project-URL: Documentation, https://github.com/fraiseql/confiture
|
|
40
|
+
Project-URL: Repository, https://github.com/fraiseql/confiture
|
|
41
|
+
Project-URL: Issues, https://github.com/fraiseql/confiture/issues
|
|
42
|
+
Project-URL: FraiseQL, https://github.com/fraiseql/fraiseql
|
|
43
|
+
|
|
44
|
+
# Confiture ๐
|
|
45
|
+
|
|
46
|
+
**PostgreSQL migrations, sweetly done**
|
|
47
|
+
|
|
48
|
+
Confiture is the official migration tool for [FraiseQL](https://github.com/fraiseql/fraiseql), designed with a **build-from-scratch philosophy** and **4 migration strategies** to handle every scenario from local development to zero-downtime production deployments.
|
|
49
|
+
|
|
50
|
+
> **Part of the FraiseQL ecosystem** - While Confiture works standalone for any PostgreSQL project, it's designed to integrate seamlessly with FraiseQL's GraphQL-first approach.
|
|
51
|
+
|
|
52
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
|
+
[](https://www.python.org/downloads/)
|
|
54
|
+
[](https://www.postgresql.org/)
|
|
55
|
+
[](https://github.com/fraiseql/confiture/actions/workflows/ci.yml)
|
|
56
|
+
[](https://github.com/astral-sh/ruff)
|
|
57
|
+
[](https://github.com/python/mypy)
|
|
58
|
+
[](https://www.rust-lang.org/)
|
|
59
|
+
[](https://github.com/fraiseql/fraiseql)
|
|
60
|
+
[](https://github.com/fraiseql/confiture)
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Why Confiture?
|
|
65
|
+
|
|
66
|
+
Traditional migration tools (Alembic, Django migrations) **replay migration history** to build databases. This is slow and brittle.
|
|
67
|
+
|
|
68
|
+
Confiture treats **DDL source files as the single source of truth**:
|
|
69
|
+
|
|
70
|
+
- โ
**Fresh databases in <1 second** (not minutes)
|
|
71
|
+
- โ
**4 migration strategies** (simple ALTER to zero-downtime FDW)
|
|
72
|
+
- โ
**Production data sync** built-in (with PII anonymization)
|
|
73
|
+
- โ
**Python + Rust performance** (10-50x faster than pure Python)
|
|
74
|
+
- โ
**Perfect with FraiseQL**, useful for everyone
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## The Four Mediums
|
|
79
|
+
|
|
80
|
+
### 1๏ธโฃ Build from DDL
|
|
81
|
+
```bash
|
|
82
|
+
confiture build --env production
|
|
83
|
+
```
|
|
84
|
+
Build fresh database from `db/schema/` DDL files in <1 second.
|
|
85
|
+
|
|
86
|
+
### 2๏ธโฃ Incremental Migrations (ALTER)
|
|
87
|
+
```bash
|
|
88
|
+
confiture migrate up
|
|
89
|
+
```
|
|
90
|
+
Apply migrations to existing database (simple schema changes).
|
|
91
|
+
|
|
92
|
+
### 3๏ธโฃ Production Data Sync
|
|
93
|
+
```bash
|
|
94
|
+
confiture sync --from production --anonymize users.email
|
|
95
|
+
```
|
|
96
|
+
Copy production data to local/staging with PII anonymization.
|
|
97
|
+
|
|
98
|
+
### 4๏ธโฃ Schema-to-Schema Migration (Zero-Downtime)
|
|
99
|
+
```bash
|
|
100
|
+
confiture migrate schema-to-schema --strategy fdw
|
|
101
|
+
```
|
|
102
|
+
Complex migrations via FDW with 0-5 second downtime.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Quick Start
|
|
107
|
+
|
|
108
|
+
### Installation
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install fraiseql-confiture
|
|
112
|
+
|
|
113
|
+
# Or with FraiseQL integration
|
|
114
|
+
pip install fraiseql-confiture[fraiseql]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Initialize Project
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
confiture init
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Creates:
|
|
124
|
+
```
|
|
125
|
+
db/
|
|
126
|
+
โโโ schema/ # DDL: CREATE TABLE, views, functions
|
|
127
|
+
โ โโโ 00_common/
|
|
128
|
+
โ โโโ 10_tables/
|
|
129
|
+
โ โโโ 20_views/
|
|
130
|
+
โโโ seeds/ # INSERT: Environment-specific test data
|
|
131
|
+
โ โโโ common/
|
|
132
|
+
โ โโโ development/
|
|
133
|
+
โ โโโ test/
|
|
134
|
+
โโโ migrations/ # Generated migration files
|
|
135
|
+
โโโ environments/ # Environment configurations
|
|
136
|
+
โโโ local.yaml
|
|
137
|
+
โโโ test.yaml
|
|
138
|
+
โโโ production.yaml
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Build Schema
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Build local database
|
|
145
|
+
confiture build --env local
|
|
146
|
+
|
|
147
|
+
# Build production schema
|
|
148
|
+
confiture build --env production
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Create Migration
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Edit schema
|
|
155
|
+
vim db/schema/10_tables/users.sql
|
|
156
|
+
|
|
157
|
+
# Generate migration
|
|
158
|
+
confiture migrate generate --name "add_user_bio"
|
|
159
|
+
|
|
160
|
+
# Apply migration
|
|
161
|
+
confiture migrate up
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Documentation
|
|
167
|
+
|
|
168
|
+
### ๐ User Guides
|
|
169
|
+
- **[Medium 1: Build from DDL](docs/guides/medium-1-build-from-ddl.md)** - Fresh databases in <1 second
|
|
170
|
+
- **[Medium 2: Incremental Migrations](docs/guides/medium-2-incremental-migrations.md)** - ALTER-based changes
|
|
171
|
+
- **[Medium 3: Production Data Sync](docs/guides/medium-3-production-sync.md)** - Copy and anonymize data
|
|
172
|
+
- **[Medium 4: Zero-Downtime Migrations](docs/guides/medium-4-schema-to-schema.md)** - Schema-to-schema via FDW
|
|
173
|
+
- **[Migration Decision Tree](docs/guides/migration-decision-tree.md)** - Choose the right strategy
|
|
174
|
+
|
|
175
|
+
### ๐ API Reference
|
|
176
|
+
- **[CLI Reference](docs/reference/cli.md)** - All commands documented
|
|
177
|
+
- **[Configuration Reference](docs/reference/configuration.md)** - Environment configuration
|
|
178
|
+
- **[Schema Builder API](docs/api/builder.md)** - Building schemas programmatically
|
|
179
|
+
- **[Migrator API](docs/api/migrator.md)** - Migration execution
|
|
180
|
+
- **[Syncer API](docs/api/syncer.md)** - Production data sync
|
|
181
|
+
- **[Schema-to-Schema API](docs/api/schema-to-schema.md)** - Zero-downtime migrations
|
|
182
|
+
|
|
183
|
+
### ๐ก Examples
|
|
184
|
+
- **[Examples Overview](examples/)** - 5 complete production examples
|
|
185
|
+
- **[Basic Migration](examples/01-basic-migration/)** - Learn the fundamentals (15 min)
|
|
186
|
+
- **[FraiseQL Integration](examples/02-fraiseql-integration/)** - GraphQL workflow (20 min)
|
|
187
|
+
- **[Zero-Downtime](examples/03-zero-downtime-migration/)** - Production deployment (30 min)
|
|
188
|
+
- **[Production Sync](examples/04-production-sync-anonymization/)** - PII anonymization (25 min)
|
|
189
|
+
- **[Multi-Environment Workflow](examples/05-multi-environment-workflow/)** - Complete CI/CD (30 min)
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Features
|
|
194
|
+
|
|
195
|
+
### โ
Complete (Phases 1-3)
|
|
196
|
+
|
|
197
|
+
**Core Migration System**:
|
|
198
|
+
- โ
Build from DDL (Medium 1) - Fresh databases in <1 second
|
|
199
|
+
- โ
Incremental migrations (Medium 2) - Simple ALTER-based changes
|
|
200
|
+
- โ
Production data sync (Medium 3) - Copy with PII anonymization
|
|
201
|
+
- โ
Zero-downtime migrations (Medium 4) - Schema-to-schema via FDW
|
|
202
|
+
|
|
203
|
+
**Performance & Distribution**:
|
|
204
|
+
- โ
**Rust performance layer** (10-50x speedup) ๐
|
|
205
|
+
- โ
**Binary wheels** for Linux, macOS, Windows
|
|
206
|
+
- โ
Parallel migration execution
|
|
207
|
+
- โ
Progress tracking with resumability
|
|
208
|
+
|
|
209
|
+
**Developer Experience**:
|
|
210
|
+
- โ
Environment-specific seed data (development/test/production)
|
|
211
|
+
- โ
Schema diff detection with auto-generation
|
|
212
|
+
- โ
CLI with rich terminal output and colors
|
|
213
|
+
- โ
Comprehensive documentation (5 guides, 4 API docs)
|
|
214
|
+
- โ
Production-ready examples (5 complete scenarios)
|
|
215
|
+
|
|
216
|
+
**Integration & Safety**:
|
|
217
|
+
- โ
FraiseQL GraphQL integration
|
|
218
|
+
- โ
Multi-environment configuration
|
|
219
|
+
- โ
Transaction safety with rollback support
|
|
220
|
+
- โ
PII anonymization with compliance tools
|
|
221
|
+
- โ
CI/CD pipeline examples (GitHub Actions)
|
|
222
|
+
|
|
223
|
+
### ๐ง Coming Soon (Phase 4)
|
|
224
|
+
- Advanced migration hooks (before/after)
|
|
225
|
+
- Custom anonymization strategies
|
|
226
|
+
- Interactive migration wizard
|
|
227
|
+
- Migration dry-run mode
|
|
228
|
+
- Database schema linting
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Comparison
|
|
233
|
+
|
|
234
|
+
| Feature | Alembic | pgroll | **Confiture** |
|
|
235
|
+
|---------|---------|--------|---------------|
|
|
236
|
+
| **Philosophy** | Migration replay | Multi-version schema | **Build-from-DDL** |
|
|
237
|
+
| **Fresh DB setup** | Minutes | Minutes | **<1 second** |
|
|
238
|
+
| **Zero-downtime** | โ No | โ
Yes | **โ
Yes (FDW)** |
|
|
239
|
+
| **Production sync** | โ No | โ No | **โ
Built-in** |
|
|
240
|
+
| **Language** | Python | Go | **Python + Rust** |
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Development Status
|
|
245
|
+
|
|
246
|
+
**Current Version**: 0.1.0 (Initial PyPI Release) ๐
|
|
247
|
+
|
|
248
|
+
**Milestone Progress**:
|
|
249
|
+
- โ
Phase 1: Python MVP (Complete - Oct 2025)
|
|
250
|
+
- โ
Phase 2: Rust Performance Layer (Complete - Oct 2025)
|
|
251
|
+
- โ
Phase 3: Production Features (Complete - Oct 2025)
|
|
252
|
+
- โ
Zero-downtime migrations (FDW)
|
|
253
|
+
- โ
Production data sync with PII anonymization
|
|
254
|
+
- โ
Comprehensive documentation (5 guides, 4 API references)
|
|
255
|
+
- โ
Production examples (5 complete scenarios)
|
|
256
|
+
- โณ Phase 4: Advanced Features (Q1 2026)
|
|
257
|
+
- Migration hooks, wizards, dry-run mode
|
|
258
|
+
|
|
259
|
+
**Statistics**:
|
|
260
|
+
- ๐ฆ 4 migration strategies implemented
|
|
261
|
+
- ๐ 5 comprehensive user guides
|
|
262
|
+
- ๐ 4 API reference pages
|
|
263
|
+
- ๐ก 5 production-ready examples
|
|
264
|
+
- ๐งช 95% test coverage
|
|
265
|
+
- โก 10-50x performance with Rust
|
|
266
|
+
|
|
267
|
+
See [PHASES.md](PHASES.md) for detailed roadmap.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Contributing
|
|
272
|
+
|
|
273
|
+
Contributions welcome! We'd love your help making Confiture even better.
|
|
274
|
+
|
|
275
|
+
**Quick Start**:
|
|
276
|
+
```bash
|
|
277
|
+
# Clone repository
|
|
278
|
+
git clone https://github.com/fraiseql/confiture.git
|
|
279
|
+
cd confiture
|
|
280
|
+
|
|
281
|
+
# Install dependencies (includes Rust build)
|
|
282
|
+
uv sync --all-extras
|
|
283
|
+
|
|
284
|
+
# Build Rust extension
|
|
285
|
+
uv run maturin develop
|
|
286
|
+
|
|
287
|
+
# Run tests
|
|
288
|
+
uv run pytest --cov=confiture
|
|
289
|
+
|
|
290
|
+
# Format code
|
|
291
|
+
uv run ruff format .
|
|
292
|
+
|
|
293
|
+
# Type checking
|
|
294
|
+
uv run mypy python/confiture/
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Resources**:
|
|
298
|
+
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Contributing guidelines
|
|
299
|
+
- **[CLAUDE.md](CLAUDE.md)** - AI-assisted development guide
|
|
300
|
+
- **[PHASES.md](PHASES.md)** - Detailed roadmap
|
|
301
|
+
|
|
302
|
+
**What to contribute**:
|
|
303
|
+
- ๐ Bug fixes
|
|
304
|
+
- โจ New features
|
|
305
|
+
- ๐ Documentation improvements
|
|
306
|
+
- ๐ก New examples
|
|
307
|
+
- ๐งช Test coverage improvements
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Author
|
|
312
|
+
|
|
313
|
+
**Vibe-engineered by [Lionel Hamayon](https://github.com/LionelHamayon)** ๐
|
|
314
|
+
|
|
315
|
+
Confiture was crafted with care as the migration tool for the FraiseQL ecosystem, combining the elegance of Python with the performance of Rust, and the sweetness of strawberry jam.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
322
|
+
|
|
323
|
+
Copyright (c) 2025 Lionel Hamayon
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Acknowledgments
|
|
328
|
+
|
|
329
|
+
- Inspired by printoptim_backend's build-from-scratch approach
|
|
330
|
+
- Built for [FraiseQL](https://github.com/fraiseql/fraiseql) GraphQL framework
|
|
331
|
+
- Influenced by pgroll, Alembic, and Reshape
|
|
332
|
+
- Developed with AI-assisted vibe engineering โจ
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## FraiseQL Ecosystem
|
|
337
|
+
|
|
338
|
+
Confiture is part of the FraiseQL family:
|
|
339
|
+
|
|
340
|
+
- **[FraiseQL](https://github.com/fraiseql/fraiseql)** - Modern GraphQL framework for Python
|
|
341
|
+
- **[Confiture](https://github.com/fraiseql/confiture)** - PostgreSQL migration tool (you are here)
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
*Making jam from strawberries, one migration at a time.* ๐โ๐ฏ
|
|
346
|
+
|
|
347
|
+
*Vibe-engineered with โค๏ธ by Lionel Hamayon*
|
|
348
|
+
|
|
349
|
+
**[Documentation](https://github.com/fraiseql/confiture)** โข **[GitHub](https://github.com/fraiseql/confiture)** โข **[PyPI](https://pypi.org/project/fraiseql-confiture/)**
|
|
350
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
confiture/__init__.py,sha256=tCAHIJRp5S5JI_5rzwmI_jqOSJKy0QuOuSzPv3l2lhI,1162
|
|
2
|
+
confiture/_core.cpython-311-x86_64-linux-gnu.so,sha256=YNx1x-t6ulDeH03S5qK9V3LyycIPJE86Rru_GU7Hj34,504496
|
|
3
|
+
confiture/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
confiture/cli/main.py,sha256=iYFN2hgkK6a_sbKrkpq6rMmdQvi58EOW7ojOaTfs0g4,22479
|
|
5
|
+
confiture/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
confiture/config/environment.py,sha256=8o2Dt6fko6Ovvn1GBW76l_zjaZABIHk5DKqBbfGcMTA,6229
|
|
7
|
+
confiture/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
confiture/core/builder.py,sha256=PlFiyD1T81briS25LkYm0EbQjo34VDBKcAGlmn7H4rc,11388
|
|
9
|
+
confiture/core/connection.py,sha256=s6ZACflypVRYUc9MZ9KNZuFObbtYjLWQbcGN5ebisaw,3397
|
|
10
|
+
confiture/core/differ.py,sha256=etICDfHY9wOvqxFkiwuVIbHS7N8gmKG04qhbfWtB2t0,18024
|
|
11
|
+
confiture/core/migration_generator.py,sha256=-vOzZO8ENjJqDUOaNsE6s28SgxQXP9NYDwgV_7RiYW4,9732
|
|
12
|
+
confiture/core/migrator.py,sha256=m-__eQ-ArzZ_Vgd0Vo0kFkjb9G6EQl-QRhmcw0ocFoQ,13352
|
|
13
|
+
confiture/core/schema_to_schema.py,sha256=ZilXOhK4O_SStqzqG3oztNpakskF2BW74bA4buZnKz8,22292
|
|
14
|
+
confiture/core/syncer.py,sha256=d1b4u1QjrRaw2jia37wiNVZYZ51GaKoJ1E7dJ3vCeM0,18393
|
|
15
|
+
confiture/exceptions.py,sha256=wuSzE-XpX5QeZEqNQqi85efKu5l2-QUhmBbjDLKm02w,3339
|
|
16
|
+
confiture/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
confiture/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
confiture/models/migration.py,sha256=KcTY2EUCrsFOqogoAzVDpZAoQAKFFaH13-Zyjf-yYfg,3106
|
|
19
|
+
confiture/models/schema.py,sha256=9zQzyyw35HR0T1b0T9hI9xoripdTeWLDFTnO_ibcrMI,5909
|
|
20
|
+
fraiseql_confiture-0.1.0.dist-info/METADATA,sha256=t_i1-h4Bq0oSaWT6zDX25vwwfCrPjnbRsk30CA5X1us,11826
|
|
21
|
+
fraiseql_confiture-0.1.0.dist-info/WHEEL,sha256=OVlAaljgWPhfhJRVM6VsLWLOev5VbYFkuMRMXaOVkcY,108
|
|
22
|
+
fraiseql_confiture-0.1.0.dist-info/entry_points.txt,sha256=nRq8YKFFl7Pr0462GxN2Y1PAe77UJVXC0xrw92qnZks,51
|
|
23
|
+
fraiseql_confiture-0.1.0.dist-info/licenses/LICENSE,sha256=c6fKI61h1Rn-8ZkldOQxeyxEQYfzjpXpu_HgVjSu1ZI,1071
|
|
24
|
+
fraiseql_confiture-0.1.0.dist-info/RECORD,,
|