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.
Files changed (33) hide show
  1. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/CLAUDE.md +16 -7
  2. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/PKG-INFO +108 -9
  3. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/README.md +103 -6
  4. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/__init__.py +18 -0
  5. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/cli.py +99 -57
  6. databricks_schema-0.10.0/databricks_schema/dbml_io.py +433 -0
  7. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/diff.py +78 -44
  8. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/extractor.py +58 -3
  9. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/models.py +2 -0
  10. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/sql_gen.py +45 -1
  11. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/yaml_io.py +42 -1
  12. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/pyproject.toml +9 -3
  13. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/skills/databricks-schema-cli/SKILL.md +28 -10
  14. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_cli.py +111 -0
  15. databricks_schema-0.10.0/tests/test_dbml_io.py +316 -0
  16. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_diff.py +66 -0
  17. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_extractor.py +116 -2
  18. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_sql_gen.py +50 -0
  19. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_yaml_io.py +23 -0
  20. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/uv.lock +41 -9
  21. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.github/workflows/ci.yml +0 -0
  22. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.github/workflows/release.yml +0 -0
  23. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.gitignore +0 -0
  24. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/.python-version +0 -0
  25. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/LICENSE +0 -0
  26. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/databricks_schema/validate.py +0 -0
  27. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/01_extract_schema.py +0 -0
  28. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/02_save_load_yaml.py +0 -0
  29. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/03_diff_schemas.py +0 -0
  30. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/examples/04_generate_sql.py +0 -0
  31. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/__init__.py +0 -0
  32. {databricks_schema-0.8.0 → databricks_schema-0.10.0}/tests/test_models.py +0 -0
  33. {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` 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)
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 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
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 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
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: databricks-schema
3
- Version: 0.8.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 in YAML format. 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
 
@@ -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 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):
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, 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:
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
- # 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)
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 in YAML format. It can also generate Databricks Spark SQL to apply schema changes across catalogs.
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 JSON, not mixed):
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, YAML or JSON, not mixed). Statements are printed to stdout by default:
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
- # Compare live catalog against local YAML files
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
  ]