ch-migrate-cli 0.5.0__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,448 @@
1
+ Metadata-Version: 2.5
2
+ Name: ch-migrate-cli
3
+ Version: 0.5.0
4
+ Summary: SQL-first ClickHouse schema migrations across environments: the ch-migrate command
5
+ Project-URL: Homepage, https://github.com/DRYCodeWorks/ch-migrate
6
+ Project-URL: Repository, https://github.com/DRYCodeWorks/ch-migrate
7
+ Project-URL: Issues, https://github.com/DRYCodeWorks/ch-migrate/issues
8
+ Project-URL: Guide, https://www.drycodeworks.com/articles/dev-guides/clickhouse-migrations-with-alembic
9
+ Author: Dan Young
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: alembic,ch-migrate,clickhouse,clickhouse-cloud,database,migrations,schema,schema-migrations
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Database
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: alembic>=1.14.0
26
+ Requires-Dist: boto3>=1.42.24
27
+ Requires-Dist: click>=8.1.0
28
+ Requires-Dist: clickhouse-connect>=0.7.0
29
+ Requires-Dist: clickhouse-sqlalchemy>=0.3.0
30
+ Requires-Dist: python-dotenv>=1.0.0
31
+ Requires-Dist: pyyaml>=6.0
32
+ Requires-Dist: rich>=13.0.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: black>=23.0.0; extra == 'dev'
35
+ Requires-Dist: isort>=5.12.0; extra == 'dev'
36
+ Requires-Dist: mypy>=1.5.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
38
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
39
+ Requires-Dist: types-pyyaml; extra == 'dev'
40
+ Provides-Extra: postgres
41
+ Requires-Dist: psycopg2-binary>=2.9.0; extra == 'postgres'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # ch-migrate
45
+
46
+ ## What it is
47
+
48
+ `ch-migrate` manages SQL-first ClickHouse migrations across environments: author SQL files, bootstrap databases and roles, inspect migrations and dependencies, and compare schema snapshots. Alembic owns revision history; the database dialect owns DDL compilation. This operational layer complements ClickHouse's official Alembic integration rather than replacing it. This development line still uses `clickhouse-sqlalchemy` for Alembic connections; it does not imply an endorsement from ClickHouse.
49
+
50
+ Background: [ClickHouse migrations with Alembic](https://www.drycodeworks.com/articles/dev-guides/clickhouse-migrations-with-alembic).
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ uv tool install ch-migrate-cli
56
+ # Or:
57
+ pip install ch-migrate-cli
58
+ ch-migrate --version
59
+ ```
60
+
61
+ The command is `ch-migrate`, the PyPI package is `ch-migrate-cli` (PyPI treats `ch-migrate` as the same name as the existing, unrelated `chmigrate`), and migrations import from `ch_migrate`. Versions up to 0.4.1 were published as `clickhouse-alembic` with the import package `clickhouse_alembic`; that import still works with a deprecation warning until 1.0, so existing migration files keep running. Replace `clickhouse_alembic` with `ch_migrate` in them when convenient, and run `ch-migrate upgrade-env` to refresh `migrations/env.py`.
62
+
63
+ To switch an existing install, remove the old package first, because both install the `ch-migrate` command and the `clickhouse_alembic` folder: `uv tool uninstall clickhouse-alembic && uv tool install ch-migrate-cli`, or `pip uninstall clickhouse-alembic && pip install ch-migrate-cli`. In a project that lists `clickhouse-alembic` as a dependency, replace it with `ch-migrate-cli`.
64
+
65
+ This README describes the source checkout, which may be ahead of PyPI. To try an unreleased checkout locally, run `uv tool install .` in the repository. For development without installing a global tool, use `uv run --locked ch-migrate`.
66
+
67
+ ## Quick start
68
+
69
+ Use a dedicated ClickHouse test server. The example uses local HTTP; replace the host and port with your server's address. For HTTPS, set `secure: true` and the HTTPS port (usually `8443`). Never run a trial migration against a shared or production database.
70
+
71
+ ### 1. Initialize
72
+
73
+ ```bash
74
+ mkdir my-clickhouse-project
75
+ cd my-clickhouse-project
76
+ ch-migrate init --name my_project
77
+ ```
78
+
79
+ ### 2. Configure the server
80
+
81
+ Replace `config.yaml` with:
82
+
83
+ ```yaml
84
+ project:
85
+ name: my_project
86
+
87
+ defaults:
88
+ port: 8123
89
+ secure: false
90
+ admin_user: default
91
+
92
+ environments:
93
+ dev:
94
+ host: 127.0.0.1
95
+ database: my_project_dev
96
+ migration_user: migration_dev
97
+ ```
98
+
99
+ Create `.env.local` in this project directory. Replace both values: the admin password is your server's existing password; the migration password is the password to give the new migration user.
100
+
101
+ ```dotenv
102
+ CH_DEV_ADMIN_PASSWORD=your-admin-password
103
+ CH_DEV_MIGRATION_PASSWORD=your-migration-password
104
+ ```
105
+
106
+ Keep `.env.local` out of git. `init` creates an ignore entry for it.
107
+
108
+ ### 3. Bootstrap and create a migration
109
+
110
+ ```bash
111
+ ch-migrate bootstrap dev --dry-run
112
+ ch-migrate bootstrap dev
113
+ ch-migrate new dev add_status --table logs
114
+ ```
115
+
116
+ The last command creates a revision plus two files under `migrations/sql/history/tables/logs/`. Their names include a timestamp and revision ID. Replace the contents of the generated `.up.sql` with:
117
+
118
+ ```sql
119
+ CREATE TABLE IF NOT EXISTS {db}.logs (id UInt64)
120
+ ENGINE = MergeTree ORDER BY id;
121
+ ALTER TABLE {db}.logs ADD COLUMN IF NOT EXISTS status String;
122
+ ```
123
+
124
+ Replace the contents of its `.down.sql` with:
125
+
126
+ ```sql
127
+ DROP TABLE IF EXISTS {db}.logs;
128
+ ```
129
+
130
+ Do not edit the generated revision file. The example downgrade drops the table and its data; only use it in this empty test project.
131
+
132
+ ### 4. Apply, inspect, and revert
133
+
134
+ ```bash
135
+ ch-migrate up dev
136
+ ch-migrate status dev
137
+ ch-migrate history dev
138
+ ch-migrate down dev
139
+ ```
140
+
141
+ After `up`, the `logs` table has `id` and `status` columns and status reports one applied revision. After `down`, the example table is gone.
142
+
143
+ ## Concepts
144
+
145
+ ### Project layout
146
+
147
+ ```text
148
+ project/
149
+ ├── config.yaml
150
+ ├── .env.local # Secrets, ignored by git
151
+ ├── alembic.ini
152
+ └── migrations/
153
+ ├── env.py # Generated Alembic environment
154
+ ├── script.py.mako
155
+ ├── versions/ # Revision graph; generated Python adapters
156
+ └── sql/
157
+ ├── bootstrap/ # Optional bootstrap SQL
158
+ └── history/
159
+ ├── tables/<name>/
160
+ ├── views/<name>/
161
+ ├── dictionaries/<name>/
162
+ └── other/ # No named object
163
+ ```
164
+
165
+ `new` creates `<YYYY_MM_DD_HHMM>_<revision>_<slug>.up.sql` and `.down.sql`. The message slug is at most 40 characters. Object directories are created when needed. Only one of `--table`, `--view`, and `--dict` may be supplied.
166
+
167
+ ### SQL files and placeholders
168
+
169
+ `run_sql` runs one statement per request. Semicolons inside strings, quoted identifiers, comments, or heredocs do not split statements. Empty or comment-only files fail instead of recording an unfilled migration as applied. Execution stops at the first failed statement; ClickHouse DDL is not transactional, so earlier changes remain.
170
+
171
+ | Placeholder | Value |
172
+ |---|---|
173
+ | `{db}` | Environment database |
174
+ | `{cluster}` | Configured cluster, or an empty string |
175
+ | `{on_cluster}` | `ON CLUSTER <cluster>`, or an empty string |
176
+
177
+ Keyword arguments to `run_sql` add or override substitutions. All other braces remain literal, including JSON and ClickHouse parameters such as `{id:UInt64}`. Doubled braces are not format escapes. Write statements that are safe to repeat where possible, such as `CREATE ... IF NOT EXISTS` and `DROP ... IF EXISTS`.
178
+
179
+ To render without executing, set `CH_ENVIRONMENT` and run `alembic upgrade head --sql`. Existing projects need `ch-migrate upgrade-env` for the offline version-table and literal-rendering fixes. The package requires Alembic 1.14 or later for that extension point.
180
+
181
+ ### Irreversible migrations
182
+
183
+ ```bash
184
+ ch-migrate new dev drop_legacy --table logs --irreversible "Drops legacy data"
185
+ ```
186
+
187
+ This creates only an upgrade file. Its revision has an `irreversible` reason and raises `IrreversibleMigration` in its downgrade. An empty reason is rejected.
188
+
189
+ `down` reads markers statically, without importing migration files. It refuses an entire known range if any revision is irreversible: no preceding reversible downgrade runs first. It understands `-N` on linear history, full or unique-prefix IDs, and `base`. If a range is unknown, including a relative target across a merge point, it prints a note and relies on the migration's exception. Direct Alembic calls rely on the same backstop.
190
+
191
+ There is no override flag. To revert past a marked revision, implement its downgrade and remove the marker in a reviewed change. `irreversible = True` is accepted as "(no reason given)".
192
+
193
+ ### Python migrations
194
+
195
+ Use `new --python` for logic that cannot be expressed as SQL files. It retains the Python template and, with an object option, a single SQL history file. Existing Python migrations continue to work.
196
+
197
+ ```python
198
+ from ch_migrate import get_db, run_sql
199
+
200
+ def upgrade():
201
+ run_sql("history/tables/logs/001_add_status.up.sql", db=get_db())
202
+ ```
203
+
204
+ `get_db()` returns the environment database. `read_sql(path, **values)` still returns a string using Python `str.format`; unlike `run_sql`, callers must escape literal braces and execute the returned SQL themselves. For a single-statement file, the existing pattern remains valid:
205
+
206
+ ```python
207
+ from alembic import op
208
+ from ch_migrate import get_db, read_sql
209
+
210
+ def upgrade():
211
+ op.execute(read_sql("history/tables/users/001_create.sql", db=get_db()))
212
+ ```
213
+
214
+ A hand-written irreversible Python revision uses both the marker and backstop:
215
+
216
+ ```python
217
+ from ch_migrate import IrreversibleMigration
218
+
219
+ irreversible = "Drops legacy data"
220
+
221
+ def downgrade():
222
+ raise IrreversibleMigration(revision, irreversible)
223
+ ```
224
+
225
+ ### Exchange and dictionary patterns
226
+
227
+ `new --exchange --table NAME` generates the existing shadow-table, copy, exchange, and drop scaffold. Coordinate or pause writers: this copy-and-swap pattern alone does not preserve inserts arriving during the copy. It is not an online-rebuild guarantee. Review the generated SQL and column mapping before applying it. The scaffold is marked irreversible because it drops the old table.
228
+
229
+ For a controlled change, the underlying pattern is:
230
+
231
+ ```sql
232
+ CREATE TABLE IF NOT EXISTS {db}.users_shadow
233
+ (id UInt64, email String, phone String) ENGINE = MergeTree ORDER BY id;
234
+ INSERT INTO {db}.users_shadow SELECT id, email, '' FROM {db}.users;
235
+ EXCHANGE TABLES {db}.users AND {db}.users_shadow;
236
+ DROP TABLE IF EXISTS {db}.users_shadow;
237
+ ```
238
+
239
+ `EXCHANGE TABLES` requires a supporting database engine. Neither the copy nor the exchange is automatically idempotent.
240
+
241
+ The dictionary helper retains automatic SELECT grants for a configured dictionary reader:
242
+
243
+ ```python
244
+ from ch_migrate import create_dictionary
245
+
246
+ def upgrade():
247
+ create_dictionary("history/dictionaries/dict_users/001_create.sql")
248
+ ```
249
+
250
+ ## Command reference
251
+
252
+ Every command accepts `--help`. Top-level `ch-migrate --version` reports the installed package version. `ENV` below names an entry in `config.yaml`.
253
+
254
+ Output lines start with `→` for a step, `✓` for a result, `!` for a warning and `✗` for an error; warnings and errors go to stderr. Colour is dropped when output is not a terminal or `NO_COLOR` is set, and lines are never wrapped, so paths and SQL can be copied or grepped.
255
+
256
+ ### `init`
257
+
258
+ `ch-migrate init [PATH] [-n NAME]` initializes the current directory by default. `-n/--name` sets the project name; otherwise it uses the directory name.
259
+
260
+ Example: `ch-migrate init analytics --name analytics`
261
+
262
+ ### `bootstrap`
263
+
264
+ `ch-migrate bootstrap ENV [--dry-run] [-v]` creates the database, roles, and configured users. `--dry-run` prints SQL without executing it; `-v/--verbose` prints statements during execution. Requires admin and migration credentials.
265
+
266
+ Example: `ch-migrate bootstrap dev --dry-run`
267
+
268
+ ### `new`
269
+
270
+ `ch-migrate new ENV NAME [--table T | --view V | --dict D] [--irreversible REASON | --python | --exchange]` creates SQL-first migrations by default. Object-option aliases are `-t`, `-v`, and `-d`. `--python` keeps the Python template. `--exchange` requires `--table`. The three authoring-mode options are mutually exclusive, and conflicts fail before a revision is written.
271
+
272
+ Example: `ch-migrate new dev add_status --table logs`
273
+
274
+ ### `up`
275
+
276
+ `ch-migrate up ENV [-r REV] [--skip-mv-check] [--verbose]` applies migrations to `head` by default, printing one line per migration. `-r/--revision` selects a target. `--skip-mv-check` bypasses materialized-view declaration validation; use it only after reviewing those findings. If a migration fails, `up` names it, the SQL file, the statement and its line, and ClickHouse's error; `--verbose` adds the Python traceback.
277
+
278
+ Example: `ch-migrate up dev --revision abc123`
279
+
280
+ ### `down`
281
+
282
+ `ch-migrate down ENV [-r REV] [--verbose]` reverts one revision by default (`-1`). `-r/--revision` accepts another target. Known ranges containing irreversible revisions are refused. Failures are reported as for `up`.
283
+
284
+ Example: `ch-migrate down dev --revision base`
285
+
286
+ ### `status`
287
+
288
+ `ch-migrate status ENV` shows connection information, applied/pending counts, and head status, and names the `up` command when migrations are pending. No command-specific options. Exits 1 if it cannot reach the database.
289
+
290
+ Example: `ch-migrate status dev`
291
+
292
+ ### `history`
293
+
294
+ `ch-migrate history ENV` displays the revision graph and applied state. No command-specific options.
295
+
296
+ Example: `ch-migrate history dev`
297
+
298
+ ### `lint`
299
+
300
+ `ch-migrate lint [ENV]` analyzes upgrade statements, not downgrade SQL. Without `ENV`, it checks every revision statically without credentials or a connection. With an environment, it checks only pending revisions and adds live size and dependency checks. If it cannot determine the pending set, it fails rather than silently checking a different scope. No command-specific options. Errors exit nonzero; warnings alone do not.
301
+
302
+ Example: `ch-migrate lint`
303
+
304
+ Findings name the project-relative SQL file and statement line. Inline Python SQL
305
+ points to its `op.execute` call. Extraction reads `run_sql`/`read_sql` file
306
+ references and literal or f-string `op.execute` arguments without importing
307
+ revisions. It preserves placeholders and adjacent comments; arbitrary Python
308
+ expressions are not evaluated. Materialized-view declaration and companion-grant
309
+ validation still uses the complete migration batch, with lint findings limited
310
+ to selected upgrade statements.
311
+
312
+ ### `deps`
313
+
314
+ `ch-migrate deps ENV [-v PATH]` reads the live materialized-view and dictionary dependency graph. `-v/--validate PATH` checks a SQL file against it.
315
+
316
+ Example: `ch-migrate deps dev --validate migrations/sql/history/tables/logs/change.up.sql`
317
+
318
+ ### `diff`
319
+
320
+ `ch-migrate diff ENV [-s PATH]` compares the live schema with the latest snapshot. `-s/--snapshot-dir PATH` chooses another snapshot. Exit code 0 means no drift; 1 means drift or an execution error.
321
+
322
+ Example: `ch-migrate diff dev --snapshot-dir migrations/sql/snapshots/20261002_120000`
323
+
324
+ ### `snapshot`
325
+
326
+ `ch-migrate snapshot ENV [-e GLOB] [-f GLOB]` writes CREATE statements to a timestamped snapshot directory. `-e/--exclude` and `-f/--filter` accept repeated glob patterns for excluded and included objects.
327
+
328
+ Example: `ch-migrate snapshot dev --exclude 'temp_*' --filter 'logs*'`
329
+
330
+ ### `rebase`
331
+
332
+ `ch-migrate rebase ENV [--onto REV] [--dry-run]` rewrites dangling local revision branches onto the deployed head. `--onto` selects an explicit target; `--dry-run` previews changes. Review the preview before rewriting migration history; do not rewrite deployed revisions.
333
+
334
+ Example: `ch-migrate rebase dev --onto abc123 --dry-run`
335
+
336
+ ### `upgrade-env`
337
+
338
+ `ch-migrate upgrade-env` replaces `migrations/env.py` with the installed version and backs up the old file as `env.py.bak`. No command-specific options. Review and reapply local customizations from the backup.
339
+
340
+ Example: `ch-migrate upgrade-env`
341
+
342
+ ### `skill`
343
+
344
+ `ch-migrate skill [--user | --project]` installs the bundled Claude skill. `--user` is the default (`~/.claude/skills/ch-migrate/`); `--project` writes `./.claude/skills/ch-migrate/`.
345
+
346
+ Example: `ch-migrate skill --project`
347
+
348
+ ## Configuration
349
+
350
+ `defaults` are merged with each environment. Environment fields override defaults. The project name controls role names. A Cloud/HTTPS example:
351
+
352
+ ```yaml
353
+ project:
354
+ name: analytics
355
+
356
+ defaults:
357
+ port: 8443
358
+ secure: true
359
+ admin_user: default
360
+ # cluster: my_cluster
361
+ # dict_reader_name: dict_reader
362
+ # mcp_user_name: mcp_reader
363
+
364
+ environments:
365
+ dev:
366
+ host: your-service.clickhouse.cloud
367
+ database: analytics_dev
368
+ migration_user: migration_dev
369
+ aws_region: us-east-1
370
+ ssm:
371
+ admin_password: /analytics/dev/admin_password
372
+ migration_password: /analytics/credentials#password
373
+ ```
374
+
375
+ ### Secrets
376
+
377
+ Choose `.env.local` (or exported environment variables) or per-environment SSM paths. When an SSM path is configured, it is used for that secret. A `#key` suffix extracts a JSON key from the parameter. SSM access requires AWS credentials and permission to read the specified parameters; `aws_region` is optional.
378
+
379
+ | Variable | Purpose |
380
+ |---|---|
381
+ | `CH_<ENV>_MIGRATION_PASSWORD` | Required migration password |
382
+ | `CH_<ENV>_ADMIN_PASSWORD` | Admin password for bootstrap |
383
+ | `CH_<ENV>_DICT_READER_PASSWORD` | Password when a dictionary reader is configured |
384
+ | `CH_<ENV>_MCP_PASSWORD` | Password when a read-only MCP user is configured |
385
+
386
+ The legacy `CH_<ENV>_PASSWORD` remains supported. Never commit credentials or pass them in migration SQL that will be logged.
387
+
388
+ ### Hooks
389
+
390
+ Top-level hooks run SQL on the migration connection. `pre_migrate` runs before the migration batch; `post_migrate` runs after each revision. `{db}` is substituted. Hooks execute as SQLAlchemy text, not through the SQL-file splitter; supply one statement per entry. Hook SQL is logged, so do not put secrets in it.
391
+
392
+ ```yaml
393
+ hooks:
394
+ pre_migrate:
395
+ - "SELECT 1"
396
+ post_migrate:
397
+ - "SYSTEM RELOAD DICTIONARY {db}.dict_regions"
398
+ ```
399
+
400
+ Only configure the dictionary hook when that dictionary exists at every revision where the hook runs.
401
+
402
+ ### Lint configuration
403
+
404
+ Set rule severities to `error`, `warn`, or `off`. `mv_validation_cutoff` can exclude older revisions from materialized-view declaration checks.
405
+
406
+ ```yaml
407
+ lint:
408
+ large_table_threshold: 100000000
409
+ mv_validation_cutoff: "2026-01-01"
410
+ rules:
411
+ destructive_changes: warn
412
+ idempotency: warn
413
+ reserved_words: warn
414
+ ```
415
+
416
+ Review findings rather than treating a successful command as a guarantee that a migration is safe. DDL and mutations are not transactional.
417
+
418
+ ### Bootstrap roles and Cloud notes
419
+
420
+ Bootstrap creates `{project}_migration_role` for schema/data operations and introspection, including explicit `system.grants` access. Optional users add `{project}_readonly_role` (SELECT/SHOW) and `{project}_dict_role` (dictionary sources). Bootstrap uses explicit grants rather than `GRANT ALL` for Cloud compatibility.
421
+
422
+ Use standard table engine names such as `MergeTree` and `ReplacingMergeTree`; ClickHouse Cloud supplies its shared variants. Cloud usually uses HTTPS port `8443`; local HTTP usually uses `8123`.
423
+
424
+ ## Development
425
+
426
+ Run unit tests without starting Docker:
427
+
428
+ ```bash
429
+ uv run --locked pytest -q
430
+ ```
431
+
432
+ Run the opt-in real-server suite:
433
+
434
+ ```bash
435
+ uv run --locked pytest -q -m integration
436
+ ```
437
+
438
+ The fixture starts `clickhouse/clickhouse-server:26.3` in its own `chm-it-*` container on a random loopback port. Each test uses a separate database. Finalizers clean up on success, failure, and handled interrupts; a forced process kill cannot run finalizers. Docker-unavailable runs skip with a reason.
439
+
440
+ `CH_MIGRATE_IT_IMAGE` overrides the image tag. `CH_MIGRATE_IT_URL` selects a dedicated test server instead of starting Docker. It is an HTTP(S) URL with credentials supplied only through the environment. Tests create and drop databases there: never select a shared or production server, and never commit the URL.
441
+
442
+ ## License
443
+
444
+ MIT License — see [LICENSE](LICENSE).
445
+
446
+ ## Author
447
+
448
+ Dan Young
@@ -0,0 +1,36 @@
1
+ ch_migrate/__init__.py,sha256=lduH_Y-wVXo3j8WeU2wmy8wSPjosHZ75i7ARTc5gs74,1850
2
+ ch_migrate/authoring.py,sha256=ONLvcLO4H4yyH2KPCxom5KqG_wQMtbKOkFqZiWFPT7g,5691
3
+ ch_migrate/bootstrap.py,sha256=2s2d__EFmCANnkj1uLa_AMnfZ5GKISZ10GmAKFXCBdY,13857
4
+ ch_migrate/cli.py,sha256=Gws_oNQXqlFcyhqZXp5wb2YqCmzyqvd3qhzF86WL-_E,38563
5
+ ch_migrate/config.py,sha256=LmWcz0bzw5IoXy0knb1ritQl5R9z5ACCzI1Hx8ccKiU,3616
6
+ ch_migrate/connection.py,sha256=OZBycH9nIffgpy8XDseLpc6XJQHJpUe_aP3j_RPrFBE,2707
7
+ ch_migrate/deps.py,sha256=IGh-BFhTqujFMm8z-KJFPGVQqWvprvyg_CTS6AlEjxs,4082
8
+ ch_migrate/diff.py,sha256=IGQXACYW-ThUQQlie2O5dc4GMaP2-ZjoYlWVgYNYGnE,8094
9
+ ch_migrate/display.py,sha256=68Tu4-dxCotvDzcih_zwgA10IsuVCNWdauRZ_P-CgFI,15142
10
+ ch_migrate/downgrade.py,sha256=eUpB8ZyJWHAHVZgz0xsmIQe9Fsi1azWPm3Aes9cqEGU,3959
11
+ ch_migrate/env.py,sha256=Gk05NuXbwbAq9k8bRi8IxS0ERBSlngJWWO203h1-RFk,7401
12
+ ch_migrate/helpers.py,sha256=Ws-Abk5HbecOSPrVtdtAq8TSYNL1KVIBz8Dwa0prs7A,4557
13
+ ch_migrate/hooks.py,sha256=jFeV4G3ELYqZ0ztf4NJIZXsDKB3oqpEC2Xk2zlb2k_A,2267
14
+ ch_migrate/introspect.py,sha256=Ih9-qJxEs46TZaVl3zeSh1qbXqAugQE9IfM_3UFoBl0,23151
15
+ ch_migrate/lint.py,sha256=vSZ3nZYLZfuC8sR936UGq5EGJaYnKSepxh0RFuXIQA8,20859
16
+ ch_migrate/mv_validate.py,sha256=kFfVmExvL9BLev_nIMFuQMLQf2M3F_9jlZWY3XyITQ0,18167
17
+ ch_migrate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ ch_migrate/rebase.py,sha256=htQwOABI9wpkzzWe05UIpURx303RYd5_KScx76D_2Rs,9338
19
+ ch_migrate/runner.py,sha256=7iNEoU7INLSw3vBn-ZXW-6uxhqIYnmx7jkpemUKgtYE,7164
20
+ ch_migrate/scaffold.py,sha256=XJnS4bPonoYil1p206HDyc0UJM8Pn6rYxxcta86GeZI,8109
21
+ ch_migrate/secrets.py,sha256=wabD3ztI2QptVEmDrHWKRBrYrcBH3TivcnCfSlAfeSo,5524
22
+ ch_migrate/sql.py,sha256=q6jRK-Q5E3pp9Pg5rh_MwSfRhPLiTvjGpX-o_HDJggM,7741
23
+ ch_migrate/statements.py,sha256=_u-4bT40RZgALNij9qXc6IE2QQNDRegQ1JC5avt8Js0,5321
24
+ ch_migrate/ui.py,sha256=uc722-VrP-vinvtyJRwyA7fqgE1vvmgoxO8WO2QgfPc,2660
25
+ ch_migrate/skills/ch-migrate/SKILL.md,sha256=T-ojVvETRPtGqeB-vjFgUkHd3yzkPkIVycALhRW8Tb4,9354
26
+ ch_migrate/templates/bootstrap/init_users.sql,sha256=Xuu8y8q1_F0pqtcViKGGAMLK4Mu6PWaHNOZeQwKqb7s,2544
27
+ ch_migrate/templates/project/alembic.ini.template,sha256=IF75FJvNZCqMq6tDh8sqRwxbLuHLClYLHYpS2aYPyGc,742
28
+ ch_migrate/templates/project/config.yaml.template,sha256=8gQKUkU0JchMRYDM0b9nVXKq9Gg-R720nSXG_JuwPeA,1820
29
+ ch_migrate/templates/project/env.local.example.template,sha256=VCJAvprSzsshYzVmnYBfMtTx7QJwF8PRStzbaV6Bkpc,1088
30
+ ch_migrate/templates/project/script.py.mako.template,sha256=TrzyWw_jxV3x9y4IurdW5FFbq0NwYFAu07yLvtzfyPg,672
31
+ clickhouse_alembic/__init__.py,sha256=vUUhh83eSrGnxdDe4A4uJli35UAXTWhd-GoaF35xmCI,2025
32
+ ch_migrate_cli-0.5.0.dist-info/METADATA,sha256=trOy0gzLrXOF3xcygU9qZUeQGb7xm3iBMYlokGlriDc,20053
33
+ ch_migrate_cli-0.5.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
34
+ ch_migrate_cli-0.5.0.dist-info/entry_points.txt,sha256=Rj6R1ck-yDxRyU03fgWeQexz0u-s5mUv8KMnKwlexYI,51
35
+ ch_migrate_cli-0.5.0.dist-info/licenses/LICENSE,sha256=tCJ1mJyU60MYqdPDvF-E5lFzcloGJkHszr4qRp-MC8A,1069
36
+ ch_migrate_cli-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ch-migrate = ch_migrate.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DRYCodeWorks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,59 @@
1
+ """Deprecated alias for `ch_migrate`, the import name before 0.5. Removed in 1.0.
2
+
3
+ `from clickhouse_alembic import run_sql` and `from clickhouse_alembic.config import ...`
4
+ (as in env.py files generated before 0.5) keep working. Every `clickhouse_alembic.X`
5
+ is the same module object as `ch_migrate.X`, so classes and state are shared rather
6
+ than loaded twice.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import importlib
12
+ import importlib.abc
13
+ import importlib.util
14
+ import sys
15
+ import warnings
16
+ from types import FrameType, ModuleType
17
+ from typing import Any, Optional
18
+
19
+ _NEW = "ch_migrate"
20
+ _MESSAGE = (
21
+ "clickhouse_alembic was renamed to ch_migrate in 0.5; update this import. "
22
+ "The old name stops working in 1.0."
23
+ )
24
+
25
+
26
+ def __getattr__(name: str) -> Any:
27
+ return getattr(importlib.import_module(_NEW), name)
28
+
29
+
30
+ class _AliasFinder(importlib.abc.MetaPathFinder, importlib.abc.Loader):
31
+ """Resolve `clickhouse_alembic.X` to the already-importable `ch_migrate.X`."""
32
+
33
+ def find_spec(self, fullname: str, path: Any, target: Any = None) -> Any:
34
+ if not fullname.startswith(__name__ + "."):
35
+ return None
36
+ return importlib.util.spec_from_loader(fullname, self)
37
+
38
+ def create_module(self, spec: Any) -> ModuleType:
39
+ return importlib.import_module(_NEW + spec.name[len(__name__) :])
40
+
41
+ def exec_module(self, module: ModuleType) -> None:
42
+ pass # create_module returned the real, already-executed module
43
+
44
+
45
+ def _warn_at_importer() -> None:
46
+ """Point the warning at the file that wrote the old import, not at importlib."""
47
+ frame: Optional[FrameType] = sys._getframe(1)
48
+ while frame is not None and (
49
+ "importlib" in frame.f_code.co_filename or frame.f_code.co_filename == __file__
50
+ ):
51
+ frame = frame.f_back
52
+ if frame is None:
53
+ warnings.warn(_MESSAGE, FutureWarning, stacklevel=2)
54
+ else:
55
+ warnings.warn_explicit(_MESSAGE, FutureWarning, frame.f_code.co_filename, frame.f_lineno)
56
+
57
+
58
+ sys.meta_path.insert(0, _AliasFinder())
59
+ _warn_at_importer()