clickhouse-charon 1.0.2__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 (43) hide show
  1. clickhouse_charon-1.0.2/.github/PULL_REQUEST_TEMPLATE.md +16 -0
  2. clickhouse_charon-1.0.2/.github/workflows/ci.yml +40 -0
  3. clickhouse_charon-1.0.2/.github/workflows/release.yml +45 -0
  4. clickhouse_charon-1.0.2/.gitignore +18 -0
  5. clickhouse_charon-1.0.2/.python-version +1 -0
  6. clickhouse_charon-1.0.2/CHANGELOG.md +17 -0
  7. clickhouse_charon-1.0.2/LICENSE +21 -0
  8. clickhouse_charon-1.0.2/Makefile +24 -0
  9. clickhouse_charon-1.0.2/PKG-INFO +303 -0
  10. clickhouse_charon-1.0.2/README.md +276 -0
  11. clickhouse_charon-1.0.2/charon/__init__.py +3 -0
  12. clickhouse_charon-1.0.2/charon/cli/__init__.py +0 -0
  13. clickhouse_charon-1.0.2/charon/cli/app.py +207 -0
  14. clickhouse_charon-1.0.2/charon/cli/config_cmd.py +184 -0
  15. clickhouse_charon-1.0.2/charon/cli/copy_cmd.py +173 -0
  16. clickhouse_charon-1.0.2/charon/cli/diff_cmd.py +167 -0
  17. clickhouse_charon-1.0.2/charon/cli/list_cmd.py +72 -0
  18. clickhouse_charon-1.0.2/charon/cli/status_cmd.py +96 -0
  19. clickhouse_charon-1.0.2/charon/client.py +164 -0
  20. clickhouse_charon-1.0.2/charon/config.py +98 -0
  21. clickhouse_charon-1.0.2/charon/copy.py +310 -0
  22. clickhouse_charon-1.0.2/charon/ddl.py +144 -0
  23. clickhouse_charon-1.0.2/charon/diff.py +104 -0
  24. clickhouse_charon-1.0.2/charon/web/__init__.py +0 -0
  25. clickhouse_charon-1.0.2/charon/web/app.py +344 -0
  26. clickhouse_charon-1.0.2/charon/web/jobs.py +191 -0
  27. clickhouse_charon-1.0.2/charon/web/models.py +61 -0
  28. clickhouse_charon-1.0.2/charon/web/templates/index.html +630 -0
  29. clickhouse_charon-1.0.2/docs/cli.md +165 -0
  30. clickhouse_charon-1.0.2/docs/configuration.md +132 -0
  31. clickhouse_charon-1.0.2/docs/index.md +32 -0
  32. clickhouse_charon-1.0.2/docs/web.md +92 -0
  33. clickhouse_charon-1.0.2/mkdocs.yml +38 -0
  34. clickhouse_charon-1.0.2/pyproject.toml +65 -0
  35. clickhouse_charon-1.0.2/tests/__init__.py +0 -0
  36. clickhouse_charon-1.0.2/tests/conftest.py +64 -0
  37. clickhouse_charon-1.0.2/tests/test_client.py +121 -0
  38. clickhouse_charon-1.0.2/tests/test_config.py +73 -0
  39. clickhouse_charon-1.0.2/tests/test_copy.py +132 -0
  40. clickhouse_charon-1.0.2/tests/test_ddl.py +80 -0
  41. clickhouse_charon-1.0.2/tests/test_diff.py +68 -0
  42. clickhouse_charon-1.0.2/tests/test_web.py +144 -0
  43. clickhouse_charon-1.0.2/uv.lock +1429 -0
@@ -0,0 +1,16 @@
1
+ ## Summary
2
+
3
+ <!-- Describe what this PR does and why -->
4
+
5
+ ## Changes
6
+
7
+ <!-- List the main changes -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] Tests added or updated for changed behaviour
12
+ - [ ] Documentation updated (README / docs/) if needed
13
+ - [ ] Commit message follows Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`, `ci:`)
14
+ - [ ] `make lint` passes
15
+ - [ ] `make typecheck` passes
16
+ - [ ] `make test` passes
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Set up uv
25
+ uses: astral-sh/setup-uv@v6
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --all-extras
29
+
30
+ - name: Lint (ruff check)
31
+ run: uv run ruff check .
32
+
33
+ - name: Format check (ruff format)
34
+ run: uv run ruff format --check .
35
+
36
+ - name: Type check (mypy)
37
+ run: uv run mypy charon
38
+
39
+ - name: Run tests
40
+ run: uv run pytest --tb=short
@@ -0,0 +1,45 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ release:
9
+ runs-on: ubuntu-latest
10
+ concurrency: release
11
+ permissions:
12
+ id-token: write
13
+ contents: write
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+ token: ${{ secrets.GH_TOKEN }}
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - name: Set up uv
27
+ uses: astral-sh/setup-uv@v6
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --all-extras
31
+
32
+ - name: Python Semantic Release — version bump
33
+ env:
34
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
35
+ run: uv run python-semantic-release version
36
+
37
+ - name: Python Semantic Release — publish GitHub Release
38
+ env:
39
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
40
+ run: uv run python-semantic-release publish
41
+
42
+ - name: Publish to PyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
44
+ with:
45
+ skip-existing: true
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ .venv/
3
+ dist/
4
+ *.egg-info/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .pytest_cache/
8
+ *.pyc
9
+ .env
10
+ .DS_Store
11
+ *.sqlite
12
+ site/
13
+
14
+ # CHaron runtime data — contains passwords, never commit
15
+ .charon/
16
+
17
+ # PyCharm
18
+ .idea/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-05-29
11
+
12
+ ### Added
13
+ - Initial release
14
+ - CLI commands: `config`, `status`, `list`, `diff`, `copy`, `web`
15
+ - Web dashboard with diff view, live copy progress (SSE), job history, config editor
16
+ - Partition-aware copy via `INSERT INTO FUNCTION remote()`
17
+ - Conventional Commits + GitHub Actions CI/CD pipeline
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yahor Yakubovich
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,24 @@
1
+ .PHONY: install lint typecheck test test-cov build clean
2
+
3
+ install:
4
+ uv sync --all-extras
5
+
6
+ lint:
7
+ uv run ruff check .
8
+ uv run ruff format --check .
9
+
10
+ typecheck:
11
+ uv run mypy chcopy
12
+
13
+ test:
14
+ uv run pytest --tb=short
15
+
16
+ test-cov:
17
+ uv run pytest --tb=short --cov=chcopy --cov-report=term-missing
18
+
19
+ build:
20
+ uv build
21
+
22
+ clean:
23
+ rm -rf dist/ .venv/ *.egg-info/ .mypy_cache/ .ruff_cache/ .pytest_cache/
24
+ find . -type d -name __pycache__ -exec rm -rf {} +
@@ -0,0 +1,303 @@
1
+ Metadata-Version: 2.4
2
+ Name: clickhouse-charon
3
+ Version: 1.0.2
4
+ Summary: CHaron — ClickHouse migration tool with CLI + web dashboard
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: fastapi>=0.111
9
+ Requires-Dist: jinja2>=3.1
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Dist: python-multipart>=0.0.9
12
+ Requires-Dist: pyyaml>=6
13
+ Requires-Dist: requests>=2.32
14
+ Requires-Dist: rich>=13
15
+ Requires-Dist: typer>=0.12
16
+ Requires-Dist: uvicorn[standard]>=0.29
17
+ Provides-Extra: dev
18
+ Requires-Dist: httpx>=0.27; extra == 'dev'
19
+ Requires-Dist: mypy>=1.10; extra == 'dev'
20
+ Requires-Dist: pytest-mock>=3; extra == 'dev'
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Requires-Dist: python-semantic-release>=9; extra == 'dev'
23
+ Requires-Dist: ruff>=0.4; extra == 'dev'
24
+ Requires-Dist: types-pyyaml>=6; extra == 'dev'
25
+ Requires-Dist: types-requests>=2; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # charon
29
+
30
+ [![PyPI version](https://img.shields.io/pypi/v/clickhouse-charon.svg)](https://pypi.org/project/clickhouse-charon/)
31
+ [![CI](https://github.com/yahoryakubovich/charon/actions/workflows/ci.yml/badge.svg)](https://github.com/yahoryakubovich/charon/actions/workflows/ci.yml)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
+
34
+ **charon** is a ClickHouse database copier with a CLI and web dashboard. It copies tables between ClickHouse instances partition-by-partition, shows a live diff, and tracks job history.
35
+
36
+ ---
37
+
38
+ ## Features
39
+
40
+ - Partition-aware copy using `INSERT INTO FUNCTION remote()` — no intermediate files
41
+ - Schema DDL sync: tables, views, materialized views, dictionaries (optional)
42
+ - Live diff: compare source vs. destination row counts per partition
43
+ - Web dashboard with SSE live log streaming
44
+ - Job history persisted to SQLite
45
+ - Configurable retry with exponential backoff
46
+ - Multiple named profiles in `~/.charon/config.yaml`
47
+ - Replicated engine → plain engine conversion option
48
+ - Dry-run mode
49
+ - `ON CLUSTER` clause stripping for single-node destinations
50
+
51
+ ---
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ # Using pipx (recommended for end-users)
57
+ pipx install clickhouse-charon
58
+
59
+ # Using uv tool
60
+ uv tool install clickhouse-charon
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Quick start
66
+
67
+ ```bash
68
+ # Initialize a profile interactively
69
+ charon config init
70
+
71
+ # Check connectivity
72
+ charon status
73
+
74
+ # List tables on the source
75
+ charon list
76
+
77
+ # Show row-count diff between src and dst
78
+ charon diff
79
+
80
+ # Copy everything
81
+ charon copy
82
+
83
+ # Copy a single table
84
+ charon copy --table my_table
85
+
86
+ # Dry run
87
+ charon copy --dry-run
88
+
89
+ # Start the web dashboard
90
+ charon web
91
+ ```
92
+
93
+ ---
94
+
95
+ ## CLI Reference
96
+
97
+ ### `charon config init`
98
+ Interactive wizard that creates or updates a named profile.
99
+
100
+ ### `charon config show [--profile NAME]`
101
+ Print the current profile (password masked).
102
+
103
+ ### `charon config list`
104
+ List all profile names.
105
+
106
+ ### `charon config delete PROFILE`
107
+ Remove a profile.
108
+
109
+ ### `charon status [--profile NAME]`
110
+ Ping source and destination; show table count, total rows, total bytes.
111
+
112
+ ### `charon list [--src|--dst] [--filter REGEX]`
113
+ List tables on source or destination with engine, row count, and byte size.
114
+
115
+ ### `charon diff [--table TABLE] [--json]`
116
+ Compare source and destination. Without `--table`: table-level summary. With `--table`: partition-level detail.
117
+
118
+ ### `charon copy [OPTIONS]`
119
+
120
+ | Option | Default | Description |
121
+ |---|---|---|
122
+ | `--table TABLE` | — | Copy a single table |
123
+ | `--tables T1,T2` | — | Copy specific tables |
124
+ | `--dry-run` | false | Show what would be copied |
125
+ | `--no-partitions` | false | Copy whole table at once |
126
+ | `--profile NAME` | default | Profile to use |
127
+
128
+ ### `charon web [OPTIONS]`
129
+
130
+ | Option | Default | Description |
131
+ |---|---|---|
132
+ | `--host` | 127.0.0.1 | Bind address |
133
+ | `--port` | 8765 | Port |
134
+ | `--reload` | false | Auto-reload (dev mode) |
135
+
136
+ ---
137
+
138
+ ## Configuration reference
139
+
140
+ Config file: `~/.charon/config.yaml` (permissions: 0600)
141
+
142
+ ```yaml
143
+ default_profile: prod
144
+
145
+ profiles:
146
+ prod:
147
+ src:
148
+ host: http://clickhouse-src:8123
149
+ user: default
150
+ password: secret
151
+ database: analytics
152
+ timeout: 3600
153
+ dst:
154
+ host: http://clickhouse-dst:8123
155
+ user: default
156
+ password: secret
157
+ database: analytics
158
+ tcp_hostport: clickhouse-dst:9000 # required for remote() INSERT
159
+ copy_by_partitions: true
160
+ copy_views: false
161
+ copy_kafka_tables: false
162
+ copy_dictionaries: false
163
+ convert_replicated_to_merge: false
164
+ skip_tables:
165
+ - huge_log_table
166
+ retry_count: 3
167
+ retry_sleep: 2.0
168
+ retry_max_sleep: 30.0
169
+ insert_settings:
170
+ max_partitions_per_insert_block: 1000
171
+ max_insert_block_size: 1048576
172
+ max_threads: 8
173
+ send_logs_level: warning
174
+ ```
175
+
176
+ ### Field descriptions
177
+
178
+ | Field | Type | Default | Description |
179
+ |---|---|---|---|
180
+ | `src.host` | str | required | HTTP URL of source CH (auto-prefixed with `http://`) |
181
+ | `src.user` | str | `default` | Username |
182
+ | `src.password` | str | `""` | Password (never logged) |
183
+ | `src.database` | str | required | Source database name |
184
+ | `src.timeout` | int | 3600 | Request timeout in seconds |
185
+ | `dst.tcp_hostport` | str | None | `host:port` for TCP — required by `remote()` INSERT |
186
+ | `copy_by_partitions` | bool | true | Copy partition-by-partition |
187
+ | `copy_views` | bool | false | Also copy VIEW tables |
188
+ | `copy_kafka_tables` | bool | false | Also copy Kafka engine tables |
189
+ | `copy_dictionaries` | bool | false | Also copy Dictionary tables |
190
+ | `convert_replicated_to_merge` | bool | false | Strip Replicated* prefix in DDL |
191
+ | `skip_tables` | list[str] | `[]` | Tables to skip |
192
+ | `retry_count` | int | 3 | Max retry attempts |
193
+ | `retry_sleep` | float | 2.0 | Base sleep between retries (seconds) |
194
+ | `retry_max_sleep` | float | 30.0 | Cap for exponential backoff |
195
+
196
+ ---
197
+
198
+ ## Web dashboard
199
+
200
+ Open [http://localhost:8765](http://localhost:8765) after running `charon web`.
201
+
202
+ Features:
203
+ - Live status cards for src and dst
204
+ - Table comparison grid with color-coded diff
205
+ - One-click copy (all or selected tables)
206
+ - SSE log stream for running jobs
207
+ - Job history (last 20 jobs)
208
+ - Profile config editor
209
+
210
+ ---
211
+
212
+ ## Development setup
213
+
214
+ ```bash
215
+ git clone https://github.com/your-org/charon
216
+ cd charon
217
+ uv sync --all-extras
218
+ ```
219
+
220
+ ### Available make targets
221
+
222
+ ```bash
223
+ make install # uv sync --all-extras
224
+ make lint # ruff check + format check
225
+ make typecheck # mypy
226
+ make test # pytest
227
+ make test-cov # pytest with coverage
228
+ make build # uv build
229
+ make clean # remove dist/, .venv/, caches
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Contributing
235
+
236
+ ### Branches
237
+
238
+ | Branch | Purpose |
239
+ |--------|---------|
240
+ | `main` | Protected. Releases happen here. Only via PR. |
241
+ | `feat/<name>` | New feature — branch off `main`, PR back to `main` |
242
+ | `fix/<name>` | Bug fix — same flow |
243
+ | `chore/<name>` | Maintenance, deps, tooling |
244
+
245
+ ```bash
246
+ git checkout -b feat/profile-switcher
247
+ # ... work ...
248
+ git push origin feat/profile-switcher
249
+ # open PR → CI runs → merge → automatic release
250
+ ```
251
+
252
+ ### Commit messages — Conventional Commits
253
+
254
+ Format: `<type>(<optional scope>): <description>`
255
+
256
+ | Type | When to use | Version bump |
257
+ |------|-------------|-------------|
258
+ | `feat:` | New feature | `minor` — `0.1.0 → 0.2.0` |
259
+ | `fix:` | Bug fix | `patch` — `0.1.0 → 0.1.1` |
260
+ | `feat!:` or `BREAKING CHANGE:` in body | Breaking API change | `major` — `0.1.0 → 1.0.0` |
261
+ | `docs:` | Documentation only | no bump |
262
+ | `chore:` | Deps, tooling, housekeeping | no bump |
263
+ | `ci:` | CI/CD changes | no bump |
264
+ | `refactor:` | Code restructure, no behaviour change | no bump |
265
+ | `test:` | Adding or fixing tests | no bump |
266
+ | `perf:` | Performance improvement | `patch` |
267
+
268
+ Examples:
269
+
270
+ ```
271
+ feat: add profile switcher in web UI
272
+ fix: handle empty partition list on first sync
273
+ feat(web)!: rename /api/config to /api/settings
274
+ docs: add TCP hostport explanation to README
275
+ chore: bump fastapi to 0.115
276
+ ```
277
+
278
+ ### Release cycle (fully automated)
279
+
280
+ When a PR is merged to `main`, GitHub Actions:
281
+
282
+ 1. Runs lint + typecheck + tests
283
+ 2. `python-semantic-release` reads all commits since the last tag
284
+ 3. Determines the next version (`major` / `minor` / `patch`)
285
+ 4. Bumps `version` in `pyproject.toml` and `charon/__init__.py`
286
+ 5. Updates `CHANGELOG.md`
287
+ 6. Creates a git tag `vX.Y.Z` and a GitHub Release
288
+ 7. Publishes the package to PyPI
289
+
290
+ > If there are no `feat:` or `fix:` commits (only `chore:`, `docs:`, etc.) — no release is created.
291
+
292
+ ### Required secrets (GitHub → Settings → Secrets)
293
+
294
+ | Secret | Value |
295
+ |--------|-------|
296
+ | `PYPI_TOKEN` | API token from pypi.org |
297
+ | `GH_TOKEN` | GitHub PAT with `contents: write` scope |
298
+
299
+ ---
300
+
301
+ ## License
302
+
303
+ MIT — see [LICENSE](LICENSE).