ducktracker 0.1.0__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. ducktracker-0.1.0/LICENSE +21 -0
  2. ducktracker-0.1.0/PKG-INFO +205 -0
  3. ducktracker-0.1.0/README.md +187 -0
  4. ducktracker-0.1.0/pyproject.toml +46 -0
  5. ducktracker-0.1.0/setup.cfg +4 -0
  6. ducktracker-0.1.0/src/ducktracker/__init__.py +3 -0
  7. ducktracker-0.1.0/src/ducktracker/__main__.py +5 -0
  8. ducktracker-0.1.0/src/ducktracker/backends.py +29 -0
  9. ducktracker-0.1.0/src/ducktracker/cli.py +328 -0
  10. ducktracker-0.1.0/src/ducktracker/config.py +136 -0
  11. ducktracker-0.1.0/src/ducktracker/connection.py +69 -0
  12. ducktracker-0.1.0/src/ducktracker/drift.py +264 -0
  13. ducktracker-0.1.0/src/ducktracker/history/__init__.py +91 -0
  14. ducktracker-0.1.0/src/ducktracker/history/ducklake.py +217 -0
  15. ducktracker-0.1.0/src/ducktracker/initializer.py +59 -0
  16. ducktracker-0.1.0/src/ducktracker/introspection/__init__.py +21 -0
  17. ducktracker-0.1.0/src/ducktracker/introspection/ducklake.py +231 -0
  18. ducktracker-0.1.0/src/ducktracker/migrator.py +357 -0
  19. ducktracker-0.1.0/src/ducktracker/models.py +197 -0
  20. ducktracker-0.1.0/src/ducktracker/resolver.py +124 -0
  21. ducktracker-0.1.0/src/ducktracker/sql_utils.py +13 -0
  22. ducktracker-0.1.0/src/ducktracker/templates/.gitignore +3 -0
  23. ducktracker-0.1.0/src/ducktracker/templates/README.md +63 -0
  24. ducktracker-0.1.0/src/ducktracker/templates/__init__.py +0 -0
  25. ducktracker-0.1.0/src/ducktracker/templates/ducktracker.ducklake-duckdb.toml +13 -0
  26. ducktracker-0.1.0/src/ducktracker/templates/ducktracker.ducklake-postgres.toml +17 -0
  27. ducktracker-0.1.0/src/ducktracker.egg-info/PKG-INFO +205 -0
  28. ducktracker-0.1.0/src/ducktracker.egg-info/SOURCES.txt +35 -0
  29. ducktracker-0.1.0/src/ducktracker.egg-info/dependency_links.txt +1 -0
  30. ducktracker-0.1.0/src/ducktracker.egg-info/entry_points.txt +2 -0
  31. ducktracker-0.1.0/src/ducktracker.egg-info/requires.txt +8 -0
  32. ducktracker-0.1.0/src/ducktracker.egg-info/top_level.txt +1 -0
  33. ducktracker-0.1.0/tests/test_backends.py +40 -0
  34. ducktracker-0.1.0/tests/test_config.py +184 -0
  35. ducktracker-0.1.0/tests/test_init.py +94 -0
  36. ducktracker-0.1.0/tests/test_models.py +254 -0
  37. ducktracker-0.1.0/tests/test_resolver.py +121 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick
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,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: ducktracker
3
+ Version: 0.1.0
4
+ Summary: DDL migration and schema drift detection for DuckLake
5
+ Author: ducktracker contributors
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: click>=8.1
11
+ Requires-Dist: duckdb>=1.2
12
+ Requires-Dist: rich>=13.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=8.0; extra == "dev"
15
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
16
+ Requires-Dist: ruff>=0.9; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # ducktracker
20
+
21
+ Simple tool to keep your ducks in a row.
22
+
23
+ DDL migration and schema drift detection for DuckLake.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install -e .
29
+ ```
30
+
31
+ For development (includes pytest and ruff):
32
+
33
+ ```bash
34
+ pip install -e ".[dev]"
35
+ ```
36
+
37
+ ## Initialize
38
+
39
+ Create a directory and run `init` to scaffold the config and migrations folder:
40
+
41
+ ```bash
42
+ mkdir my-ducklake
43
+ ducktracker init my-ducklake --backend ducklake-duckdb
44
+ ```
45
+
46
+ If you omit `--backend`, you'll be prompted. Two options:
47
+
48
+ | Backend | Use when |
49
+ |---|---|
50
+ | `ducklake-duckdb` | DuckLake catalog in a local DuckDB file. Good for local dev. |
51
+ | `ducklake-postgres` | DuckLake catalog in PostgreSQL. Good for teams. |
52
+
53
+ `init` creates `ducktracker.toml`, `migrations/`, `.gitignore`, and a `README.md` in the target directory. The directory must already exist — `init` won't create it.
54
+
55
+ ## Writing migrations
56
+
57
+ Two file types, both live in your `migrations/` directory:
58
+
59
+ - **Versioned:** `V{n}__{description}.sql` — applied once, in version order
60
+ - **Repeatable:** `R__{description}.sql` — re-applied whenever the file content changes
61
+
62
+ Use `create` to scaffold the next file:
63
+
64
+ ```bash
65
+ ducktracker create "create users table"
66
+ # → migrations/V2__create_users_table.sql
67
+
68
+ ducktracker create "refresh views" --repeatable
69
+ # → migrations/R__refresh_views.sql
70
+ ```
71
+
72
+ Then open the file and add your DDL.
73
+
74
+ ## Running migrations
75
+
76
+ ```bash
77
+ ducktracker migrate
78
+ ```
79
+
80
+ Flags worth knowing:
81
+
82
+ ```
83
+ --dry-run Show what would be applied without executing any migration SQL
84
+ --target N Only apply migrations up to version N (inclusive)
85
+ ```
86
+
87
+ By default, checksums of previously applied versioned migrations are validated before anything runs. If a migration file has been modified after it was applied, `migrate` will refuse to proceed.
88
+
89
+ ## Checking status
90
+
91
+ ```bash
92
+ ducktracker info
93
+ ```
94
+
95
+ Prints a table with version, description, type, state, applied timestamp, and checksum for every migration it knows about. States: `applied`, `pending`, `failed`, `outdated`, `missing`.
96
+
97
+ To check only checksums without touching the database state:
98
+
99
+ ```bash
100
+ ducktracker validate
101
+ ```
102
+
103
+ Exits 1 if any applied migration's file has changed since it was applied.
104
+
105
+ ## Detecting drift
106
+
107
+ ```bash
108
+ ducktracker drift
109
+ ```
110
+
111
+ Compares the schema snapshot captured at the last `migrate` or `baseline` run against the live catalog. Exits 1 if differences are found — useful in CI to catch out-of-band schema changes.
112
+
113
+ Output shows added (`+`), removed (`-`), and modified (`~`) objects.
114
+
115
+ ## Baselining an existing database
116
+
117
+ If you're bringing an existing database under ducktracker management, use `baseline` before running any migrations:
118
+
119
+ ```bash
120
+ ducktracker baseline --version 3
121
+ ducktracker baseline --version 3 --description "after initial setup"
122
+ ```
123
+
124
+ This captures the current schema as a snapshot and marks the database at the given version. It will refuse to run if the history table already contains any records (including a prior baseline).
125
+
126
+ ## Configuration
127
+
128
+ ducktracker looks for `ducktracker.toml` in the current directory by default. Override with `-c`:
129
+
130
+ ```bash
131
+ ducktracker -c path/to/ducktracker.toml migrate
132
+ ```
133
+
134
+ A minimal config looks like this:
135
+
136
+ ```toml
137
+ [connection]
138
+ catalog_name = "my_ducklake"
139
+ catalog_backend = "duckdb"
140
+ duckdb_metadata_path = "ducklake_metadata.db"
141
+
142
+ [migrations]
143
+ directory = "migrations"
144
+ target_schema = "main"
145
+
146
+ [behavior]
147
+ validate_on_migrate = true
148
+ out_of_order = false
149
+ ```
150
+
151
+ ### Using a DuckDB secret
152
+
153
+ Instead of putting connection details directly in the config file, you can reference a named DuckDB persistent secret. Set `secret_name` in place of `duckdb_metadata_path` (duckdb backend) or `postgres_connection` (postgres backend):
154
+
155
+ ```toml
156
+ [connection]
157
+ catalog_name = "my_ducklake"
158
+ catalog_backend = "postgres"
159
+ secret_name = "my_ducklake_secret"
160
+ ```
161
+
162
+ The secret must already exist in DuckDB before ducktracker runs.
163
+
164
+ If your secrets are stored outside DuckDB's default location (`~/.duckdb/stored_secrets/`), add `secret_directory` to the same `[connection]` block:
165
+
166
+ ```toml
167
+ [connection]
168
+ catalog_name = "my_ducklake"
169
+ catalog_backend = "postgres"
170
+ secret_name = "my_ducklake_secret"
171
+ secret_directory = "/run/secrets/duckdb"
172
+ ```
173
+
174
+ ducktracker issues `SET secret_directory` before opening any catalog connection, so the path is in effect for the entire session.
175
+
176
+ ### Config layers
177
+
178
+ Settings are resolved in this order, with later layers winning:
179
+
180
+ 1. `ducktracker.toml`
181
+ 2. Environment variables
182
+ 3. CLI flags
183
+
184
+ ### Key environment variables
185
+
186
+ | Variable | Config equivalent |
187
+ |---|---|
188
+ | `DUCKTRACKER_CATALOG_NAME` | `connection.catalog_name` |
189
+ | `DUCKTRACKER_CATALOG_BACKEND` | `connection.catalog_backend` |
190
+ | `DUCKTRACKER_DUCKDB_METADATA_PATH` | `connection.duckdb_metadata_path` |
191
+ | `DUCKTRACKER_POSTGRES_CONNECTION` | `connection.postgres_connection` |
192
+ | `DUCKTRACKER_SECRET_DIRECTORY` | `connection.secret_directory` |
193
+
194
+ ### Global CLI flags
195
+
196
+ These apply to all commands except `init`:
197
+
198
+ ```
199
+ -c, --config PATH Path to ducktracker.toml
200
+ --catalog NAME Override catalog name
201
+ --backend TYPE Override backend (duckdb, postgres) — note these differ from init's backend values
202
+ --metadata PATH Override DuckDB metadata file path
203
+ --connection STRING Override PostgreSQL connection string
204
+ --secrets-dir PATH Override DuckDB persistent secrets directory
205
+ ```
@@ -0,0 +1,187 @@
1
+ # ducktracker
2
+
3
+ Simple tool to keep your ducks in a row.
4
+
5
+ DDL migration and schema drift detection for DuckLake.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ For development (includes pytest and ruff):
14
+
15
+ ```bash
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ ## Initialize
20
+
21
+ Create a directory and run `init` to scaffold the config and migrations folder:
22
+
23
+ ```bash
24
+ mkdir my-ducklake
25
+ ducktracker init my-ducklake --backend ducklake-duckdb
26
+ ```
27
+
28
+ If you omit `--backend`, you'll be prompted. Two options:
29
+
30
+ | Backend | Use when |
31
+ |---|---|
32
+ | `ducklake-duckdb` | DuckLake catalog in a local DuckDB file. Good for local dev. |
33
+ | `ducklake-postgres` | DuckLake catalog in PostgreSQL. Good for teams. |
34
+
35
+ `init` creates `ducktracker.toml`, `migrations/`, `.gitignore`, and a `README.md` in the target directory. The directory must already exist — `init` won't create it.
36
+
37
+ ## Writing migrations
38
+
39
+ Two file types, both live in your `migrations/` directory:
40
+
41
+ - **Versioned:** `V{n}__{description}.sql` — applied once, in version order
42
+ - **Repeatable:** `R__{description}.sql` — re-applied whenever the file content changes
43
+
44
+ Use `create` to scaffold the next file:
45
+
46
+ ```bash
47
+ ducktracker create "create users table"
48
+ # → migrations/V2__create_users_table.sql
49
+
50
+ ducktracker create "refresh views" --repeatable
51
+ # → migrations/R__refresh_views.sql
52
+ ```
53
+
54
+ Then open the file and add your DDL.
55
+
56
+ ## Running migrations
57
+
58
+ ```bash
59
+ ducktracker migrate
60
+ ```
61
+
62
+ Flags worth knowing:
63
+
64
+ ```
65
+ --dry-run Show what would be applied without executing any migration SQL
66
+ --target N Only apply migrations up to version N (inclusive)
67
+ ```
68
+
69
+ By default, checksums of previously applied versioned migrations are validated before anything runs. If a migration file has been modified after it was applied, `migrate` will refuse to proceed.
70
+
71
+ ## Checking status
72
+
73
+ ```bash
74
+ ducktracker info
75
+ ```
76
+
77
+ Prints a table with version, description, type, state, applied timestamp, and checksum for every migration it knows about. States: `applied`, `pending`, `failed`, `outdated`, `missing`.
78
+
79
+ To check only checksums without touching the database state:
80
+
81
+ ```bash
82
+ ducktracker validate
83
+ ```
84
+
85
+ Exits 1 if any applied migration's file has changed since it was applied.
86
+
87
+ ## Detecting drift
88
+
89
+ ```bash
90
+ ducktracker drift
91
+ ```
92
+
93
+ Compares the schema snapshot captured at the last `migrate` or `baseline` run against the live catalog. Exits 1 if differences are found — useful in CI to catch out-of-band schema changes.
94
+
95
+ Output shows added (`+`), removed (`-`), and modified (`~`) objects.
96
+
97
+ ## Baselining an existing database
98
+
99
+ If you're bringing an existing database under ducktracker management, use `baseline` before running any migrations:
100
+
101
+ ```bash
102
+ ducktracker baseline --version 3
103
+ ducktracker baseline --version 3 --description "after initial setup"
104
+ ```
105
+
106
+ This captures the current schema as a snapshot and marks the database at the given version. It will refuse to run if the history table already contains any records (including a prior baseline).
107
+
108
+ ## Configuration
109
+
110
+ ducktracker looks for `ducktracker.toml` in the current directory by default. Override with `-c`:
111
+
112
+ ```bash
113
+ ducktracker -c path/to/ducktracker.toml migrate
114
+ ```
115
+
116
+ A minimal config looks like this:
117
+
118
+ ```toml
119
+ [connection]
120
+ catalog_name = "my_ducklake"
121
+ catalog_backend = "duckdb"
122
+ duckdb_metadata_path = "ducklake_metadata.db"
123
+
124
+ [migrations]
125
+ directory = "migrations"
126
+ target_schema = "main"
127
+
128
+ [behavior]
129
+ validate_on_migrate = true
130
+ out_of_order = false
131
+ ```
132
+
133
+ ### Using a DuckDB secret
134
+
135
+ Instead of putting connection details directly in the config file, you can reference a named DuckDB persistent secret. Set `secret_name` in place of `duckdb_metadata_path` (duckdb backend) or `postgres_connection` (postgres backend):
136
+
137
+ ```toml
138
+ [connection]
139
+ catalog_name = "my_ducklake"
140
+ catalog_backend = "postgres"
141
+ secret_name = "my_ducklake_secret"
142
+ ```
143
+
144
+ The secret must already exist in DuckDB before ducktracker runs.
145
+
146
+ If your secrets are stored outside DuckDB's default location (`~/.duckdb/stored_secrets/`), add `secret_directory` to the same `[connection]` block:
147
+
148
+ ```toml
149
+ [connection]
150
+ catalog_name = "my_ducklake"
151
+ catalog_backend = "postgres"
152
+ secret_name = "my_ducklake_secret"
153
+ secret_directory = "/run/secrets/duckdb"
154
+ ```
155
+
156
+ ducktracker issues `SET secret_directory` before opening any catalog connection, so the path is in effect for the entire session.
157
+
158
+ ### Config layers
159
+
160
+ Settings are resolved in this order, with later layers winning:
161
+
162
+ 1. `ducktracker.toml`
163
+ 2. Environment variables
164
+ 3. CLI flags
165
+
166
+ ### Key environment variables
167
+
168
+ | Variable | Config equivalent |
169
+ |---|---|
170
+ | `DUCKTRACKER_CATALOG_NAME` | `connection.catalog_name` |
171
+ | `DUCKTRACKER_CATALOG_BACKEND` | `connection.catalog_backend` |
172
+ | `DUCKTRACKER_DUCKDB_METADATA_PATH` | `connection.duckdb_metadata_path` |
173
+ | `DUCKTRACKER_POSTGRES_CONNECTION` | `connection.postgres_connection` |
174
+ | `DUCKTRACKER_SECRET_DIRECTORY` | `connection.secret_directory` |
175
+
176
+ ### Global CLI flags
177
+
178
+ These apply to all commands except `init`:
179
+
180
+ ```
181
+ -c, --config PATH Path to ducktracker.toml
182
+ --catalog NAME Override catalog name
183
+ --backend TYPE Override backend (duckdb, postgres) — note these differ from init's backend values
184
+ --metadata PATH Override DuckDB metadata file path
185
+ --connection STRING Override PostgreSQL connection string
186
+ --secrets-dir PATH Override DuckDB persistent secrets directory
187
+ ```
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ducktracker"
7
+ version = "0.1.0"
8
+ description = "DDL migration and schema drift detection for DuckLake"
9
+ requires-python = ">=3.12"
10
+ license = "MIT"
11
+ readme = "README.md"
12
+ authors = [
13
+ { name = "ducktracker contributors" }
14
+ ]
15
+ dependencies = [
16
+ "click>=8.1",
17
+ "duckdb>=1.2",
18
+ "rich>=13.0",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "pytest>=8.0",
24
+ "pytest-cov>=5.0",
25
+ "ruff>=0.9",
26
+ ]
27
+
28
+ [project.scripts]
29
+ ducktracker = "ducktracker.cli:cli"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools.package-data]
35
+ "ducktracker" = ["templates/*", "templates/.*"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ pythonpath = ["src"]
40
+
41
+ [tool.ruff]
42
+ line-length = 120
43
+ target-version = "py312"
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "N", "W", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """ducktracker: DDL migration and schema drift detection for DuckLake and PostgreSQL."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow running ducktracker as ``python -m ducktracker``."""
2
+
3
+ from ducktracker.cli import cli
4
+
5
+ cli()
@@ -0,0 +1,29 @@
1
+ """Factory functions to select the right backend implementations from config."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ducktracker.config import DuckTrackerConfig
6
+ from ducktracker.history import HistoryManagerBase
7
+ from ducktracker.history.ducklake import DuckLakeHistoryManager
8
+ from ducktracker.introspection import IntrospectorBase
9
+ from ducktracker.introspection.ducklake import DuckLakeIntrospector
10
+
11
+ _INTROSPECTORS: dict[str, type[IntrospectorBase]] = {
12
+ "duckdb": DuckLakeIntrospector,
13
+ "postgres": DuckLakeIntrospector,
14
+ }
15
+
16
+ _HISTORY_MANAGERS: dict[str, type[HistoryManagerBase]] = {
17
+ "duckdb": DuckLakeHistoryManager,
18
+ "postgres": DuckLakeHistoryManager,
19
+ }
20
+
21
+
22
+ def get_introspector(config: DuckTrackerConfig) -> IntrospectorBase:
23
+ """Return the appropriate IntrospectorBase implementation for this config."""
24
+ return _INTROSPECTORS[config.catalog_backend]()
25
+
26
+
27
+ def get_history_manager(config: DuckTrackerConfig) -> HistoryManagerBase:
28
+ """Return the appropriate HistoryManagerBase implementation for this config."""
29
+ return _HISTORY_MANAGERS[config.catalog_backend]()