dbt-polars-catalog-databricks 0.3.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.
- dbt_polars_catalog_databricks-0.3.0/.gitignore +228 -0
- dbt_polars_catalog_databricks-0.3.0/PKG-INFO +93 -0
- dbt_polars_catalog_databricks-0.3.0/README.md +69 -0
- dbt_polars_catalog_databricks-0.3.0/dbt_polars_catalog_databricks/__init__.py +23 -0
- dbt_polars_catalog_databricks-0.3.0/dbt_polars_catalog_databricks/config.py +59 -0
- dbt_polars_catalog_databricks-0.3.0/dbt_polars_catalog_databricks/databricks_types.py +119 -0
- dbt_polars_catalog_databricks-0.3.0/dbt_polars_catalog_databricks/delta.py +623 -0
- dbt_polars_catalog_databricks-0.3.0/hatch_build.py +39 -0
- dbt_polars_catalog_databricks-0.3.0/pyproject.toml +69 -0
- dbt_polars_catalog_databricks-0.3.0/tests/test_databricks_catalog.py +61 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
.vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
|
|
220
|
+
# uv.lock
|
|
221
|
+
uv.lock
|
|
222
|
+
|
|
223
|
+
# dbt-polars
|
|
224
|
+
logs/
|
|
225
|
+
test.env
|
|
226
|
+
.claude/
|
|
227
|
+
decisions.md
|
|
228
|
+
test_project*/
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dbt-polars-catalog-databricks
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Databricks Unity Catalog catalog for dbt-polars
|
|
5
|
+
Project-URL: Homepage, https://github.com/datamindedbe/incubator-dbt-polars
|
|
6
|
+
Project-URL: Repository, https://github.com/datamindedbe/incubator-dbt-polars
|
|
7
|
+
Project-URL: Changelog, https://github.com/datamindedbe/incubator-dbt-polars/releases
|
|
8
|
+
Author-email: Jonas Crevecoeur <jonas.crevecoeur@dataminded.com>
|
|
9
|
+
Maintainer-email: Jonas Crevecoeur <jonas.crevecoeur@dataminded.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: catalog,databricks,dbt,polars,unity-catalog
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Requires-Python: <3.14,>=3.10
|
|
21
|
+
Requires-Dist: databricks-sdk>=0.30.0
|
|
22
|
+
Requires-Dist: dbt-polars==0.3.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# dbt-polars
|
|
26
|
+
|
|
27
|
+
dbt-polars integrates the flexibility and performance of Polars with the data governance and lineage capabilities of dbt.
|
|
28
|
+
|
|
29
|
+
Key features of **dbt-polars**:
|
|
30
|
+
|
|
31
|
+
* **Run anywhere**: Polars runs on any compute platform, so you’re not locked into an expensive data warehouse.
|
|
32
|
+
* **Flexible**: Available catalogs let you connect to local storage, Iceberg catalogs, Databricks, and more.
|
|
33
|
+
* **Extensible**: Create a custom catalog to fit your unique storage and warehouse needs.
|
|
34
|
+
* **First-class Python models**: Run Python models with the same performance and capabilities as SQL models.
|
|
35
|
+
* **Mix Python and SQL**: Choose the language that best fits the transformation.
|
|
36
|
+
* **dbt-native**: Bring lineage, documentation, and data testing into your workflow.
|
|
37
|
+
* **Ingestion**: Build efficient, incremental API ingestion with Python models and keep the entire pipeline visible in the lineage graph.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install dbt-polars
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Extra dependencies are required for cloud and Iceberg backends:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install 'dbt-polars[azure]' # Azure Blob Storage
|
|
49
|
+
pip install 'dbt-polars[databricks]' # Azure Blob Storage
|
|
50
|
+
pip install 'dbt-polars[iceberg]' # Apache Iceberg (SQLite, REST, Databricks, …)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Catalogs
|
|
54
|
+
|
|
55
|
+
dbt-polars uses **catalogs** to define where data is stored. Each catalog maps to a storage backend. You can configure multiple catalogs in a single profile and reference them from your models.
|
|
56
|
+
|
|
57
|
+
| Catalog | Backend | Status |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `local` | Local filesystem | Usable |
|
|
60
|
+
| `iceberg` with SQLite | Iceberg + SQLite metastore | Usable |
|
|
61
|
+
| `iceberg` with REST + Databricks | Iceberg REST API (Unity Catalog) | Experimental |
|
|
62
|
+
| `iceberg` with other backends | Any pyiceberg-supported backend | Experimental |
|
|
63
|
+
| `azure` | Azure Blob Storage / ADLS Gen2 | Experimental |
|
|
64
|
+
| `databricks` | Unity Catalog REST API + credential vending | Experimental |
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
See the [Getting started](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/demos/getting_started.md) guide.
|
|
69
|
+
|
|
70
|
+
A minimal `profiles.yml` for local development is:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
my_project:
|
|
74
|
+
target: dev
|
|
75
|
+
outputs:
|
|
76
|
+
dev:
|
|
77
|
+
type: polars
|
|
78
|
+
schema: dev
|
|
79
|
+
|
|
80
|
+
catalog_type: local
|
|
81
|
+
root: ./data
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
- [Getting started](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/demos/getting_started.md)
|
|
87
|
+
- [Catalog overview and profile reference](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalog-overview.md)
|
|
88
|
+
- [Local catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/local.md)
|
|
89
|
+
- [Azure catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/azure.md)
|
|
90
|
+
- [Iceberg catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/iceberg.md)
|
|
91
|
+
- [Databricks catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/databricks.md)
|
|
92
|
+
- [Custom catalogs](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/custom.md)
|
|
93
|
+
- [Python models and tests](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/python-models.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# dbt-polars
|
|
2
|
+
|
|
3
|
+
dbt-polars integrates the flexibility and performance of Polars with the data governance and lineage capabilities of dbt.
|
|
4
|
+
|
|
5
|
+
Key features of **dbt-polars**:
|
|
6
|
+
|
|
7
|
+
* **Run anywhere**: Polars runs on any compute platform, so you’re not locked into an expensive data warehouse.
|
|
8
|
+
* **Flexible**: Available catalogs let you connect to local storage, Iceberg catalogs, Databricks, and more.
|
|
9
|
+
* **Extensible**: Create a custom catalog to fit your unique storage and warehouse needs.
|
|
10
|
+
* **First-class Python models**: Run Python models with the same performance and capabilities as SQL models.
|
|
11
|
+
* **Mix Python and SQL**: Choose the language that best fits the transformation.
|
|
12
|
+
* **dbt-native**: Bring lineage, documentation, and data testing into your workflow.
|
|
13
|
+
* **Ingestion**: Build efficient, incremental API ingestion with Python models and keep the entire pipeline visible in the lineage graph.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install dbt-polars
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Extra dependencies are required for cloud and Iceberg backends:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install 'dbt-polars[azure]' # Azure Blob Storage
|
|
25
|
+
pip install 'dbt-polars[databricks]' # Azure Blob Storage
|
|
26
|
+
pip install 'dbt-polars[iceberg]' # Apache Iceberg (SQLite, REST, Databricks, …)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Catalogs
|
|
30
|
+
|
|
31
|
+
dbt-polars uses **catalogs** to define where data is stored. Each catalog maps to a storage backend. You can configure multiple catalogs in a single profile and reference them from your models.
|
|
32
|
+
|
|
33
|
+
| Catalog | Backend | Status |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `local` | Local filesystem | Usable |
|
|
36
|
+
| `iceberg` with SQLite | Iceberg + SQLite metastore | Usable |
|
|
37
|
+
| `iceberg` with REST + Databricks | Iceberg REST API (Unity Catalog) | Experimental |
|
|
38
|
+
| `iceberg` with other backends | Any pyiceberg-supported backend | Experimental |
|
|
39
|
+
| `azure` | Azure Blob Storage / ADLS Gen2 | Experimental |
|
|
40
|
+
| `databricks` | Unity Catalog REST API + credential vending | Experimental |
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
See the [Getting started](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/demos/getting_started.md) guide.
|
|
45
|
+
|
|
46
|
+
A minimal `profiles.yml` for local development is:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
my_project:
|
|
50
|
+
target: dev
|
|
51
|
+
outputs:
|
|
52
|
+
dev:
|
|
53
|
+
type: polars
|
|
54
|
+
schema: dev
|
|
55
|
+
|
|
56
|
+
catalog_type: local
|
|
57
|
+
root: ./data
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Documentation
|
|
61
|
+
|
|
62
|
+
- [Getting started](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/demos/getting_started.md)
|
|
63
|
+
- [Catalog overview and profile reference](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalog-overview.md)
|
|
64
|
+
- [Local catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/local.md)
|
|
65
|
+
- [Azure catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/azure.md)
|
|
66
|
+
- [Iceberg catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/iceberg.md)
|
|
67
|
+
- [Databricks catalog](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/databricks.md)
|
|
68
|
+
- [Custom catalogs](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/catalogs/custom.md)
|
|
69
|
+
- [Python models and tests](https://github.com/datamindedbe/incubator-dbt-polars/blob/main/docs/python-models.md)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dbt.adapters.polars.catalogs import BaseCatalog
|
|
2
|
+
|
|
3
|
+
from dbt_polars_catalog_databricks.config import DatabricksCatalogConfig
|
|
4
|
+
from dbt_polars_catalog_databricks.delta import DatabricksDeltaCatalog
|
|
5
|
+
|
|
6
|
+
CATALOG_BY_TABLE_FORMAT: dict[str, type[BaseCatalog]] = {
|
|
7
|
+
"delta": DatabricksDeltaCatalog,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
config_class = DatabricksCatalogConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_catalog(config: DatabricksCatalogConfig, project_root: str) -> BaseCatalog:
|
|
14
|
+
return CATALOG_BY_TABLE_FORMAT[config.table_format](config, project_root)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"CATALOG_BY_TABLE_FORMAT",
|
|
19
|
+
"DatabricksCatalogConfig",
|
|
20
|
+
"DatabricksDeltaCatalog",
|
|
21
|
+
"config_class",
|
|
22
|
+
"create_catalog",
|
|
23
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from dbt.adapters.polars.catalogs import CatalogConfig
|
|
2
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
3
|
+
|
|
4
|
+
SUPPORTED_TABLE_FORMATS = ("delta",)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatabricksCatalogConfig(CatalogConfig):
|
|
8
|
+
"""Config for the Databricks/Unity Catalog backend.
|
|
9
|
+
|
|
10
|
+
`catalog_name` is the only required dbt-polars-specific field (which Unity
|
|
11
|
+
Catalog catalog to register tables in - schema storage locations are read
|
|
12
|
+
from Unity Catalog itself, not configured here). Every other keyword is
|
|
13
|
+
passed straight through to `databricks.sdk.Config`, so any auth method it
|
|
14
|
+
supports (PAT, OAuth M2M, Azure client-secret, external-browser, azure-cli,
|
|
15
|
+
databricks-cli profile, ...) works by supplying the matching kwargs.
|
|
16
|
+
|
|
17
|
+
`persist_docs_http_path`: docs (table/column comments from persist_docs)
|
|
18
|
+
are always written to the Delta table's own metadata. If a SQL warehouse's
|
|
19
|
+
HTTP path (e.g. `/sql/1.0/warehouses/<warehouse_id>`, as shown on the
|
|
20
|
+
warehouse's Connection Details page) is also set here, they're additionally
|
|
21
|
+
written into Unity Catalog's native comment fields via `COMMENT ON TABLE`/
|
|
22
|
+
`ALTER TABLE ... ALTER COLUMN ... COMMENT` - the only way to set those,
|
|
23
|
+
since Unity Catalog's REST API has no endpoint for it at all. Left unset,
|
|
24
|
+
docs never reach Unity Catalog itself.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
name: str,
|
|
31
|
+
type: str,
|
|
32
|
+
schema: str,
|
|
33
|
+
catalog_name: str,
|
|
34
|
+
host: str | None = None,
|
|
35
|
+
persist_docs_http_path: str | None = None,
|
|
36
|
+
table_format: str = "delta",
|
|
37
|
+
**kwargs: object,
|
|
38
|
+
) -> None:
|
|
39
|
+
if table_format not in SUPPORTED_TABLE_FORMATS:
|
|
40
|
+
raise DbtRuntimeError(
|
|
41
|
+
f"Databricks catalog '{name}': table_format '{table_format}' is not "
|
|
42
|
+
f"supported, only {', '.join(SUPPORTED_TABLE_FORMATS)} for now."
|
|
43
|
+
)
|
|
44
|
+
self.name = name
|
|
45
|
+
self.type = type
|
|
46
|
+
self.schema = schema
|
|
47
|
+
self.catalog_name = catalog_name
|
|
48
|
+
self.persist_docs_http_path = persist_docs_http_path
|
|
49
|
+
self.table_format = table_format
|
|
50
|
+
self.sdk_kwargs: dict[str, object] = {
|
|
51
|
+
**({"host": host} if host else {}),
|
|
52
|
+
**kwargs,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def unique_field(self) -> str:
|
|
56
|
+
return f"{self.catalog_name}/{self.schema}"
|
|
57
|
+
|
|
58
|
+
def connection_keys(self) -> tuple[str, ...]:
|
|
59
|
+
return ("name", "catalog_name", "host", "persist_docs_http_path")
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Maps Polars dtypes to Unity Catalog column type descriptors.
|
|
2
|
+
|
|
3
|
+
Used only to populate column metadata when registering a table via the Unity
|
|
4
|
+
Catalog Tables API (`POST /api/2.1/unity-catalog/tables`). Unity Catalog does
|
|
5
|
+
not validate this against the table's actual Parquet/Delta physical types, so
|
|
6
|
+
this is a best-effort, display/documentation-oriented mapping, not a source of
|
|
7
|
+
truth for how data is physically stored.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import polars as pl
|
|
16
|
+
from dbt.adapters.events.logging import AdapterLogger
|
|
17
|
+
|
|
18
|
+
logger = AdapterLogger("polars")
|
|
19
|
+
|
|
20
|
+
# Delta/Unity Catalog has no unsigned integer types; describe them as the
|
|
21
|
+
# next signed type wide enough to hold every value (mirrors IcebergCatalog's
|
|
22
|
+
# _cast_unsigned_to_signed rationale in icebergCatalog.py).
|
|
23
|
+
UNSIGNED_TO_SIGNED_NAME_TEXT: dict[type, tuple[str, str]] = {
|
|
24
|
+
pl.UInt8: ("SHORT", "smallint"),
|
|
25
|
+
pl.UInt16: ("INT", "int"),
|
|
26
|
+
pl.UInt32: ("LONG", "bigint"),
|
|
27
|
+
pl.UInt64: ("DECIMAL", "decimal(20,0)"),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
SIMPLE_NAME_TEXT: dict[type, tuple[str, str]] = {
|
|
31
|
+
pl.Boolean: ("BOOLEAN", "boolean"),
|
|
32
|
+
pl.Int8: ("BYTE", "tinyint"),
|
|
33
|
+
pl.Int16: ("SHORT", "smallint"),
|
|
34
|
+
pl.Int32: ("INT", "int"),
|
|
35
|
+
pl.Int64: ("LONG", "bigint"),
|
|
36
|
+
pl.Float32: ("FLOAT", "float"),
|
|
37
|
+
pl.Float64: ("DOUBLE", "double"),
|
|
38
|
+
pl.Utf8: ("STRING", "string"),
|
|
39
|
+
pl.Categorical: ("STRING", "string"),
|
|
40
|
+
pl.Date: ("DATE", "date"),
|
|
41
|
+
pl.Binary: ("BINARY", "binary"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def decimal_name_text(dtype: pl.Decimal) -> tuple[str, str]:
|
|
46
|
+
precision = dtype.precision if dtype.precision is not None else 38
|
|
47
|
+
scale = dtype.scale or 0
|
|
48
|
+
return "DECIMAL", f"decimal({precision},{scale})"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def datetime_name_text(dtype: pl.Datetime) -> tuple[str, str]:
|
|
52
|
+
if dtype.time_zone:
|
|
53
|
+
return "TIMESTAMP", "timestamp"
|
|
54
|
+
return "TIMESTAMP_NTZ", "timestamp_ntz"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def json_value(dtype: Any) -> object:
|
|
58
|
+
"""Return the JSON-able value (str or dict) describing dtype, Spark-schema style."""
|
|
59
|
+
base = type(dtype)
|
|
60
|
+
|
|
61
|
+
if base in UNSIGNED_TO_SIGNED_NAME_TEXT:
|
|
62
|
+
return UNSIGNED_TO_SIGNED_NAME_TEXT[base][1]
|
|
63
|
+
if base in SIMPLE_NAME_TEXT:
|
|
64
|
+
return SIMPLE_NAME_TEXT[base][1]
|
|
65
|
+
if isinstance(dtype, pl.Decimal):
|
|
66
|
+
return decimal_name_text(dtype)[1]
|
|
67
|
+
if isinstance(dtype, pl.Datetime):
|
|
68
|
+
return datetime_name_text(dtype)[1]
|
|
69
|
+
if isinstance(dtype, (pl.List, pl.Array)):
|
|
70
|
+
return {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"elementType": json_value(dtype.inner),
|
|
73
|
+
"containsNull": True,
|
|
74
|
+
}
|
|
75
|
+
if isinstance(dtype, pl.Struct):
|
|
76
|
+
return {
|
|
77
|
+
"type": "struct",
|
|
78
|
+
"fields": [
|
|
79
|
+
{
|
|
80
|
+
"name": f.name,
|
|
81
|
+
"type": json_value(f.dtype),
|
|
82
|
+
"nullable": True,
|
|
83
|
+
"metadata": {},
|
|
84
|
+
}
|
|
85
|
+
for f in dtype.fields
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
logger.debug(
|
|
89
|
+
f"No Unity Catalog type mapping for Polars dtype {dtype!r}; "
|
|
90
|
+
"describing this column as STRING in the registered table metadata."
|
|
91
|
+
)
|
|
92
|
+
return "string"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def polars_dtype_to_uc_type(dtype: Any) -> tuple[str, str, str]:
|
|
96
|
+
"""Return (type_name, type_text, type_json) for a Unity Catalog table column."""
|
|
97
|
+
base = type(dtype)
|
|
98
|
+
|
|
99
|
+
if base in UNSIGNED_TO_SIGNED_NAME_TEXT:
|
|
100
|
+
name, text = UNSIGNED_TO_SIGNED_NAME_TEXT[base]
|
|
101
|
+
elif base in SIMPLE_NAME_TEXT:
|
|
102
|
+
name, text = SIMPLE_NAME_TEXT[base]
|
|
103
|
+
elif isinstance(dtype, pl.Decimal):
|
|
104
|
+
name, text = decimal_name_text(dtype)
|
|
105
|
+
elif isinstance(dtype, pl.Datetime):
|
|
106
|
+
name, text = datetime_name_text(dtype)
|
|
107
|
+
elif isinstance(dtype, (pl.List, pl.Array)):
|
|
108
|
+
_, inner_text, _ = polars_dtype_to_uc_type(dtype.inner)
|
|
109
|
+
name, text = "ARRAY", f"array<{inner_text}>"
|
|
110
|
+
elif isinstance(dtype, pl.Struct):
|
|
111
|
+
field_texts = []
|
|
112
|
+
for f in dtype.fields:
|
|
113
|
+
_, f_text, _ = polars_dtype_to_uc_type(f.dtype)
|
|
114
|
+
field_texts.append(f"{f.name}:{f_text}")
|
|
115
|
+
name, text = "STRUCT", f"struct<{','.join(field_texts)}>"
|
|
116
|
+
else:
|
|
117
|
+
name, text = "STRING", "string"
|
|
118
|
+
|
|
119
|
+
return name, text, json.dumps(json_value(dtype))
|