databricks-schema 0.7.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.
Files changed (34) hide show
  1. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/.gitignore +0 -1
  2. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/CLAUDE.md +19 -9
  3. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/PKG-INFO +219 -91
  4. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/README.md +214 -88
  5. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/__init__.py +18 -0
  6. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/cli.py +353 -86
  7. databricks_schema-0.10.0/databricks_schema/dbml_io.py +433 -0
  8. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/diff.py +92 -43
  9. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/extractor.py +58 -3
  10. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/models.py +2 -0
  11. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/sql_gen.py +45 -1
  12. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/yaml_io.py +42 -1
  13. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/pyproject.toml +9 -3
  14. databricks_schema-0.10.0/skills/databricks-schema-cli/SKILL.md +218 -0
  15. databricks_schema-0.10.0/tests/test_cli.py +527 -0
  16. databricks_schema-0.10.0/tests/test_dbml_io.py +316 -0
  17. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_diff.py +122 -0
  18. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_extractor.py +116 -2
  19. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_sql_gen.py +50 -0
  20. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_yaml_io.py +23 -0
  21. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/uv.lock +41 -9
  22. databricks_schema-0.7.0/.claude/skills/databricks-schema-cli/SKILL.md +0 -203
  23. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/.github/workflows/ci.yml +0 -0
  24. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/.github/workflows/release.yml +0 -0
  25. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/.python-version +0 -0
  26. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/LICENSE +0 -0
  27. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/databricks_schema/validate.py +0 -0
  28. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/examples/01_extract_schema.py +0 -0
  29. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/examples/02_save_load_yaml.py +0 -0
  30. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/examples/03_diff_schemas.py +0 -0
  31. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/examples/04_generate_sql.py +0 -0
  32. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/__init__.py +0 -0
  33. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_models.py +0 -0
  34. {databricks_schema-0.7.0 → databricks_schema-0.10.0}/tests/test_validate.py +0 -0
@@ -14,4 +14,3 @@ wheels/
14
14
 
15
15
  # Claude Code
16
16
  .claude/*
17
- !.claude/skills/
@@ -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,15 +30,17 @@ 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
35
- .claude/skills/databricks-schema-cli/SKILL.md # Agent Skill: teaches an AI agent to drive the CLI
37
+ skills/databricks-schema-cli/SKILL.md # Agent Skill: teaches an AI agent to drive the CLI
36
38
  ```
37
39
 
38
40
  ## Conventions
39
41
 
40
42
  - Package manager: `uv`; do not use `pip` directly
43
+ - Version bumps: use `uv version --bump <major|minor|patch>` (updates `pyproject.toml` and `uv.lock` together) — do not hand-edit the version string
41
44
  - Ruff: select E, W, F, I, UP; line-length 100; target py311
42
45
  - Use `X | None` not `Optional[X]`; use `datetime.UTC` not `timezone.utc`
43
46
  - `from __future__ import annotations` in every module
@@ -48,20 +51,27 @@ tests/
48
51
  - Column order in YAML = SDK position (None → 9999)
49
52
  - `_strip_empty`: removes `None` and empty `dict`/`list`; preserves `False`, `0`, empty strings
50
53
  - `yaml_io.py` has parallel YAML and JSON functions (`schema_to_yaml`/`schema_to_json`, etc.)
51
- - `diff_catalog_with_dir` accepts `fmt: Literal["yaml", "json"] = "yaml"` to select file format
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)
52
60
  - CLI: catalog is a required positional argument (not a flag)
53
- - `extract` `--format`/`-f` selects output format (`yaml` default, `json` opt-in); dest is `fmt`
54
- - `diff` auto-detects format from files present in the directory; exits 2 on mixed YAML+JSON
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
55
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
56
64
  - `TableType` is re-exported from `databricks.sdk.service.catalog` — do not redefine it
57
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
58
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`
59
69
  - `--quiet`/`-q` flag (on `extract`, `diff`, `generate-sql`, `diff-files`) raises the log level to suppress progress messages; errors always print
60
70
  - `diff` command exits 0 (no changes) or 1 (differences found) — useful in CI
61
71
  - Diff result types are dataclasses (not Pydantic); comparison functions are pure (no SDK calls)
62
- - `generate-sql` auto-detects format from files; exits 2 on mixed YAML+JSON or empty directory
72
+ - `generate-sql` auto-detects format from files; exits 2 on mixed formats or empty directory; invalid DBML exits 2
63
73
  - `generate-sql` destructive statements commented out by default; `--allow-drop` emits real DROPs
64
74
  - `sql_gen.py` is pure (no SDK, no I/O); diff direction: `FieldChange.old` = stored (target), `.new` = live
65
75
  - FK refs in SQL use same catalog as the source table (`ref_schema` + `ref_table` from model)
66
76
  - Unsupported field changes (`table_type`) emit `-- TODO: unsupported change: …` comments
67
- - When CLI commands, flags, or output shape change, update `.claude/skills/databricks-schema-cli/SKILL.md` to match
77
+ - When CLI commands, flags, or output shape change, update `skills/databricks-schema-cli/SKILL.md` to match
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: databricks-schema
3
- Version: 0.7.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.64.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 as YAML files. It can also generate Databricks Spark SQL to apply schema changes across catalogs.
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
 
@@ -53,23 +55,94 @@ databricks-schema diff test_catalog ./schemas/
53
55
  databricks-schema generate-sql test_catalog ./schemas/ --output-dir ./migrations/
54
56
  ```
55
57
 
56
- The YAML files act as a version-controllable snapshot of your schema. The `diff` command exits with code `1` when differences are found, making it suitable for CI pipelines.
58
+ ## Databricks Authentication
59
+
60
+ The tool uses the [Databricks SDK](https://github.com/databricks/databricks-sdk-py) for auth. Configure it via environment variables:
61
+
62
+ ```bash
63
+ export DATABRICKS_HOST=https://<databricks-url>
64
+ export DATABRICKS_TOKEN=<your-personal-access-token>
65
+ ```
66
+
67
+ Or use a [Databricks CLI profile](https://docs.databricks.com/dev-tools/cli/profiles.html) (`~/.databrickscfg`) — the SDK will pick it up automatically.
68
+
69
+ You can also pass credentials directly as flags using `--host` / `--token` below.
70
+
71
+ ## Development
72
+
73
+ Requires Python 3.11+ and [uv](https://github.com/astral-sh/uv).
74
+
75
+ ```bash
76
+ git clone https://github.com/frizzleqq/databricks-schema
77
+ cd databricks-schema
78
+ uv sync --all-groups # includes pytest and ruff
79
+ ```
80
+
81
+ ```bash
82
+ # Run tests
83
+ uv run pytest
84
+
85
+ # Lint
86
+ uv run ruff check databricks_schema/ tests/
87
+
88
+ # Format
89
+ uv run ruff format databricks_schema/ tests/
90
+ ```
91
+
92
+ ## Agent Skill
93
+
94
+ This repo ships an [Agent Skill](https://code.claude.com/docs/en/skills) at
95
+ [`skills/databricks-schema-cli/SKILL.md`](skills/databricks-schema-cli/SKILL.md)
96
+ that teaches an AI coding agent (e.g. Claude Code) how to use the `databricks-schema` CLI to
97
+ explore, snapshot, diff, and generate migration SQL for a live Unity Catalog. Copy the skill into
98
+ your own project and it can use the CLI directly instead of writing ad-hoc Databricks SDK calls
99
+ or queries.
100
+
101
+ The skill assumes the `databricks-schema` command is on `PATH`. Install it as a standalone tool
102
+ with [uv](https://github.com/astral-sh/uv):
103
+
104
+ ```bash
105
+ uv tool install databricks-schema
106
+ ```
107
+
108
+ To use the skill in your own project without cloning this repo, download it directly into your
109
+ project's `.claude/skills/`:
110
+
111
+ ```bash
112
+ mkdir -p .claude/skills/databricks-schema-cli
113
+ curl -o .claude/skills/databricks-schema-cli/SKILL.md \
114
+ https://raw.githubusercontent.com/frizzleqq/databricks-schema/main/skills/databricks-schema-cli/SKILL.md
115
+ ```
116
+
117
+ For agentic harnesses other than Claude Code that look under `.agents/skills/` instead:
118
+
119
+ ```bash
120
+ mkdir -p .agents/skills/databricks-schema-cli
121
+ curl -o .agents/skills/databricks-schema-cli/SKILL.md \
122
+ https://raw.githubusercontent.com/frizzleqq/databricks-schema/main/skills/databricks-schema-cli/SKILL.md
123
+ ```
57
124
 
58
125
  ## Output Format
59
126
 
60
- Each schema is written to `{output-dir}/{schema-name}.yaml`. Fields with no value (null comments, empty tag dicts, empty FK lists) are omitted. Use `--format json` to write `.json` files with the same structure.
127
+ Each schema is written to `{output-dir}/{schema-name}.yaml` if `--output-dir` is specified. Fields with no value (null comments, empty tag dicts, empty FK lists) are omitted. Use `--format json` to write `.json` files with the same structure.
61
128
 
62
129
  ```yaml
63
130
  name: main
64
131
  comment: Main production schema
65
132
  tags:
66
133
  env: prod
134
+ grants:
135
+ analysts:
136
+ - USE_SCHEMA
67
137
  tables:
68
138
  - name: users
69
139
  table_type: MANAGED
70
140
  comment: User accounts
71
141
  tags:
72
142
  domain: identity
143
+ grants:
144
+ analysts:
145
+ - SELECT
73
146
  columns:
74
147
  - name: id
75
148
  data_type: bigint
@@ -93,74 +166,74 @@ tables:
93
166
  - id
94
167
  ```
95
168
 
96
- 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:
97
-
98
- ```yaml
99
- name: prod_catalog
100
- schemas:
101
- - name: main
102
- comment: Main production schema
103
- tags:
104
- env: prod
105
- tables: [...]
106
- ```
107
-
108
- ## Authentication
169
+ ### DBML
109
170
 
110
- The tool uses the [Databricks SDK](https://github.com/databricks/databricks-sdk-py) for auth. Configure it via environment variables:
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):
111
172
 
112
173
  ```bash
113
- export DATABRICKS_HOST=https://<databricks-url>
114
- export DATABRICKS_TOKEN=<your-personal-access-token>
174
+ pip install 'databricks-schema[dbml]'
175
+ uv tool install 'databricks-schema[dbml]'
115
176
  ```
116
177
 
117
- Or use a [Databricks CLI profile](https://docs.databricks.com/dev-tools/cli/profiles.html) (`~/.databrickscfg`) — the SDK will pick it up automatically.
178
+ Without it, those commands exit `2` with a message saying how to install it.
118
179
 
119
- You can also pass credentials directly as flags (see `--host` / `--token` below).
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.
120
181
 
121
- ## Agent Skill
182
+ ```dbml
183
+ Project "prod_catalog" {
184
+ database_type: 'Databricks'
185
+ }
122
186
 
123
- This repo ships an [Agent Skill](https://code.claude.com/docs/en/skills) at
124
- [`.claude/skills/databricks-schema-cli/SKILL.md`](.claude/skills/databricks-schema-cli/SKILL.md)
125
- that teaches an AI coding agent (e.g. Claude Code) how to use the `databricks-schema` CLI to
126
- explore, snapshot, diff, and generate migration SQL for a live Unity Catalog. Point an agent at
127
- this repo (or copy the skill into your own project) and it can use the CLI directly instead of
128
- writing ad-hoc Databricks SDK calls.
187
+ TableGroup "main" [note: 'Main production schema'] {
188
+ "main"."users"
189
+ }
129
190
 
130
- The skill assumes the `databricks-schema` command is on `PATH`. Install it as a standalone tool
131
- with [uv](https://github.com/astral-sh/uv):
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>"
132
196
 
133
- ```bash
134
- uv tool install databricks-schema
135
- ```
197
+ indexes {
198
+ ("id") [pk, name: 'pk_users']
199
+ }
136
200
 
137
- To use the skill in your own project without cloning this repo, download it directly:
201
+ Note: '''User accounts
138
202
 
139
- ```bash
140
- mkdir -p .claude/skills/databricks-schema-cli
141
- curl -o .claude/skills/databricks-schema-cli/SKILL.md \
142
- https://raw.githubusercontent.com/frizzleqq/databricks-schema/main/.claude/skills/databricks-schema-cli/SKILL.md
203
+ @table_type: MANAGED
204
+ @tags: {"domain": "identity"}'''
205
+ }
206
+
207
+ Ref "fk_org": "main"."users"."org_id" > "orgs"."organizations"."id"
143
208
  ```
144
209
 
145
- ## Development
210
+ How the model maps to DBML:
146
211
 
147
- Requires Python 3.11+ and [uv](https://github.com/astral-sh/uv).
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) |
148
222
 
149
- ```bash
150
- git clone https://github.com/frizzleqq/databricks-schema
151
- cd databricks-schema
152
- uv sync --all-groups # includes pytest and ruff
153
- ```
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.
154
224
 
155
- ```bash
156
- # Run tests
157
- uv run pytest
225
+ ### Catalog document
158
226
 
159
- # Lint
160
- uv run ruff check databricks_schema/ tests/
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:
161
228
 
162
- # Format
163
- uv run ruff format databricks_schema/ tests/
229
+ ```yaml
230
+ name: prod_catalog
231
+ schemas:
232
+ - name: main
233
+ comment: Main production schema
234
+ tags:
235
+ env: prod
236
+ tables: [...]
164
237
  ```
165
238
 
166
239
  ## CLI Usage
@@ -169,9 +242,27 @@ uv run ruff format databricks_schema/ tests/
169
242
  databricks-schema [OPTIONS] COMMAND [ARGS]...
170
243
  ```
171
244
 
172
- `extract`, `diff`, `generate-sql`, and `diff-files` print progress messages (e.g. `Extracting
173
- catalog 'X'...`) to stderr as they run. Pass `--quiet` / `-q` to suppress them; results and
174
- errors are unaffected.
245
+ `extract`, `diff`, `generate-sql`, and `diff-files` print progress messages (e.g. `Extracting catalog 'X'...`) to stderr as they run. Pass `--quiet` / `-q` to suppress them;
246
+ results and errors are unaffected.
247
+
248
+ ### `list-catalogs`
249
+
250
+ List all accessible catalogs:
251
+
252
+ ```bash
253
+ databricks-schema list-catalogs
254
+ ```
255
+
256
+ ### `list-schemas`
257
+
258
+ List schemas in a catalog:
259
+
260
+ ```bash
261
+ databricks-schema list-schemas <catalog>
262
+ ```
263
+
264
+ Both print one name per line by default. Pass `--format json` / `-f json` (or `--format yaml`) to
265
+ get the names as a JSON array or YAML list instead.
175
266
 
176
267
  ### `extract`
177
268
 
@@ -181,7 +272,11 @@ Extract all schemas from a catalog to YAML files:
181
272
  databricks-schema extract <catalog> --output-dir ./schemas/
182
273
  ```
183
274
 
184
- Use `--format json` to write `.json` files instead of `.yaml`.
275
+ Print to stdout instead of writing files (no `--output-dir`) — the catalog and its schemas are printed as a single `Catalog` document (`name` + `schemas: [...]`):
276
+
277
+ ```bash
278
+ databricks-schema extract <catalog>
279
+ ```
185
280
 
186
281
  Extract specific schemas only:
187
282
 
@@ -196,16 +291,16 @@ databricks-schema extract <catalog>.<schema>
196
291
  databricks-schema extract <catalog>.<schema>.<table>
197
292
  ```
198
293
 
199
- Print to stdout instead of writing files (no `--output-dir`) — the catalog and its matching schemas are printed as a single `Catalog` document (`name` + `schemas: [...]`):
294
+ Include Unity Catalog tags in the output (extra API calls per entity; excluded by default):
200
295
 
201
296
  ```bash
202
- databricks-schema extract <catalog> --schema main
297
+ databricks-schema extract <catalog> --output-dir ./schemas/ --include-tags
203
298
  ```
204
299
 
205
- Include Unity Catalog tags in the output (extra API calls per entity; excluded by default):
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):
206
301
 
207
302
  ```bash
208
- databricks-schema extract <catalog> --output-dir ./schemas/ --include-tags
303
+ databricks-schema extract <catalog> --output-dir ./schemas/ --include-grants
209
304
  ```
210
305
 
211
306
  Include additional metadata (`owner`, `storage_location`) in the output:
@@ -214,7 +309,13 @@ Include additional metadata (`owner`, `storage_location`) in the output:
214
309
  databricks-schema extract <catalog> --output-dir ./schemas/ --include-metadata
215
310
  ```
216
311
 
217
- Control the number of parallel workers (default: 4):
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
+
318
+ Control the number of parallel workers (default: 4), useful for large schemas with many tags:
218
319
 
219
320
  ```bash
220
321
  databricks-schema extract <catalog> --output-dir ./schemas/ --workers 8
@@ -222,13 +323,27 @@ databricks-schema extract <catalog> --output-dir ./schemas/ --workers 8
222
323
 
223
324
  ### `diff`
224
325
 
225
- Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML or JSON, not mixed):
326
+ Compare the live catalog against previously extracted schema files (format auto-detected from the directory — YAML, JSON or DBML, not mixed):
226
327
 
227
328
  ```bash
228
329
  databricks-schema diff <catalog> ./schemas/
229
330
  ```
230
331
 
231
- Or compare two live catalogs directly, e.g. dev against prod — no local files needed. The second argument is treated as a directory if one exists at that path, otherwise as a catalog name:
332
+ Exits with code `0` if no differences are found, `1` if there are — making it suitable for CI pipelines. Output example:
333
+
334
+ ```
335
+ ~ Schema: main [MODIFIED]
336
+ ~ Table: users [MODIFIED]
337
+ ~ Column: score [MODIFIED]
338
+ data_type: 'int' -> 'double'
339
+ + Column: phone [ADDED]
340
+ + Table: events [ADDED]
341
+ - Schema: legacy [REMOVED]
342
+ ```
343
+
344
+ Markers: `+` added in catalog, `-` removed from catalog, `~` modified.
345
+
346
+ Or compare two live catalogs directly, e.g. dev against prod. The second argument is treated as a directory if one exists at that path, otherwise as a catalog name:
232
347
 
233
348
  ```bash
234
349
  databricks-schema diff dev_catalog prod_catalog
@@ -252,23 +367,22 @@ Include `owner` in the comparison (excluded by default):
252
367
  databricks-schema diff <catalog> ./schemas/ --include-metadata
253
368
  ```
254
369
 
255
- Exits with code `0` if no differences are found, `1` if there are — making it suitable for CI pipelines. Output example:
370
+ Include direct grants in the comparison (excluded by default; without it, grants in the files are ignored):
256
371
 
257
- ```
258
- ~ Schema: main [MODIFIED]
259
- ~ Table: users [MODIFIED]
260
- ~ Column: score [MODIFIED]
261
- data_type: 'int' -> 'double'
262
- + Column: phone [ADDED]
263
- + Table: events [ADDED]
264
- - Schema: legacy [REMOVED]
372
+ ```bash
373
+ databricks-schema diff <catalog> ./schemas/ --include-grants
265
374
  ```
266
375
 
267
- Markers: `+` added in catalog, `-` removed from catalog, `~` modified.
376
+ Get the same comparison as structured JSON or YAML instead of the `+`/`-`/`~` tree (exit codes unchanged) — useful for scripts or agents:
377
+
378
+ ```bash
379
+ databricks-schema diff <catalog> ./schemas/ --format json
380
+ databricks-schema diff <catalog> ./schemas/ --format yaml
381
+ ```
268
382
 
269
383
  ### `generate-sql`
270
384
 
271
- Generate Databricks Spark SQL statements to bring the live catalog in line with local schema files (format auto-detected, YAML or JSON, not mixed). Statements are printed to stdout by default:
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:
272
386
 
273
387
  ```bash
274
388
  databricks-schema generate-sql <catalog> ./schemas/
@@ -280,7 +394,7 @@ Write one `.sql` file per schema to a directory instead:
280
394
  databricks-schema generate-sql <catalog> ./schemas/ --output-dir ./migrations/
281
395
  ```
282
396
 
283
- 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:
284
398
 
285
399
  ```bash
286
400
  databricks-schema generate-sql <catalog> ./schemas/ --allow-drop
@@ -304,6 +418,12 @@ Include `owner` in the comparison (excluded by default):
304
418
  databricks-schema generate-sql <catalog> ./schemas/ --include-metadata
305
419
  ```
306
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
+
307
427
  ### `validate`
308
428
 
309
429
  Validate local schema files for structural integrity (no Databricks connection needed):
@@ -312,29 +432,28 @@ Validate local schema files for structural integrity (no Databricks connection n
312
432
  databricks-schema validate ./schemas/
313
433
  ```
314
434
 
435
+ Pass `--format json` or `--format yaml` for an `{"issues": [...]}` document instead of `OK`/`ERROR:` text.
436
+
315
437
  ### `diff-files`
316
438
 
317
439
  Compare two local directories of schema files (no Databricks connection needed). Same output,
318
- exit codes, and `--schema` / `--include-metadata` flags as `diff`:
440
+ exit codes, `--format json`/`yaml`, and `--schema` / `--include-metadata` / `--include-grants` flags as `diff`:
319
441
 
320
442
  ```bash
321
443
  databricks-schema diff-files ./schemas-prod/ ./schemas-test/
322
444
  ```
323
445
 
324
- ### `list-catalogs`
325
-
326
- List all accessible catalogs:
327
-
328
- ```bash
329
- databricks-schema list-catalogs
330
- ```
331
-
332
- ### `list-schemas`
446
+ ### `json-schema`
333
447
 
334
- List schemas in a catalog:
448
+ Print the JSON Schema for one of the CLI's structured output shapes — no Databricks connection
449
+ needed. Useful before parsing `--format json`/`yaml` output (or `extract`'s output)
450
+ programmatically. Add `--format yaml` to render the schema itself as YAML:
335
451
 
336
452
  ```bash
337
- databricks-schema list-schemas <catalog>
453
+ databricks-schema json-schema catalog # extract's stdout Catalog document
454
+ databricks-schema json-schema schema # one per-schema extract file
455
+ databricks-schema json-schema diff # diff / diff-files --format json/yaml
456
+ databricks-schema json-schema validate # validate --format json/yaml
338
457
  ```
339
458
 
340
459
  ## Python Library Usage
@@ -353,6 +472,10 @@ catalog = extractor.extract_catalog("my_catalog", schema_filter=["main", "raw"])
353
472
  # Include Unity Catalog tags (extra API calls per entity; excluded by default)
354
473
  catalog = extractor.extract_catalog("my_catalog", include_tags=True)
355
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
+
356
479
  # Include additional metadata (owner, storage_location)
357
480
  catalog = extractor.extract_catalog("my_catalog", include_metadata=True)
358
481
 
@@ -363,7 +486,12 @@ yaml_text = catalog_to_yaml(catalog)
363
486
  schema = schema_from_yaml(open("schemas/main.yaml").read())
364
487
  print(schema.tables[0].columns)
365
488
 
366
- # Compare live catalog against local YAML files
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)
367
495
  result = diff_catalog_with_dir(catalog, Path("./schemas/"))
368
496
  if result.has_changes:
369
497
  for schema_diff in result.schemas: