dbconform 0.2.4__tar.gz → 0.2.6__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.
- {dbconform-0.2.4/src/dbconform.egg-info → dbconform-0.2.6}/PKG-INFO +17 -1
- {dbconform-0.2.4 → dbconform-0.2.6}/README.md +15 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/pyproject.toml +3 -2
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/adapters/model_schema.py +69 -3
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/plan/builder.py +10 -1
- {dbconform-0.2.4 → dbconform-0.2.6/src/dbconform.egg-info}/PKG-INFO +17 -1
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform.egg-info/requires.txt +1 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/LICENSE +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/setup.cfg +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/adapters/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/adapters/sa_to_neutral.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/cli.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/compare/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/compare/db_schema.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/compare/diff.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/conform.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/errors.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/internal/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/internal/objects.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/internal/types.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/plan/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/plan/steps.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/db_schema.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/diff.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/model_schema.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/objects.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/schema/sa_to_neutral.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/sql_dialect/__init__.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/sql_dialect/base.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/sql_dialect/postgresql.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/sql_dialect/sqlite.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform/sql_dialect/sqlite_rebuild.py +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform.egg-info/SOURCES.txt +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform.egg-info/dependency_links.txt +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform.egg-info/entry_points.txt +0 -0
- {dbconform-0.2.4 → dbconform-0.2.6}/src/dbconform.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbconform
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Synchronize database schema to models — document-driven project.
|
|
5
5
|
Author: Brian L. Pond
|
|
6
6
|
License: MIT
|
|
@@ -24,6 +24,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
25
25
|
Requires-Dist: ruff; extra == "dev"
|
|
26
26
|
Requires-Dist: sqlmodel; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
28
|
Requires-Dist: typer>=0.9; extra == "dev"
|
|
28
29
|
Provides-Extra: postgres
|
|
29
30
|
Requires-Dist: psycopg[binary]>=3; extra == "postgres"
|
|
@@ -144,6 +145,21 @@ else:
|
|
|
144
145
|
print(result.sql()) # Full DDL script
|
|
145
146
|
```
|
|
146
147
|
|
|
148
|
+
**View a human-readable summary:**
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
result.print_summary()
|
|
152
|
+
# Output:
|
|
153
|
+
# ConformPlan: 2 steps, 0 extra tables, 1 skipped steps
|
|
154
|
+
# Steps:
|
|
155
|
+
# - Add column price to product
|
|
156
|
+
# - Create table cart
|
|
157
|
+
# Skipped steps:
|
|
158
|
+
# - Drop column legacy_field from product (reason: Column drop blocked: allow_drop_extra_columns=False)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The summary shows planned steps, extra tables (in DB but not in models), and skipped steps (drift that requires opt-in to fix).
|
|
162
|
+
|
|
147
163
|
### 3. Apply changes
|
|
148
164
|
|
|
149
165
|
```python
|
|
@@ -113,6 +113,21 @@ else:
|
|
|
113
113
|
print(result.sql()) # Full DDL script
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
**View a human-readable summary:**
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
result.print_summary()
|
|
120
|
+
# Output:
|
|
121
|
+
# ConformPlan: 2 steps, 0 extra tables, 1 skipped steps
|
|
122
|
+
# Steps:
|
|
123
|
+
# - Add column price to product
|
|
124
|
+
# - Create table cart
|
|
125
|
+
# Skipped steps:
|
|
126
|
+
# - Drop column legacy_field from product (reason: Column drop blocked: allow_drop_extra_columns=False)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The summary shows planned steps, extra tables (in DB but not in models), and skipped steps (drift that requires opt-in to fix).
|
|
130
|
+
|
|
116
131
|
### 3. Apply changes
|
|
117
132
|
|
|
118
133
|
```python
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbconform"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "Synchronize database schema to models — document-driven project."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -36,6 +36,7 @@ dev = [
|
|
|
36
36
|
"pytest-asyncio",
|
|
37
37
|
"ruff",
|
|
38
38
|
"sqlmodel",
|
|
39
|
+
"twine",
|
|
39
40
|
"typer>=0.9",
|
|
40
41
|
]
|
|
41
42
|
postgres = [
|
|
@@ -61,7 +62,7 @@ where = ["src"]
|
|
|
61
62
|
dbconform = "dbconform.cli:main"
|
|
62
63
|
|
|
63
64
|
[tool.commitizen]
|
|
64
|
-
version = "0.2.
|
|
65
|
+
version = "0.2.6"
|
|
65
66
|
version_scheme = "semver"
|
|
66
67
|
commit = true
|
|
67
68
|
tag = true
|
|
@@ -9,16 +9,25 @@ definitions and build a ModelSchema (name -> TableDef). See docs/requirements/01
|
|
|
9
9
|
model.__table__ and its columns/constraints/indexes; we never assign to or modify
|
|
10
10
|
the caller's Table or column objects. ModelSchema stores only internal TableDef
|
|
11
11
|
instances, not references to the original tables.
|
|
12
|
+
|
|
13
|
+
**Column defaults:** How Python and server defaults become DDL strings (including the
|
|
14
|
+
PostgreSQL date-literal pitfall) is documented in docs/technical/05-model-column-defaults.md.
|
|
12
15
|
"""
|
|
13
16
|
|
|
14
17
|
from __future__ import annotations
|
|
15
18
|
|
|
19
|
+
import math
|
|
16
20
|
from collections.abc import Sequence
|
|
21
|
+
from datetime import date, datetime, time
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
from enum import Enum
|
|
17
24
|
from typing import Any, Protocol
|
|
25
|
+
from uuid import UUID
|
|
18
26
|
|
|
19
27
|
from sqlalchemy import Table
|
|
20
28
|
from sqlalchemy.engine import Dialect
|
|
21
29
|
from sqlalchemy.schema import CheckConstraint, ForeignKeyConstraint, UniqueConstraint
|
|
30
|
+
from sqlalchemy.sql.elements import ClauseElement
|
|
22
31
|
|
|
23
32
|
from dbconform.adapters.sa_to_neutral import sa_column_to_neutral_type
|
|
24
33
|
from dbconform.internal.objects import (
|
|
@@ -69,15 +78,72 @@ def _check_expression_str(sqltext: Any) -> str:
|
|
|
69
78
|
return str(sqltext)
|
|
70
79
|
|
|
71
80
|
|
|
72
|
-
def
|
|
73
|
-
"""
|
|
81
|
+
def _python_scalar_to_sql_literal(value: Any) -> str | None:
|
|
82
|
+
"""
|
|
83
|
+
Map a Python scalar to a SQL DEFAULT fragment (dialect-agnostic literals).
|
|
84
|
+
|
|
85
|
+
Used when SQLAlchemy's column ``default`` carries a Python value (e.g. SQLModel
|
|
86
|
+
``Field(default=date(...))``). Must not use ``str(value)`` alone: bare dates
|
|
87
|
+
would emit ``1970-01-01``, which PostgreSQL parses as integer subtraction, not
|
|
88
|
+
a DATE literal. See docs/technical/05-model-column-defaults.md.
|
|
89
|
+
|
|
90
|
+
Traceability: docs/requirements/01-functional.md (Schema parity: column defaults).
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A string safe to place after ``DEFAULT `` in DDL, or None if no static
|
|
94
|
+
literal can be produced (unknown types, non-finite float).
|
|
95
|
+
"""
|
|
96
|
+
if isinstance(value, datetime):
|
|
97
|
+
inner = value.isoformat(sep=" ").replace("'", "''")
|
|
98
|
+
return f"'{inner}'"
|
|
99
|
+
if isinstance(value, date):
|
|
100
|
+
return f"'{value.isoformat()}'"
|
|
101
|
+
if isinstance(value, time):
|
|
102
|
+
return f"'{value.isoformat()}'"
|
|
103
|
+
if isinstance(value, bool):
|
|
104
|
+
return "TRUE" if value else "FALSE"
|
|
105
|
+
if isinstance(value, int):
|
|
106
|
+
return str(value)
|
|
107
|
+
if isinstance(value, float):
|
|
108
|
+
if not math.isfinite(value):
|
|
109
|
+
return None
|
|
110
|
+
return str(value)
|
|
111
|
+
if isinstance(value, Decimal):
|
|
112
|
+
return str(value)
|
|
113
|
+
if isinstance(value, str):
|
|
114
|
+
return "'" + value.replace("'", "''") + "'"
|
|
115
|
+
if isinstance(value, Enum):
|
|
116
|
+
return _python_scalar_to_sql_literal(value.value)
|
|
117
|
+
if isinstance(value, UUID):
|
|
118
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _default_expr(column: Any, _dialect: Dialect | None) -> str | None:
|
|
123
|
+
"""
|
|
124
|
+
Return a column default as a SQL expression string for DDL, or None.
|
|
125
|
+
|
|
126
|
+
Prefer ``server_default``; otherwise use ``default`` (Python-side). Callable
|
|
127
|
+
``.arg`` (e.g. ``default_factory``) yields None—no static DDL.
|
|
128
|
+
|
|
129
|
+
For ``.arg`` that is a SQLAlchemy :class:`~sqlalchemy.sql.elements.ClauseElement`
|
|
130
|
+
(typical for ``server_default=text(...)`` and many reflected defaults),
|
|
131
|
+
``str(.arg)`` is used so quoting matches SQLAlchemy's rendering.
|
|
132
|
+
|
|
133
|
+
For other ``.arg`` values, :func:`_python_scalar_to_sql_literal` produces quoted
|
|
134
|
+
literals. See docs/technical/05-model-column-defaults.md (PostgreSQL date bug).
|
|
135
|
+
|
|
136
|
+
Traceability: docs/requirements/01-functional.md (Schema parity: columns, defaults).
|
|
137
|
+
"""
|
|
74
138
|
default = getattr(column, "server_default", None) or getattr(column, "default", None)
|
|
75
139
|
if default is None:
|
|
76
140
|
return None
|
|
77
141
|
if hasattr(default, "arg") and default.arg is not None:
|
|
78
142
|
if callable(default.arg):
|
|
79
143
|
return None # Python-side default; no DDL expression
|
|
80
|
-
|
|
144
|
+
if isinstance(default.arg, ClauseElement):
|
|
145
|
+
return str(default.arg)
|
|
146
|
+
return _python_scalar_to_sql_literal(default.arg)
|
|
81
147
|
if hasattr(default, "text") and default.text is not None:
|
|
82
148
|
return default.text
|
|
83
149
|
return None
|
|
@@ -213,11 +213,20 @@ class ConformPlanBuilder:
|
|
|
213
213
|
if drop_sql:
|
|
214
214
|
steps.append(
|
|
215
215
|
AlterTableStep(
|
|
216
|
-
description=f"Drop column {col.name} from {name}",
|
|
216
|
+
description=f"Drop column `{col.name}` from `{name}`",
|
|
217
217
|
sql=drop_sql,
|
|
218
218
|
table_name=name,
|
|
219
219
|
)
|
|
220
220
|
)
|
|
221
|
+
else:
|
|
222
|
+
for col in table_diff.removed_columns:
|
|
223
|
+
skipped_steps.append(
|
|
224
|
+
SkippedStep(
|
|
225
|
+
description=f"Drop column `{col.name}` from `{name}`",
|
|
226
|
+
reason="Column drop blocked: allow_drop_extra_columns=False",
|
|
227
|
+
table_name=name,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
221
230
|
for col in table_diff.added_columns:
|
|
222
231
|
sql = self.dialect.add_column_sql(name, col)
|
|
223
232
|
steps.append(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbconform
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Synchronize database schema to models — document-driven project.
|
|
5
5
|
Author: Brian L. Pond
|
|
6
6
|
License: MIT
|
|
@@ -24,6 +24,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
25
25
|
Requires-Dist: ruff; extra == "dev"
|
|
26
26
|
Requires-Dist: sqlmodel; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
28
|
Requires-Dist: typer>=0.9; extra == "dev"
|
|
28
29
|
Provides-Extra: postgres
|
|
29
30
|
Requires-Dist: psycopg[binary]>=3; extra == "postgres"
|
|
@@ -144,6 +145,21 @@ else:
|
|
|
144
145
|
print(result.sql()) # Full DDL script
|
|
145
146
|
```
|
|
146
147
|
|
|
148
|
+
**View a human-readable summary:**
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
result.print_summary()
|
|
152
|
+
# Output:
|
|
153
|
+
# ConformPlan: 2 steps, 0 extra tables, 1 skipped steps
|
|
154
|
+
# Steps:
|
|
155
|
+
# - Add column price to product
|
|
156
|
+
# - Create table cart
|
|
157
|
+
# Skipped steps:
|
|
158
|
+
# - Drop column legacy_field from product (reason: Column drop blocked: allow_drop_extra_columns=False)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The summary shows planned steps, extra tables (in DB but not in models), and skipped steps (drift that requires opt-in to fix).
|
|
162
|
+
|
|
147
163
|
### 3. Apply changes
|
|
148
164
|
|
|
149
165
|
```python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|