databricks-schema 0.8.0__tar.gz → 0.10.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.
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/CLAUDE.md +16 -7
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/PKG-INFO +108 -9
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/README.md +103 -6
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/__init__.py +18 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/cli.py +99 -57
- databricks_schema-0.10.0/databricks_schema/dbml_io.py +433 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/diff.py +78 -44
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/extractor.py +58 -3
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/models.py +2 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/sql_gen.py +45 -1
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/yaml_io.py +42 -1
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/pyproject.toml +9 -3
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/skills/databricks-schema-cli/SKILL.md +28 -10
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_cli.py +111 -0
- databricks_schema-0.10.0/tests/test_dbml_io.py +316 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_diff.py +66 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_extractor.py +116 -2
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_sql_gen.py +50 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_yaml_io.py +23 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/uv.lock +41 -9
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.github/workflows/ci.yml +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.github/workflows/release.yml +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.gitignore +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.python-version +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/LICENSE +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/validate.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/01_extract_schema.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/02_save_load_yaml.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/03_diff_schemas.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/04_generate_sql.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/__init__.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_models.py +0 -0
- {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_validate.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Project
|
|
4
4
|
|
|
5
|
-
`databricks-schema` — CLI + Python library that extracts, diffs, and generates SQL for Databricks Unity Catalog schemas stored as YAML or JSON files (one file per schema). Pydantic v2 models serve as the intermediate representation for future bidirectional sync.
|
|
5
|
+
`databricks-schema` — CLI + Python library that extracts, diffs, and generates SQL for Databricks Unity Catalog schemas stored as YAML or JSON files (one file per schema) or DBML (one file per catalog). Pydantic v2 models serve as the intermediate representation for future bidirectional sync.
|
|
6
6
|
|
|
7
7
|
## Key commands
|
|
8
8
|
|
|
@@ -18,9 +18,10 @@ uv run databricks-schema --help
|
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
databricks_schema/
|
|
21
|
-
models.py # Pydantic v2 models: Catalog, Schema, Table, Column, PrimaryKey, ForeignKey
|
|
21
|
+
models.py # Pydantic v2 models: Catalog, Schema, Table, Column, PrimaryKey, ForeignKey (Schema/Table carry grants)
|
|
22
22
|
extractor.py # CatalogExtractor — wraps databricks-sdk
|
|
23
|
-
yaml_io.py # schema/catalog to/from YAML and JSON; _strip_empty removes None + empty collections
|
|
23
|
+
yaml_io.py # schema/catalog to/from YAML and JSON; _strip_empty removes None + empty collections; load_schema_dir (all formats)
|
|
24
|
+
dbml_io.py # schema/catalog to/from DBML; own writer, PyDBML grammar for parsing
|
|
24
25
|
diff.py # diff_schemas / diff_catalogs / diff_catalog_with_dir / diff_schema_dirs; FieldChange, ColumnDiff, TableDiff, SchemaDiff, CatalogDiff
|
|
25
26
|
sql_gen.py # schema_diff_to_sql — pure SQL generation from SchemaDiff; no SDK/IO
|
|
26
27
|
cli.py # argparse CLI: extract, diff, generate-sql, validate, diff-files, list-catalogs, list-schemas
|
|
@@ -29,6 +30,7 @@ tests/
|
|
|
29
30
|
test_models.py
|
|
30
31
|
test_extractor.py # all SDK calls mocked with MagicMock
|
|
31
32
|
test_yaml_io.py
|
|
33
|
+
test_dbml_io.py
|
|
32
34
|
test_diff.py # pure model comparison, no SDK calls
|
|
33
35
|
test_sql_gen.py # pure SQL generation tests, no SDK calls
|
|
34
36
|
test_validate.py
|
|
@@ -49,18 +51,25 @@ skills/databricks-schema-cli/SKILL.md # Agent Skill: teaches an AI agent to dri
|
|
|
49
51
|
- Column order in YAML = SDK position (None → 9999)
|
|
50
52
|
- `_strip_empty`: removes `None` and empty `dict`/`list`; preserves `False`, `0`, empty strings
|
|
51
53
|
- `yaml_io.py` has parallel YAML and JSON functions (`schema_to_yaml`/`schema_to_json`, etc.)
|
|
52
|
-
- `diff_catalog_with_dir`
|
|
54
|
+
- `diff_catalog_with_dir` / `diff_schema_dirs` / `load_schema_dir` take `fmt: SchemaFormat` (`"yaml" | "json" | "dbml"`, default `"yaml"`)
|
|
55
|
+
- DBML is one document per catalog (`extract -f dbml -o DIR` writes `DIR/<catalog>.dbml`): Catalog → `Project`, Schema → `TableGroup` (always emitted), PK → `indexes { (...) [pk] }`, FK → `Ref`
|
|
56
|
+
- DBML has no key/value metadata: tags/grants/owner/table_type/storage_location are trailing `@key: value` lines in notes (`@tags`/`@grants` = JSON objects); writer output must round-trip exactly and stay parseable by `@dbml/core`
|
|
57
|
+
- PyDBML is optional (`databricks-schema[dbml]` extra; also in the `dev` group): `dbml_io` imports it in a top-level `try/except ImportError` setting `PYDBML_AVAILABLE`; writing DBML must never need it, readers raise `DbmlDependencyError` (CLI exits 2)
|
|
58
|
+
- `dbml_io` parses via PyDBML's grammar (`pydbml.definitions`) but resolves refs itself, so refs to tables outside the file are kept; never build a PyDBML `Database` (it rejects such refs and reflows note text) and never use PyDBML's renderer (it leaves complex types unquoted)
|
|
59
|
+
- For DBML directories, `--schema` filters match schema names (YAML/JSON match file stems)
|
|
53
60
|
- CLI: catalog is a required positional argument (not a flag)
|
|
54
|
-
- `extract` `--format`/`-f` selects output format (`yaml` default, `json` opt-in); dest is `fmt`
|
|
55
|
-
- `diff` auto-detects format from files present in the directory; exits 2 on mixed
|
|
61
|
+
- `extract` `--format`/`-f` selects output format (`yaml` default, `json`/`dbml` opt-in); dest is `fmt`
|
|
62
|
+
- `diff` auto-detects format from files present in the directory; exits 2 on mixed formats
|
|
56
63
|
- `diff`'s second positional arg is a directory if one exists at that path, else a second catalog name (`diff_catalogs`); first `catalog` arg is always the live side
|
|
57
64
|
- `TableType` is re-exported from `databricks.sdk.service.catalog` — do not redefine it
|
|
58
65
|
- `--include-metadata` flag (on `extract`, `diff`, `generate-sql`, `diff-files`) adds `owner` to output/comparison; `extract` additionally includes `storage_location` in its output (not compared by `diff`/`generate-sql`/`diff-files`); excluded by default
|
|
59
66
|
- `--include-tags` flag (on `extract`, `diff`, `generate-sql`) enables Unity Catalog tag lookups; excluded by default
|
|
67
|
+
- `--include-grants` flag (on `extract`, `diff`, `generate-sql`, `diff-files`) adds `grants: dict[str, list[str]]` (principal → sorted privileges) on `Schema`/`Table` via `grants.list()` — direct grants only, never inherited/effective ones; diff functions take `include_grants` and ignore grants otherwise; a grants `FieldChange` holds only differing principals
|
|
68
|
+
- `generate-sql` emits `GRANT` for stored-not-live privileges and `REVOKE` for live-not-stored; `REVOKE` is destructive and follows `--allow-drop`
|
|
60
69
|
- `--quiet`/`-q` flag (on `extract`, `diff`, `generate-sql`, `diff-files`) raises the log level to suppress progress messages; errors always print
|
|
61
70
|
- `diff` command exits 0 (no changes) or 1 (differences found) — useful in CI
|
|
62
71
|
- Diff result types are dataclasses (not Pydantic); comparison functions are pure (no SDK calls)
|
|
63
|
-
- `generate-sql` auto-detects format from files; exits 2 on mixed
|
|
72
|
+
- `generate-sql` auto-detects format from files; exits 2 on mixed formats or empty directory; invalid DBML exits 2
|
|
64
73
|
- `generate-sql` destructive statements commented out by default; `--allow-drop` emits real DROPs
|
|
65
74
|
- `sql_gen.py` is pure (no SDK, no I/O); diff direction: `FieldChange.old` = stored (target), `.new` = live
|
|
66
75
|
- FK refs in SQL use same catalog as the source table (`ref_schema` + `ref_table` from model)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: databricks-schema
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Databricks Unity Catalog schema extractor
|
|
5
5
|
Project-URL: Homepage, https://github.com/frizzleqq/databricks-schema
|
|
6
6
|
Project-URL: Repository, https://github.com/frizzleqq/databricks-schema
|
|
@@ -18,14 +18,16 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
|
-
Requires-Dist: databricks-sdk>=0.
|
|
21
|
+
Requires-Dist: databricks-sdk>=0.121.0
|
|
22
22
|
Requires-Dist: pydantic>=2.0
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Provides-Extra: dbml
|
|
25
|
+
Requires-Dist: pydbml<2,>=1.2.1; extra == 'dbml'
|
|
24
26
|
Description-Content-Type: text/markdown
|
|
25
27
|
|
|
26
28
|
# databricks-schema
|
|
27
29
|
|
|
28
|
-
A CLI tool and Python library that uses the Databricks SDK to extract and diff Unity Catalog schemas
|
|
30
|
+
A CLI tool and Python library that uses the Databricks SDK to extract and diff Unity Catalog schemas as YAML, JSON or [DBML](https://dbml.dbdiagram.io/docs/) files. It can also generate Databricks Spark SQL to apply schema changes across catalogs.
|
|
29
31
|
|
|
30
32
|
## Getting Started
|
|
31
33
|
|
|
@@ -129,12 +131,18 @@ name: main
|
|
|
129
131
|
comment: Main production schema
|
|
130
132
|
tags:
|
|
131
133
|
env: prod
|
|
134
|
+
grants:
|
|
135
|
+
analysts:
|
|
136
|
+
- USE_SCHEMA
|
|
132
137
|
tables:
|
|
133
138
|
- name: users
|
|
134
139
|
table_type: MANAGED
|
|
135
140
|
comment: User accounts
|
|
136
141
|
tags:
|
|
137
142
|
domain: identity
|
|
143
|
+
grants:
|
|
144
|
+
analysts:
|
|
145
|
+
- SELECT
|
|
138
146
|
columns:
|
|
139
147
|
- name: id
|
|
140
148
|
data_type: bigint
|
|
@@ -158,6 +166,64 @@ tables:
|
|
|
158
166
|
- id
|
|
159
167
|
```
|
|
160
168
|
|
|
169
|
+
### DBML
|
|
170
|
+
|
|
171
|
+
Writing DBML works out of the box; reading it back (`diff`, `generate-sql`, `validate`, `diff-files`, `catalog_from_dbml`) needs the optional `dbml` extra, which installs [PyDBML](https://github.com/Vanderhoof/PyDBML):
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
pip install 'databricks-schema[dbml]'
|
|
175
|
+
uv tool install 'databricks-schema[dbml]'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Without it, those commands exit `2` with a message saying how to install it.
|
|
179
|
+
|
|
180
|
+
Use `--format dbml` to write the catalog as a single [DBML](https://dbml.dbdiagram.io/docs/) document — `{output-dir}/{catalog}.dbml` with `--output-dir`, or stdout without. Unlike YAML/JSON, DBML is one file per catalog, so that foreign keys between schemas resolve within the file. Paste it into [dbdiagram.io](https://dbdiagram.io) for an ER diagram, or point `diff`, `generate-sql`, `validate` and `diff-files` at the directory just like a YAML/JSON one.
|
|
181
|
+
|
|
182
|
+
```dbml
|
|
183
|
+
Project "prod_catalog" {
|
|
184
|
+
database_type: 'Databricks'
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
TableGroup "main" [note: 'Main production schema'] {
|
|
188
|
+
"main"."users"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Table "main"."users" {
|
|
192
|
+
"id" bigint [not null, note: 'Primary key']
|
|
193
|
+
"email" string
|
|
194
|
+
"org_id" bigint
|
|
195
|
+
"settings" "struct<theme:string,beta:boolean>"
|
|
196
|
+
|
|
197
|
+
indexes {
|
|
198
|
+
("id") [pk, name: 'pk_users']
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Note: '''User accounts
|
|
202
|
+
|
|
203
|
+
@table_type: MANAGED
|
|
204
|
+
@tags: {"domain": "identity"}'''
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
Ref "fk_org": "main"."users"."org_id" > "orgs"."organizations"."id"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
How the model maps to DBML:
|
|
211
|
+
|
|
212
|
+
| Model | DBML |
|
|
213
|
+
|---|---|
|
|
214
|
+
| Catalog | `Project` (comment in its `Note`) |
|
|
215
|
+
| Schema | `TableGroup` named after the schema (comment in its `note`); written for every schema, so empty schemas survive |
|
|
216
|
+
| Table / column comment | `Note` / `note:` |
|
|
217
|
+
| `nullable: false` | `not null` |
|
|
218
|
+
| Primary key | `indexes { (...) [pk, name: '...'] }` |
|
|
219
|
+
| Foreign key | `Ref "<name>": ... > ...` |
|
|
220
|
+
| Complex types (`struct<...>`, `map<...>`) | double-quoted type |
|
|
221
|
+
| `tags`, `grants`, `owner`, `table_type`, `storage_location` | trailing `@key: value` lines in the note (`@tags` and `@grants` are JSON objects) |
|
|
222
|
+
|
|
223
|
+
Everything round-trips: reading a written file gives back exactly the extracted model. Hand-written DBML (e.g. exported from dbdiagram) is accepted too — tables without a schema land in `public`, column `pk` and inline `ref:` settings are understood, and constructs with no Unity Catalog equivalent (enums, sticky notes, other indexes, defaults, many-to-many refs, TableGroups that don't match a schema) are ignored. A directory may hold several `.dbml` files, but each schema may be defined in only one of them.
|
|
224
|
+
|
|
225
|
+
### Catalog document
|
|
226
|
+
|
|
161
227
|
Without `--output-dir`, `extract` prints a single `Catalog` document to stdout instead — the same schema shape nested under a `schemas` list, with the catalog name as a top-level field:
|
|
162
228
|
|
|
163
229
|
```yaml
|
|
@@ -231,12 +297,24 @@ Include Unity Catalog tags in the output (extra API calls per entity; excluded b
|
|
|
231
297
|
databricks-schema extract <catalog> --output-dir ./schemas/ --include-tags
|
|
232
298
|
```
|
|
233
299
|
|
|
300
|
+
Include grants (principal → privileges) on schemas and tables. Only grants made directly on each object are extracted — privileges inherited from a parent catalog or schema are left out, so they don't show up repeatedly on every child (one extra API call per schema/table; excluded by default):
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
databricks-schema extract <catalog> --output-dir ./schemas/ --include-grants
|
|
304
|
+
```
|
|
305
|
+
|
|
234
306
|
Include additional metadata (`owner`, `storage_location`) in the output:
|
|
235
307
|
|
|
236
308
|
```bash
|
|
237
309
|
databricks-schema extract <catalog> --output-dir ./schemas/ --include-metadata
|
|
238
310
|
```
|
|
239
311
|
|
|
312
|
+
Write a single DBML file (`./schemas/<catalog>.dbml`) instead — see [DBML](#dbml):
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
databricks-schema extract <catalog> --output-dir ./schemas/ --format dbml
|
|
316
|
+
```
|
|
317
|
+
|
|
240
318
|
Control the number of parallel workers (default: 4), useful for large schemas with many tags:
|
|
241
319
|
|
|
242
320
|
```bash
|
|
@@ -245,7 +323,7 @@ databricks-schema extract <catalog> --output-dir ./schemas/ --workers 8
|
|
|
245
323
|
|
|
246
324
|
### `diff`
|
|
247
325
|
|
|
248
|
-
Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML or
|
|
326
|
+
Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML, JSON or DBML, not mixed):
|
|
249
327
|
|
|
250
328
|
```bash
|
|
251
329
|
databricks-schema diff <catalog> ./schemas/
|
|
@@ -289,6 +367,12 @@ Include `owner` in the comparison (excluded by default):
|
|
|
289
367
|
databricks-schema diff <catalog> ./schemas/ --include-metadata
|
|
290
368
|
```
|
|
291
369
|
|
|
370
|
+
Include direct grants in the comparison (excluded by default; without it, grants in the files are ignored):
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
databricks-schema diff <catalog> ./schemas/ --include-grants
|
|
374
|
+
```
|
|
375
|
+
|
|
292
376
|
Get the same comparison as structured JSON or YAML instead of the `+`/`-`/`~` tree (exit codes unchanged) — useful for scripts or agents:
|
|
293
377
|
|
|
294
378
|
```bash
|
|
@@ -298,7 +382,7 @@ databricks-schema diff <catalog> ./schemas/ --format yaml
|
|
|
298
382
|
|
|
299
383
|
### `generate-sql`
|
|
300
384
|
|
|
301
|
-
Generate Databricks Spark SQL statements to bring the live catalog in line with local schema files (format auto-detected
|
|
385
|
+
Generate Databricks Spark SQL statements to bring the live catalog in line with local schema files (format auto-detected — YAML, JSON or DBML, not mixed). Statements are printed to stdout by default:
|
|
302
386
|
|
|
303
387
|
```bash
|
|
304
388
|
databricks-schema generate-sql <catalog> ./schemas/
|
|
@@ -310,7 +394,7 @@ Write one `.sql` file per schema to a directory instead:
|
|
|
310
394
|
databricks-schema generate-sql <catalog> ./schemas/ --output-dir ./migrations/
|
|
311
395
|
```
|
|
312
396
|
|
|
313
|
-
Destructive statements (`DROP SCHEMA`, `DROP TABLE`, `DROP COLUMN`) are emitted as SQL comments by default. Pass `--allow-drop` to emit them as executable statements:
|
|
397
|
+
Destructive statements (`DROP SCHEMA`, `DROP TABLE`, `DROP COLUMN`, `REVOKE`) are emitted as SQL comments by default. Pass `--allow-drop` to emit them as executable statements:
|
|
314
398
|
|
|
315
399
|
```bash
|
|
316
400
|
databricks-schema generate-sql <catalog> ./schemas/ --allow-drop
|
|
@@ -334,6 +418,12 @@ Include `owner` in the comparison (excluded by default):
|
|
|
334
418
|
databricks-schema generate-sql <catalog> ./schemas/ --include-metadata
|
|
335
419
|
```
|
|
336
420
|
|
|
421
|
+
Include direct grants in the comparison, emitting `GRANT` for privileges in the files but not live and (commented-out unless `--allow-drop`) `REVOKE` for privileges live but not in the files:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
databricks-schema generate-sql <catalog> ./schemas/ --include-grants
|
|
425
|
+
```
|
|
426
|
+
|
|
337
427
|
### `validate`
|
|
338
428
|
|
|
339
429
|
Validate local schema files for structural integrity (no Databricks connection needed):
|
|
@@ -347,7 +437,7 @@ Pass `--format json` or `--format yaml` for an `{"issues": [...]}` document inst
|
|
|
347
437
|
### `diff-files`
|
|
348
438
|
|
|
349
439
|
Compare two local directories of schema files (no Databricks connection needed). Same output,
|
|
350
|
-
exit codes, `--format json`/`yaml`, and `--schema` / `--include-metadata` flags as `diff`:
|
|
440
|
+
exit codes, `--format json`/`yaml`, and `--schema` / `--include-metadata` / `--include-grants` flags as `diff`:
|
|
351
441
|
|
|
352
442
|
```bash
|
|
353
443
|
databricks-schema diff-files ./schemas-prod/ ./schemas-test/
|
|
@@ -382,6 +472,10 @@ catalog = extractor.extract_catalog("my_catalog", schema_filter=["main", "raw"])
|
|
|
382
472
|
# Include Unity Catalog tags (extra API calls per entity; excluded by default)
|
|
383
473
|
catalog = extractor.extract_catalog("my_catalog", include_tags=True)
|
|
384
474
|
|
|
475
|
+
# Include direct (non-inherited) grants on schemas and tables; compare them with
|
|
476
|
+
# diff_schemas(..., include_grants=True) and friends
|
|
477
|
+
catalog = extractor.extract_catalog("my_catalog", include_grants=True)
|
|
478
|
+
|
|
385
479
|
# Include additional metadata (owner, storage_location)
|
|
386
480
|
catalog = extractor.extract_catalog("my_catalog", include_metadata=True)
|
|
387
481
|
|
|
@@ -392,7 +486,12 @@ yaml_text = catalog_to_yaml(catalog)
|
|
|
392
486
|
schema = schema_from_yaml(open("schemas/main.yaml").read())
|
|
393
487
|
print(schema.tables[0].columns)
|
|
394
488
|
|
|
395
|
-
#
|
|
489
|
+
# DBML: one document per catalog
|
|
490
|
+
from databricks_schema import catalog_from_dbml, catalog_to_dbml
|
|
491
|
+
Path("schemas/my_catalog.dbml").write_text(catalog_to_dbml(catalog))
|
|
492
|
+
catalog = catalog_from_dbml(Path("schemas/my_catalog.dbml").read_text())
|
|
493
|
+
|
|
494
|
+
# Compare live catalog against local YAML files (fmt="json" / fmt="dbml" for other formats)
|
|
396
495
|
result = diff_catalog_with_dir(catalog, Path("./schemas/"))
|
|
397
496
|
if result.has_changes:
|
|
398
497
|
for schema_diff in result.schemas:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# databricks-schema
|
|
2
2
|
|
|
3
|
-
A CLI tool and Python library that uses the Databricks SDK to extract and diff Unity Catalog schemas
|
|
3
|
+
A CLI tool and Python library that uses the Databricks SDK to extract and diff Unity Catalog schemas as YAML, JSON or [DBML](https://dbml.dbdiagram.io/docs/) files. It can also generate Databricks Spark SQL to apply schema changes across catalogs.
|
|
4
4
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
@@ -104,12 +104,18 @@ name: main
|
|
|
104
104
|
comment: Main production schema
|
|
105
105
|
tags:
|
|
106
106
|
env: prod
|
|
107
|
+
grants:
|
|
108
|
+
analysts:
|
|
109
|
+
- USE_SCHEMA
|
|
107
110
|
tables:
|
|
108
111
|
- name: users
|
|
109
112
|
table_type: MANAGED
|
|
110
113
|
comment: User accounts
|
|
111
114
|
tags:
|
|
112
115
|
domain: identity
|
|
116
|
+
grants:
|
|
117
|
+
analysts:
|
|
118
|
+
- SELECT
|
|
113
119
|
columns:
|
|
114
120
|
- name: id
|
|
115
121
|
data_type: bigint
|
|
@@ -133,6 +139,64 @@ tables:
|
|
|
133
139
|
- id
|
|
134
140
|
```
|
|
135
141
|
|
|
142
|
+
### DBML
|
|
143
|
+
|
|
144
|
+
Writing DBML works out of the box; reading it back (`diff`, `generate-sql`, `validate`, `diff-files`, `catalog_from_dbml`) needs the optional `dbml` extra, which installs [PyDBML](https://github.com/Vanderhoof/PyDBML):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install 'databricks-schema[dbml]'
|
|
148
|
+
uv tool install 'databricks-schema[dbml]'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Without it, those commands exit `2` with a message saying how to install it.
|
|
152
|
+
|
|
153
|
+
Use `--format dbml` to write the catalog as a single [DBML](https://dbml.dbdiagram.io/docs/) document — `{output-dir}/{catalog}.dbml` with `--output-dir`, or stdout without. Unlike YAML/JSON, DBML is one file per catalog, so that foreign keys between schemas resolve within the file. Paste it into [dbdiagram.io](https://dbdiagram.io) for an ER diagram, or point `diff`, `generate-sql`, `validate` and `diff-files` at the directory just like a YAML/JSON one.
|
|
154
|
+
|
|
155
|
+
```dbml
|
|
156
|
+
Project "prod_catalog" {
|
|
157
|
+
database_type: 'Databricks'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
TableGroup "main" [note: 'Main production schema'] {
|
|
161
|
+
"main"."users"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Table "main"."users" {
|
|
165
|
+
"id" bigint [not null, note: 'Primary key']
|
|
166
|
+
"email" string
|
|
167
|
+
"org_id" bigint
|
|
168
|
+
"settings" "struct<theme:string,beta:boolean>"
|
|
169
|
+
|
|
170
|
+
indexes {
|
|
171
|
+
("id") [pk, name: 'pk_users']
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Note: '''User accounts
|
|
175
|
+
|
|
176
|
+
@table_type: MANAGED
|
|
177
|
+
@tags: {"domain": "identity"}'''
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Ref "fk_org": "main"."users"."org_id" > "orgs"."organizations"."id"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
How the model maps to DBML:
|
|
184
|
+
|
|
185
|
+
| Model | DBML |
|
|
186
|
+
|---|---|
|
|
187
|
+
| Catalog | `Project` (comment in its `Note`) |
|
|
188
|
+
| Schema | `TableGroup` named after the schema (comment in its `note`); written for every schema, so empty schemas survive |
|
|
189
|
+
| Table / column comment | `Note` / `note:` |
|
|
190
|
+
| `nullable: false` | `not null` |
|
|
191
|
+
| Primary key | `indexes { (...) [pk, name: '...'] }` |
|
|
192
|
+
| Foreign key | `Ref "<name>": ... > ...` |
|
|
193
|
+
| Complex types (`struct<...>`, `map<...>`) | double-quoted type |
|
|
194
|
+
| `tags`, `grants`, `owner`, `table_type`, `storage_location` | trailing `@key: value` lines in the note (`@tags` and `@grants` are JSON objects) |
|
|
195
|
+
|
|
196
|
+
Everything round-trips: reading a written file gives back exactly the extracted model. Hand-written DBML (e.g. exported from dbdiagram) is accepted too — tables without a schema land in `public`, column `pk` and inline `ref:` settings are understood, and constructs with no Unity Catalog equivalent (enums, sticky notes, other indexes, defaults, many-to-many refs, TableGroups that don't match a schema) are ignored. A directory may hold several `.dbml` files, but each schema may be defined in only one of them.
|
|
197
|
+
|
|
198
|
+
### Catalog document
|
|
199
|
+
|
|
136
200
|
Without `--output-dir`, `extract` prints a single `Catalog` document to stdout instead — the same schema shape nested under a `schemas` list, with the catalog name as a top-level field:
|
|
137
201
|
|
|
138
202
|
```yaml
|
|
@@ -206,12 +270,24 @@ Include Unity Catalog tags in the output (extra API calls per entity; excluded b
|
|
|
206
270
|
databricks-schema extract <catalog> --output-dir ./schemas/ --include-tags
|
|
207
271
|
```
|
|
208
272
|
|
|
273
|
+
Include grants (principal → privileges) on schemas and tables. Only grants made directly on each object are extracted — privileges inherited from a parent catalog or schema are left out, so they don't show up repeatedly on every child (one extra API call per schema/table; excluded by default):
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
databricks-schema extract <catalog> --output-dir ./schemas/ --include-grants
|
|
277
|
+
```
|
|
278
|
+
|
|
209
279
|
Include additional metadata (`owner`, `storage_location`) in the output:
|
|
210
280
|
|
|
211
281
|
```bash
|
|
212
282
|
databricks-schema extract <catalog> --output-dir ./schemas/ --include-metadata
|
|
213
283
|
```
|
|
214
284
|
|
|
285
|
+
Write a single DBML file (`./schemas/<catalog>.dbml`) instead — see [DBML](#dbml):
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
databricks-schema extract <catalog> --output-dir ./schemas/ --format dbml
|
|
289
|
+
```
|
|
290
|
+
|
|
215
291
|
Control the number of parallel workers (default: 4), useful for large schemas with many tags:
|
|
216
292
|
|
|
217
293
|
```bash
|
|
@@ -220,7 +296,7 @@ databricks-schema extract <catalog> --output-dir ./schemas/ --workers 8
|
|
|
220
296
|
|
|
221
297
|
### `diff`
|
|
222
298
|
|
|
223
|
-
Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML or
|
|
299
|
+
Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML, JSON or DBML, not mixed):
|
|
224
300
|
|
|
225
301
|
```bash
|
|
226
302
|
databricks-schema diff <catalog> ./schemas/
|
|
@@ -264,6 +340,12 @@ Include `owner` in the comparison (excluded by default):
|
|
|
264
340
|
databricks-schema diff <catalog> ./schemas/ --include-metadata
|
|
265
341
|
```
|
|
266
342
|
|
|
343
|
+
Include direct grants in the comparison (excluded by default; without it, grants in the files are ignored):
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
databricks-schema diff <catalog> ./schemas/ --include-grants
|
|
347
|
+
```
|
|
348
|
+
|
|
267
349
|
Get the same comparison as structured JSON or YAML instead of the `+`/`-`/`~` tree (exit codes unchanged) — useful for scripts or agents:
|
|
268
350
|
|
|
269
351
|
```bash
|
|
@@ -273,7 +355,7 @@ databricks-schema diff <catalog> ./schemas/ --format yaml
|
|
|
273
355
|
|
|
274
356
|
### `generate-sql`
|
|
275
357
|
|
|
276
|
-
Generate Databricks Spark SQL statements to bring the live catalog in line with local schema files (format auto-detected
|
|
358
|
+
Generate Databricks Spark SQL statements to bring the live catalog in line with local schema files (format auto-detected — YAML, JSON or DBML, not mixed). Statements are printed to stdout by default:
|
|
277
359
|
|
|
278
360
|
```bash
|
|
279
361
|
databricks-schema generate-sql <catalog> ./schemas/
|
|
@@ -285,7 +367,7 @@ Write one `.sql` file per schema to a directory instead:
|
|
|
285
367
|
databricks-schema generate-sql <catalog> ./schemas/ --output-dir ./migrations/
|
|
286
368
|
```
|
|
287
369
|
|
|
288
|
-
Destructive statements (`DROP SCHEMA`, `DROP TABLE`, `DROP COLUMN`) are emitted as SQL comments by default. Pass `--allow-drop` to emit them as executable statements:
|
|
370
|
+
Destructive statements (`DROP SCHEMA`, `DROP TABLE`, `DROP COLUMN`, `REVOKE`) are emitted as SQL comments by default. Pass `--allow-drop` to emit them as executable statements:
|
|
289
371
|
|
|
290
372
|
```bash
|
|
291
373
|
databricks-schema generate-sql <catalog> ./schemas/ --allow-drop
|
|
@@ -309,6 +391,12 @@ Include `owner` in the comparison (excluded by default):
|
|
|
309
391
|
databricks-schema generate-sql <catalog> ./schemas/ --include-metadata
|
|
310
392
|
```
|
|
311
393
|
|
|
394
|
+
Include direct grants in the comparison, emitting `GRANT` for privileges in the files but not live and (commented-out unless `--allow-drop`) `REVOKE` for privileges live but not in the files:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
databricks-schema generate-sql <catalog> ./schemas/ --include-grants
|
|
398
|
+
```
|
|
399
|
+
|
|
312
400
|
### `validate`
|
|
313
401
|
|
|
314
402
|
Validate local schema files for structural integrity (no Databricks connection needed):
|
|
@@ -322,7 +410,7 @@ Pass `--format json` or `--format yaml` for an `{"issues": [...]}` document inst
|
|
|
322
410
|
### `diff-files`
|
|
323
411
|
|
|
324
412
|
Compare two local directories of schema files (no Databricks connection needed). Same output,
|
|
325
|
-
exit codes, `--format json`/`yaml`, and `--schema` / `--include-metadata` flags as `diff`:
|
|
413
|
+
exit codes, `--format json`/`yaml`, and `--schema` / `--include-metadata` / `--include-grants` flags as `diff`:
|
|
326
414
|
|
|
327
415
|
```bash
|
|
328
416
|
databricks-schema diff-files ./schemas-prod/ ./schemas-test/
|
|
@@ -357,6 +445,10 @@ catalog = extractor.extract_catalog("my_catalog", schema_filter=["main", "raw"])
|
|
|
357
445
|
# Include Unity Catalog tags (extra API calls per entity; excluded by default)
|
|
358
446
|
catalog = extractor.extract_catalog("my_catalog", include_tags=True)
|
|
359
447
|
|
|
448
|
+
# Include direct (non-inherited) grants on schemas and tables; compare them with
|
|
449
|
+
# diff_schemas(..., include_grants=True) and friends
|
|
450
|
+
catalog = extractor.extract_catalog("my_catalog", include_grants=True)
|
|
451
|
+
|
|
360
452
|
# Include additional metadata (owner, storage_location)
|
|
361
453
|
catalog = extractor.extract_catalog("my_catalog", include_metadata=True)
|
|
362
454
|
|
|
@@ -367,7 +459,12 @@ yaml_text = catalog_to_yaml(catalog)
|
|
|
367
459
|
schema = schema_from_yaml(open("schemas/main.yaml").read())
|
|
368
460
|
print(schema.tables[0].columns)
|
|
369
461
|
|
|
370
|
-
#
|
|
462
|
+
# DBML: one document per catalog
|
|
463
|
+
from databricks_schema import catalog_from_dbml, catalog_to_dbml
|
|
464
|
+
Path("schemas/my_catalog.dbml").write_text(catalog_to_dbml(catalog))
|
|
465
|
+
catalog = catalog_from_dbml(Path("schemas/my_catalog.dbml").read_text())
|
|
466
|
+
|
|
467
|
+
# Compare live catalog against local YAML files (fmt="json" / fmt="dbml" for other formats)
|
|
371
468
|
result = diff_catalog_with_dir(catalog, Path("./schemas/"))
|
|
372
469
|
if result.has_changes:
|
|
373
470
|
for schema_diff in result.schemas:
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
from databricks.sdk.service.catalog import TableType
|
|
2
2
|
|
|
3
|
+
from databricks_schema.dbml_io import (
|
|
4
|
+
DbmlDependencyError,
|
|
5
|
+
DbmlParseError,
|
|
6
|
+
catalog_from_dbml,
|
|
7
|
+
catalog_to_dbml,
|
|
8
|
+
schema_from_dbml,
|
|
9
|
+
schema_to_dbml,
|
|
10
|
+
schemas_from_dbml,
|
|
11
|
+
)
|
|
3
12
|
from databricks_schema.diff import (
|
|
4
13
|
CatalogDiff,
|
|
5
14
|
ColumnDiff,
|
|
@@ -20,6 +29,7 @@ from databricks_schema.yaml_io import (
|
|
|
20
29
|
catalog_from_yaml,
|
|
21
30
|
catalog_to_json,
|
|
22
31
|
catalog_to_yaml,
|
|
32
|
+
load_schema_dir,
|
|
23
33
|
schema_from_json,
|
|
24
34
|
schema_from_yaml,
|
|
25
35
|
schema_to_json,
|
|
@@ -32,6 +42,8 @@ __all__ = [
|
|
|
32
42
|
"CatalogExtractor",
|
|
33
43
|
"Column",
|
|
34
44
|
"ColumnDiff",
|
|
45
|
+
"DbmlDependencyError",
|
|
46
|
+
"DbmlParseError",
|
|
35
47
|
"FieldChange",
|
|
36
48
|
"ForeignKey",
|
|
37
49
|
"PrimaryKey",
|
|
@@ -42,18 +54,24 @@ __all__ = [
|
|
|
42
54
|
"TableType",
|
|
43
55
|
"ValidationIssue",
|
|
44
56
|
"ValidationResult",
|
|
57
|
+
"catalog_from_dbml",
|
|
45
58
|
"catalog_from_json",
|
|
46
59
|
"catalog_from_yaml",
|
|
60
|
+
"catalog_to_dbml",
|
|
47
61
|
"catalog_to_json",
|
|
48
62
|
"catalog_to_yaml",
|
|
49
63
|
"diff_catalog_with_dir",
|
|
50
64
|
"diff_catalogs",
|
|
51
65
|
"diff_schema_dirs",
|
|
52
66
|
"diff_schemas",
|
|
67
|
+
"load_schema_dir",
|
|
53
68
|
"schema_diff_to_sql",
|
|
69
|
+
"schema_from_dbml",
|
|
54
70
|
"schema_from_json",
|
|
55
71
|
"schema_from_yaml",
|
|
72
|
+
"schema_to_dbml",
|
|
56
73
|
"schema_to_json",
|
|
57
74
|
"schema_to_yaml",
|
|
75
|
+
"schemas_from_dbml",
|
|
58
76
|
"validate_schemas",
|
|
59
77
|
]
|