tablemaster 2.1.7__tar.gz → 2.1.9__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.
Files changed (37) hide show
  1. {tablemaster-2.1.7 → tablemaster-2.1.9}/PKG-INFO +14 -2
  2. {tablemaster-2.1.7 → tablemaster-2.1.9}/README.md +13 -1
  3. {tablemaster-2.1.7 → tablemaster-2.1.9}/pyproject.toml +1 -1
  4. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/cli.py +9 -2
  5. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/__init__.py +2 -1
  6. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/diff.py +27 -4
  7. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/init.py +8 -0
  8. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/loader.py +43 -1
  9. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/PKG-INFO +14 -2
  10. {tablemaster-2.1.7 → tablemaster-2.1.9}/tests/test_schema_core.py +137 -1
  11. {tablemaster-2.1.7 → tablemaster-2.1.9}/LICENSE +0 -0
  12. {tablemaster-2.1.7 → tablemaster-2.1.9}/setup.cfg +0 -0
  13. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/__init__.py +0 -0
  14. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/__main__.py +0 -0
  15. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/config.py +0 -0
  16. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/database.py +0 -0
  17. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/feishu.py +0 -0
  18. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/gspread.py +0 -0
  19. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/local.py +0 -0
  20. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/apply.py +0 -0
  21. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/dialects/__init__.py +0 -0
  22. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/dialects/base.py +0 -0
  23. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/dialects/mysql.py +0 -0
  24. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/dialects/postgresql.py +0 -0
  25. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/dialects/tidb.py +0 -0
  26. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/introspect.py +0 -0
  27. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/models.py +0 -0
  28. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/plan.py +0 -0
  29. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/schema/pull.py +0 -0
  30. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/sync.py +0 -0
  31. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster/utils.py +0 -0
  32. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/SOURCES.txt +0 -0
  33. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/dependency_links.txt +0 -0
  34. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/entry_points.txt +0 -0
  35. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/requires.txt +0 -0
  36. {tablemaster-2.1.7 → tablemaster-2.1.9}/tablemaster.egg-info/top_level.txt +0 -0
  37. {tablemaster-2.1.7 → tablemaster-2.1.9}/tests/test_error_visibility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablemaster
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Summary: tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API.
5
5
  Author-email: Livid <livid.su@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/ilivid/tablemaster
@@ -220,6 +220,8 @@ tablemaster schema apply mydb --cfg-path ./cfg.yaml
220
220
  tablemaster schema pull mydb --cfg-path ./cfg.yaml
221
221
  ```
222
222
 
223
+ After `tablemaster init`, each `schema/<connection>/` directory includes `_ignore_tables.yaml` by default.
224
+
223
225
  Example schema file:
224
226
 
225
227
  ```yaml
@@ -238,6 +240,16 @@ indexes:
238
240
  unique: true
239
241
  ```
240
242
 
243
+ Ignore specific tables from schema diff by editing `_ignore_tables.yaml` (or `_ignore_tables.yml`) under `schema/<connection>/`:
244
+
245
+ ```yaml
246
+ tables:
247
+ - ods_orders_archive
248
+ - tmp_legacy_users
249
+ ```
250
+
251
+ Tables listed in this file are excluded from schema comparison, so they do not produce `plan` actions or warnings.
252
+
241
253
  ## CLI
242
254
 
243
255
  `tablemaster` now ships with a built-in CLI:
@@ -271,7 +283,7 @@ CLI command groups:
271
283
  - `db query <sql>`: Run SQL with `--cfg-key`; use `--limit` to control stdout preview and `--output` to export full result as CSV.
272
284
  - `local read <pattern>`: Read one local CSV/Excel match and print preview; use `--det-header/--no-det-header` to control header detection.
273
285
  - `config list`: List top-level keys from config.
274
- - `init`: Bootstrap `cfg.yaml` and `schema/<connection>/` scaffold in current directory.
286
+ - `init`: Bootstrap `cfg.yaml`, `schema/<connection>/`, and `_ignore_tables.yaml` scaffold in current directory.
275
287
  - `schema plan <connection>`: Compare YAML schema and live DB, print/apply-safe plan.
276
288
  - `schema apply <connection>`: Execute DDL actions from generated or saved plan.
277
289
  - `schema pull <connection>`: Generate YAML schema files from live DB tables.
@@ -185,6 +185,8 @@ tablemaster schema apply mydb --cfg-path ./cfg.yaml
185
185
  tablemaster schema pull mydb --cfg-path ./cfg.yaml
186
186
  ```
187
187
 
188
+ After `tablemaster init`, each `schema/<connection>/` directory includes `_ignore_tables.yaml` by default.
189
+
188
190
  Example schema file:
189
191
 
190
192
  ```yaml
@@ -203,6 +205,16 @@ indexes:
203
205
  unique: true
204
206
  ```
205
207
 
208
+ Ignore specific tables from schema diff by editing `_ignore_tables.yaml` (or `_ignore_tables.yml`) under `schema/<connection>/`:
209
+
210
+ ```yaml
211
+ tables:
212
+ - ods_orders_archive
213
+ - tmp_legacy_users
214
+ ```
215
+
216
+ Tables listed in this file are excluded from schema comparison, so they do not produce `plan` actions or warnings.
217
+
206
218
  ## CLI
207
219
 
208
220
  `tablemaster` now ships with a built-in CLI:
@@ -236,7 +248,7 @@ CLI command groups:
236
248
  - `db query <sql>`: Run SQL with `--cfg-key`; use `--limit` to control stdout preview and `--output` to export full result as CSV.
237
249
  - `local read <pattern>`: Read one local CSV/Excel match and print preview; use `--det-header/--no-det-header` to control header detection.
238
250
  - `config list`: List top-level keys from config.
239
- - `init`: Bootstrap `cfg.yaml` and `schema/<connection>/` scaffold in current directory.
251
+ - `init`: Bootstrap `cfg.yaml`, `schema/<connection>/`, and `_ignore_tables.yaml` scaffold in current directory.
240
252
  - `schema plan <connection>`: Compare YAML schema and live DB, print/apply-safe plan.
241
253
  - `schema apply <connection>`: Execute DDL actions from generated or saved plan.
242
254
  - `schema pull <connection>`: Generate YAML schema files from live DB tables.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tablemaster"
7
- version = "2.1.7"
7
+ version = "2.1.9"
8
8
  description = "tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -43,9 +43,10 @@ def _build_plan(
43
43
  from .schema.dialects import get_dialect
44
44
  from .schema.diff import generate_plan
45
45
  from .schema.introspect import introspect_tables
46
- from .schema.loader import load_schema_definitions
46
+ from .schema.loader import load_ignored_tables, load_schema_definitions
47
47
 
48
48
  db_cfg = _load_named_cfg(cfg_path, connection)
49
+ ignored_tables = load_ignored_tables(connection=connection, root_dir=schema_dir)
49
50
  desired = load_schema_definitions(connection=connection, root_dir=schema_dir, table=table)
50
51
  actual = introspect_tables(
51
52
  db_cfg,
@@ -53,7 +54,13 @@ def _build_plan(
53
54
  schema_name=getattr(desired[0], 'schema_name', None) if desired else None,
54
55
  )
55
56
  dialect = get_dialect(getattr(db_cfg, 'db_type', 'mysql'))
56
- plan = generate_plan(connection_name=connection, desired=desired, actual=actual, dialect=dialect)
57
+ plan = generate_plan(
58
+ connection_name=connection,
59
+ desired=desired,
60
+ actual=actual,
61
+ dialect=dialect,
62
+ ignored_tables=ignored_tables,
63
+ )
57
64
  return db_cfg, plan
58
65
 
59
66
 
@@ -2,7 +2,7 @@ from .apply import ApplyResult, apply_plan
2
2
  from .diff import generate_plan
3
3
  from .init import init_scaffold
4
4
  from .introspect import introspect_tables
5
- from .loader import load_schema_definitions
5
+ from .loader import load_ignored_tables, load_schema_definitions
6
6
  from .models import (
7
7
  ActualColumn,
8
8
  ActualTable,
@@ -25,6 +25,7 @@ __all__ = [
25
25
  'Plan',
26
26
  'ApplyResult',
27
27
  'load_schema_definitions',
28
+ 'load_ignored_tables',
28
29
  'introspect_tables',
29
30
  'generate_plan',
30
31
  'render_plan',
@@ -1,12 +1,33 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
4
+
3
5
  from .dialects.base import BaseDialect
4
6
  from .models import ActualTable, ColumnDef, Plan, PlanAction, TableDef
5
7
 
6
8
 
7
- def _norm_default(value: str | None) -> str | None:
9
+ def _strip_outer_parens(value: str) -> str:
10
+ result = value.strip()
11
+ while result.startswith('(') and result.endswith(')'):
12
+ result = result[1:-1].strip()
13
+ return result
14
+
15
+
16
+ def _normalize_pg_default(value: str) -> str:
17
+ normalized = _strip_outer_parens(value)
18
+ # PostgreSQL introspection often returns defaults like: 'N'::bpchar
19
+ # or ('unknown'::character varying). Strip trailing casts for comparison.
20
+ normalized = re.sub(r"::[a-zA-Z_][a-zA-Z0-9_\[\]\.\s]*$", '', normalized).strip()
21
+ if normalized.startswith("'") and normalized.endswith("'") and len(normalized) >= 2:
22
+ normalized = normalized[1:-1].replace("''", "'")
23
+ return normalized.upper()
24
+
25
+
26
+ def _norm_default(value: str | None, dialect: BaseDialect) -> str | None:
8
27
  if value is None:
9
28
  return None
29
+ if dialect.__class__.__name__ == 'PostgreSQLDialect':
30
+ return _normalize_pg_default(value)
10
31
  return value.strip().strip("'").upper()
11
32
 
12
33
 
@@ -37,10 +58,12 @@ def generate_plan(
37
58
  desired: list[TableDef],
38
59
  actual: list[ActualTable],
39
60
  dialect: BaseDialect,
61
+ ignored_tables: set[str] | None = None,
40
62
  ) -> Plan:
41
63
  plan = Plan(connection=connection_name)
42
- desired_map = {t.table: t for t in desired}
43
- actual_map = {t.table: t for t in actual}
64
+ ignored = set(ignored_tables or [])
65
+ desired_map = {t.table: t for t in desired if t.table not in ignored}
66
+ actual_map = {t.table: t for t in actual if t.table not in ignored}
44
67
 
45
68
  for table_name, table_def in desired_map.items():
46
69
  current = actual_map.get(table_name)
@@ -161,7 +184,7 @@ def generate_plan(
161
184
  )
162
185
  )
163
186
 
164
- if _norm_default(desired_col.default) != _norm_default(actual_col.default):
187
+ if _norm_default(desired_col.default, dialect) != _norm_default(actual_col.default, dialect):
165
188
  plan.actions.append(
166
189
  _action(
167
190
  'ALTER_COLUMN_DEFAULT',
@@ -15,6 +15,10 @@ _CFG_TEMPLATE = """mydb:
15
15
  db_type: mysql
16
16
  """
17
17
 
18
+ _IGNORE_TABLES_TEMPLATE = """# Tables listed here are ignored by schema plan/apply compare.
19
+ tables: []
20
+ """
21
+
18
22
 
19
23
  def _is_db_entry(value) -> bool:
20
24
  return isinstance(value, dict) and 'host' in value and 'database' in value
@@ -67,6 +71,10 @@ def init_scaffold(
67
71
  if not keep_file.exists():
68
72
  keep_file.write_text('', encoding='utf-8')
69
73
  created_paths.append(str(keep_file))
74
+ ignore_file = conn_dir / '_ignore_tables.yaml'
75
+ if not ignore_file.exists():
76
+ ignore_file.write_text(_IGNORE_TABLES_TEMPLATE, encoding='utf-8')
77
+ created_paths.append(str(ignore_file))
70
78
 
71
79
  return {
72
80
  'cfg_path': str(cfg_file),
@@ -7,6 +7,8 @@ import yaml
7
7
 
8
8
  from .models import ColumnDef, IndexDef, TableDef
9
9
 
10
+ IGNORE_TABLES_FILENAMES = {'_ignore_tables.yaml', '_ignore_tables.yml'}
11
+
10
12
 
11
13
  def _coerce_default(value) -> Optional[str]:
12
14
  if value is None:
@@ -61,6 +63,43 @@ def parse_table_file(path: Path) -> TableDef:
61
63
  )
62
64
 
63
65
 
66
+ def _is_ignore_tables_file(path: Path) -> bool:
67
+ return path.name.lower() in IGNORE_TABLES_FILENAMES
68
+
69
+
70
+ def _parse_ignore_tables_file(path: Path) -> set[str]:
71
+ with path.open('r', encoding='utf-8') as f:
72
+ raw = yaml.safe_load(f) or {}
73
+
74
+ if isinstance(raw, dict):
75
+ tables = raw.get('tables', raw.get('ignore_tables', []))
76
+ elif isinstance(raw, list):
77
+ tables = raw
78
+ else:
79
+ raise ValueError(f'Ignore tables file root must be list/dict: {path}')
80
+
81
+ if not isinstance(tables, list):
82
+ raise ValueError(f'Ignore tables must be a list: {path}')
83
+ return {str(t).strip() for t in tables if str(t).strip()}
84
+
85
+
86
+ def load_ignored_tables(
87
+ connection: str,
88
+ root_dir: str | Path = 'schema',
89
+ ) -> set[str]:
90
+ root = Path(root_dir).resolve()
91
+ conn_dir = root / connection
92
+ if not conn_dir.exists() or not conn_dir.is_dir():
93
+ raise FileNotFoundError(f'Schema directory not found: {conn_dir}')
94
+
95
+ files = sorted(conn_dir.rglob('*.yaml')) + sorted(conn_dir.rglob('*.yml'))
96
+ ignored: set[str] = set()
97
+ for file in files:
98
+ if _is_ignore_tables_file(file):
99
+ ignored.update(_parse_ignore_tables_file(file))
100
+ return ignored
101
+
102
+
64
103
  def load_schema_definitions(
65
104
  connection: str,
66
105
  root_dir: str | Path = 'schema',
@@ -71,12 +110,15 @@ def load_schema_definitions(
71
110
  if not conn_dir.exists() or not conn_dir.is_dir():
72
111
  raise FileNotFoundError(f'Schema directory not found: {conn_dir}')
73
112
  files = sorted(conn_dir.rglob('*.yaml')) + sorted(conn_dir.rglob('*.yml'))
113
+ ignore_tables = load_ignored_tables(connection=connection, root_dir=root_dir)
74
114
  defs: list[TableDef] = []
75
115
  for file in files:
116
+ if _is_ignore_tables_file(file):
117
+ continue
76
118
  parsed = parse_table_file(file)
77
119
  if table and parsed.table != table:
78
120
  continue
79
121
  defs.append(parsed)
80
- if table and not defs:
122
+ if table and not defs and table not in ignore_tables:
81
123
  raise FileNotFoundError(f'Table schema not found under {conn_dir}: {table}')
82
124
  return defs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablemaster
3
- Version: 2.1.7
3
+ Version: 2.1.9
4
4
  Summary: tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API.
5
5
  Author-email: Livid <livid.su@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/ilivid/tablemaster
@@ -220,6 +220,8 @@ tablemaster schema apply mydb --cfg-path ./cfg.yaml
220
220
  tablemaster schema pull mydb --cfg-path ./cfg.yaml
221
221
  ```
222
222
 
223
+ After `tablemaster init`, each `schema/<connection>/` directory includes `_ignore_tables.yaml` by default.
224
+
223
225
  Example schema file:
224
226
 
225
227
  ```yaml
@@ -238,6 +240,16 @@ indexes:
238
240
  unique: true
239
241
  ```
240
242
 
243
+ Ignore specific tables from schema diff by editing `_ignore_tables.yaml` (or `_ignore_tables.yml`) under `schema/<connection>/`:
244
+
245
+ ```yaml
246
+ tables:
247
+ - ods_orders_archive
248
+ - tmp_legacy_users
249
+ ```
250
+
251
+ Tables listed in this file are excluded from schema comparison, so they do not produce `plan` actions or warnings.
252
+
241
253
  ## CLI
242
254
 
243
255
  `tablemaster` now ships with a built-in CLI:
@@ -271,7 +283,7 @@ CLI command groups:
271
283
  - `db query <sql>`: Run SQL with `--cfg-key`; use `--limit` to control stdout preview and `--output` to export full result as CSV.
272
284
  - `local read <pattern>`: Read one local CSV/Excel match and print preview; use `--det-header/--no-det-header` to control header detection.
273
285
  - `config list`: List top-level keys from config.
274
- - `init`: Bootstrap `cfg.yaml` and `schema/<connection>/` scaffold in current directory.
286
+ - `init`: Bootstrap `cfg.yaml`, `schema/<connection>/`, and `_ignore_tables.yaml` scaffold in current directory.
275
287
  - `schema plan <connection>`: Compare YAML schema and live DB, print/apply-safe plan.
276
288
  - `schema apply <connection>`: Execute DDL actions from generated or saved plan.
277
289
  - `schema pull <connection>`: Generate YAML schema files from live DB tables.
@@ -5,12 +5,23 @@ import unittest
5
5
  from tablemaster.schema.dialects.mysql import MySQLDialect
6
6
  from tablemaster.schema.dialects.postgresql import PostgreSQLDialect
7
7
  from tablemaster.schema.diff import generate_plan
8
- from tablemaster.schema.loader import load_schema_definitions
8
+ from tablemaster.schema.init import init_scaffold
9
+ from tablemaster.schema.loader import load_ignored_tables, load_schema_definitions
9
10
  from tablemaster.schema.models import ActualColumn, ActualTable
10
11
  from tablemaster.schema.pull import write_pulled_schema
11
12
 
12
13
 
13
14
  class SchemaCoreTests(unittest.TestCase):
15
+ def test_init_creates_ignore_tables_template(self):
16
+ with TemporaryDirectory() as td:
17
+ root = Path(td)
18
+ result = init_scaffold(base_dir=root)
19
+ ignore_path = root / 'schema' / 'mydb' / '_ignore_tables.yaml'
20
+ self.assertTrue(ignore_path.exists())
21
+ content = ignore_path.read_text(encoding='utf-8')
22
+ self.assertIn('tables: []', content)
23
+ self.assertIn(str(ignore_path), result['created_paths'])
24
+
14
25
  def test_loader_reads_yaml(self):
15
26
  with TemporaryDirectory() as td:
16
27
  root = Path(td)
@@ -34,6 +45,40 @@ class SchemaCoreTests(unittest.TestCase):
34
45
  self.assertEqual('orders', tables[0].table)
35
46
  self.assertEqual('id', tables[0].columns[0].name)
36
47
 
48
+ def test_loader_reads_ignored_tables_file(self):
49
+ with TemporaryDirectory() as td:
50
+ root = Path(td)
51
+ schema_dir = root / 'schema' / 'mydb'
52
+ schema_dir.mkdir(parents=True, exist_ok=True)
53
+ (schema_dir / '_ignore_tables.yaml').write_text(
54
+ '\n'.join(
55
+ [
56
+ 'tables:',
57
+ ' - orders',
58
+ ' - legacy_users',
59
+ ]
60
+ ),
61
+ encoding='utf-8',
62
+ )
63
+ (schema_dir / 'orders.yaml').write_text(
64
+ '\n'.join(
65
+ [
66
+ 'table: orders',
67
+ 'columns:',
68
+ ' - name: id',
69
+ ' type: BIGINT',
70
+ ' primary_key: true',
71
+ ' nullable: false',
72
+ ]
73
+ ),
74
+ encoding='utf-8',
75
+ )
76
+ ignored = load_ignored_tables(connection='mydb', root_dir=root / 'schema')
77
+ tables = load_schema_definitions(connection='mydb', root_dir=root / 'schema')
78
+ self.assertEqual({'orders', 'legacy_users'}, ignored)
79
+ self.assertEqual(1, len(tables))
80
+ self.assertEqual('orders', tables[0].table)
81
+
37
82
  def test_diff_emits_add_column_and_warning(self):
38
83
  with TemporaryDirectory() as td:
39
84
  root = Path(td)
@@ -244,6 +289,97 @@ class SchemaCoreTests(unittest.TestCase):
244
289
  )
245
290
  self.assertTrue(any(a.action == 'ADD_PRIMARY_KEY' for a in plan.actions))
246
291
 
292
+ def test_postgresql_default_literal_with_cast_not_repeated(self):
293
+ with TemporaryDirectory() as td:
294
+ root = Path(td)
295
+ schema_dir = root / 'schema' / 'mydb'
296
+ schema_dir.mkdir(parents=True, exist_ok=True)
297
+ (schema_dir / 'orders.yaml').write_text(
298
+ '\n'.join(
299
+ [
300
+ 'table: orders',
301
+ 'columns:',
302
+ ' - name: archive',
303
+ ' type: CHAR(1)',
304
+ ' nullable: false',
305
+ ' default: "\'N\'"',
306
+ ]
307
+ ),
308
+ encoding='utf-8',
309
+ )
310
+ desired = load_schema_definitions(connection='mydb', root_dir=root / 'schema')
311
+ actual = [
312
+ ActualTable(
313
+ table='orders',
314
+ columns=[
315
+ ActualColumn(
316
+ name='archive',
317
+ type='character(1)',
318
+ nullable=False,
319
+ default="'N'::bpchar",
320
+ comment=None,
321
+ )
322
+ ],
323
+ indexes=[],
324
+ )
325
+ ]
326
+ plan = generate_plan('mydb', desired, actual, PostgreSQLDialect())
327
+ self.assertFalse(any(a.action == 'ALTER_COLUMN_DEFAULT' for a in plan.actions))
328
+
329
+ def test_diff_ignored_table_has_no_actions_or_warnings(self):
330
+ with TemporaryDirectory() as td:
331
+ root = Path(td)
332
+ schema_dir = root / 'schema' / 'mydb'
333
+ schema_dir.mkdir(parents=True, exist_ok=True)
334
+ (schema_dir / '_ignore_tables.yaml').write_text(
335
+ '\n'.join(
336
+ [
337
+ 'tables:',
338
+ ' - orders',
339
+ ]
340
+ ),
341
+ encoding='utf-8',
342
+ )
343
+ (schema_dir / 'orders.yaml').write_text(
344
+ '\n'.join(
345
+ [
346
+ 'table: orders',
347
+ 'columns:',
348
+ ' - name: id',
349
+ ' type: BIGINT',
350
+ ' nullable: false',
351
+ ]
352
+ ),
353
+ encoding='utf-8',
354
+ )
355
+ desired = load_schema_definitions(connection='mydb', root_dir=root / 'schema')
356
+ ignored = load_ignored_tables(connection='mydb', root_dir=root / 'schema')
357
+ actual = [
358
+ ActualTable(
359
+ table='orders',
360
+ columns=[
361
+ ActualColumn(
362
+ name='id',
363
+ type='BIGINT',
364
+ nullable=True,
365
+ default=None,
366
+ comment=None,
367
+ ),
368
+ ActualColumn(
369
+ name='legacy_col',
370
+ type='VARCHAR(32)',
371
+ nullable=True,
372
+ default=None,
373
+ comment=None,
374
+ ),
375
+ ],
376
+ indexes=[],
377
+ )
378
+ ]
379
+ plan = generate_plan('mydb', desired, actual, MySQLDialect(), ignored_tables=ignored)
380
+ self.assertEqual([], plan.actions)
381
+ self.assertEqual([], plan.warnings)
382
+
247
383
 
248
384
  if __name__ == '__main__':
249
385
  unittest.main()
File without changes
File without changes