dbt-hotdata 0.2.1__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_hotdata-0.2.1/.github/CODEOWNERS +1 -0
- dbt_hotdata-0.2.1/.github/workflows/ci.yml +72 -0
- dbt_hotdata-0.2.1/.github/workflows/release.yml +83 -0
- dbt_hotdata-0.2.1/.gitignore +25 -0
- dbt_hotdata-0.2.1/CHANGELOG.md +57 -0
- dbt_hotdata-0.2.1/LICENSE +21 -0
- dbt_hotdata-0.2.1/PKG-INFO +218 -0
- dbt_hotdata-0.2.1/README.md +198 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/__init__.py +19 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/__version__.py +1 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/client.py +384 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/column.py +74 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/connections.py +185 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/credentials.py +154 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/impl.py +291 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/relation.py +19 -0
- dbt_hotdata-0.2.1/dbt/adapters/hotdata/seeds.py +154 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/__init__.py +3 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/dbt_project.yml +5 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/adapters.sql +97 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/materializations/incremental.sql +65 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/materializations/seed.sql +33 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/materializations/snapshot.sql +12 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/materializations/table.sql +28 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/materializations/view.sql +13 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/macros/utils.sql +53 -0
- dbt_hotdata-0.2.1/dbt/include/hotdata/profile_template.yml +14 -0
- dbt_hotdata-0.2.1/docs/architecture.md +114 -0
- dbt_hotdata-0.2.1/pyproject.toml +94 -0
- dbt_hotdata-0.2.1/tests/conftest.py +95 -0
- dbt_hotdata-0.2.1/tests/test_client.py +224 -0
- dbt_hotdata-0.2.1/tests/test_column.py +37 -0
- dbt_hotdata-0.2.1/tests/test_connections.py +73 -0
- dbt_hotdata-0.2.1/tests/test_credentials.py +139 -0
- dbt_hotdata-0.2.1/tests/test_impl.py +159 -0
- dbt_hotdata-0.2.1/tests/test_project_parse.py +59 -0
- dbt_hotdata-0.2.1/tests/test_project_run.py +119 -0
- dbt_hotdata-0.2.1/tests/test_review_fixes.py +102 -0
- dbt_hotdata-0.2.1/tests/test_seeds.py +97 -0
- dbt_hotdata-0.2.1/uv.lock +1757 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @hotdata-dev/engineers
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
checks:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: uv sync
|
|
21
|
+
- name: Lint
|
|
22
|
+
run: uv run ruff check dbt tests
|
|
23
|
+
- name: Format
|
|
24
|
+
run: uv run ruff format --check dbt tests
|
|
25
|
+
- name: Type-check
|
|
26
|
+
run: uv run mypy
|
|
27
|
+
- name: Test (offline)
|
|
28
|
+
run: uv run pytest -q
|
|
29
|
+
|
|
30
|
+
wheel-contents:
|
|
31
|
+
# The adapter ships as PEP 420 namespace packages (dbt/adapters/hotdata +
|
|
32
|
+
# dbt/include/hotdata). A packaging change that drops the macros or config
|
|
33
|
+
# files produces a wheel that imports fine but cannot run a single model —
|
|
34
|
+
# so assert the contents explicitly.
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: astral-sh/setup-uv@v5
|
|
39
|
+
- name: Build
|
|
40
|
+
run: uv build
|
|
41
|
+
- name: Assert wheel contents
|
|
42
|
+
run: |
|
|
43
|
+
python3 - <<'EOF'
|
|
44
|
+
import glob, sys, zipfile
|
|
45
|
+
|
|
46
|
+
wheel = glob.glob("dist/*.whl")[0]
|
|
47
|
+
names = set(zipfile.ZipFile(wheel).namelist())
|
|
48
|
+
required = [
|
|
49
|
+
"dbt/adapters/hotdata/__init__.py",
|
|
50
|
+
"dbt/adapters/hotdata/__version__.py",
|
|
51
|
+
"dbt/adapters/hotdata/client.py",
|
|
52
|
+
"dbt/adapters/hotdata/column.py",
|
|
53
|
+
"dbt/adapters/hotdata/connections.py",
|
|
54
|
+
"dbt/adapters/hotdata/credentials.py",
|
|
55
|
+
"dbt/adapters/hotdata/impl.py",
|
|
56
|
+
"dbt/adapters/hotdata/relation.py",
|
|
57
|
+
"dbt/adapters/hotdata/seeds.py",
|
|
58
|
+
"dbt/include/hotdata/__init__.py",
|
|
59
|
+
"dbt/include/hotdata/dbt_project.yml",
|
|
60
|
+
"dbt/include/hotdata/profile_template.yml",
|
|
61
|
+
"dbt/include/hotdata/macros/adapters.sql",
|
|
62
|
+
"dbt/include/hotdata/macros/materializations/table.sql",
|
|
63
|
+
"dbt/include/hotdata/macros/materializations/incremental.sql",
|
|
64
|
+
"dbt/include/hotdata/macros/materializations/seed.sql",
|
|
65
|
+
"dbt/include/hotdata/macros/materializations/view.sql",
|
|
66
|
+
"dbt/include/hotdata/macros/materializations/snapshot.sql",
|
|
67
|
+
]
|
|
68
|
+
missing = [name for name in required if name not in names]
|
|
69
|
+
if missing:
|
|
70
|
+
sys.exit(f"wheel {wheel} is missing: {missing}")
|
|
71
|
+
print(f"ok: {wheel} contains all {len(required)} required files")
|
|
72
|
+
EOF
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- uses: astral-sh/setup-uv@v5
|
|
14
|
+
|
|
15
|
+
- name: Assert tag matches pyproject version
|
|
16
|
+
run: |
|
|
17
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
18
|
+
VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
|
|
19
|
+
if [ "$TAG" != "$VERSION" ]; then
|
|
20
|
+
echo "Tag v$TAG does not match pyproject version $VERSION" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Assert wheel contents
|
|
28
|
+
# Same guard as CI: a packaging change that drops the macros or config
|
|
29
|
+
# files produces a wheel that imports fine but cannot run a model.
|
|
30
|
+
run: |
|
|
31
|
+
python3 - <<'EOF'
|
|
32
|
+
import glob, sys, zipfile
|
|
33
|
+
|
|
34
|
+
wheel = glob.glob("dist/*.whl")[0]
|
|
35
|
+
names = set(zipfile.ZipFile(wheel).namelist())
|
|
36
|
+
required = [
|
|
37
|
+
"dbt/adapters/hotdata/__init__.py",
|
|
38
|
+
"dbt/adapters/hotdata/__version__.py",
|
|
39
|
+
"dbt/adapters/hotdata/client.py",
|
|
40
|
+
"dbt/adapters/hotdata/column.py",
|
|
41
|
+
"dbt/adapters/hotdata/connections.py",
|
|
42
|
+
"dbt/adapters/hotdata/credentials.py",
|
|
43
|
+
"dbt/adapters/hotdata/impl.py",
|
|
44
|
+
"dbt/adapters/hotdata/relation.py",
|
|
45
|
+
"dbt/adapters/hotdata/seeds.py",
|
|
46
|
+
"dbt/include/hotdata/__init__.py",
|
|
47
|
+
"dbt/include/hotdata/dbt_project.yml",
|
|
48
|
+
"dbt/include/hotdata/profile_template.yml",
|
|
49
|
+
"dbt/include/hotdata/macros/adapters.sql",
|
|
50
|
+
"dbt/include/hotdata/macros/materializations/table.sql",
|
|
51
|
+
"dbt/include/hotdata/macros/materializations/incremental.sql",
|
|
52
|
+
"dbt/include/hotdata/macros/materializations/seed.sql",
|
|
53
|
+
"dbt/include/hotdata/macros/materializations/view.sql",
|
|
54
|
+
"dbt/include/hotdata/macros/materializations/snapshot.sql",
|
|
55
|
+
]
|
|
56
|
+
missing = [name for name in required if name not in names]
|
|
57
|
+
if missing:
|
|
58
|
+
sys.exit(f"wheel {wheel} is missing: {missing}")
|
|
59
|
+
print(f"ok: {wheel} contains all {len(required)} required files")
|
|
60
|
+
EOF
|
|
61
|
+
|
|
62
|
+
- uses: actions/upload-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: dist
|
|
65
|
+
path: dist/
|
|
66
|
+
if-no-files-found: error
|
|
67
|
+
|
|
68
|
+
publish:
|
|
69
|
+
needs: build
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
environment:
|
|
72
|
+
name: pypi
|
|
73
|
+
url: https://pypi.org/project/dbt-hotdata/
|
|
74
|
+
permissions:
|
|
75
|
+
# Required for PyPI trusted publishing (OIDC) — no API token needed.
|
|
76
|
+
id-token: write
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/download-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: dist
|
|
81
|
+
path: dist/
|
|
82
|
+
|
|
83
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Environments
|
|
10
|
+
.venv/
|
|
11
|
+
.env
|
|
12
|
+
|
|
13
|
+
# macOS
|
|
14
|
+
.DS_Store
|
|
15
|
+
|
|
16
|
+
# Tooling caches
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
|
|
22
|
+
# dbt artifacts from local test projects
|
|
23
|
+
**/target/
|
|
24
|
+
**/dbt_packages/
|
|
25
|
+
logs/
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Ambient-environment fallbacks for profile fields from the platform's own
|
|
13
|
+
`HOTDATA_*` variables (explicit profile values always win): the API key
|
|
14
|
+
from `HOTDATA_API_KEY`, `workspace_id` from `HOTDATA_WORKSPACE`,
|
|
15
|
+
`database_id` from `HOTDATA_DATABASE`, and `api_base_url` from
|
|
16
|
+
`HOTDATA_API_URL`. Resolution reuses the `hotdata_framework.env` helpers,
|
|
17
|
+
so URLs are normalized the same way as every other SDK consumer. A dbt
|
|
18
|
+
project runs with no profile fields beyond `type: hotdata` when the
|
|
19
|
+
environment provides the rest, and orchestrator bridges (e.g.
|
|
20
|
+
hotdata-dlt-destination's dbt bridge) drive the adapter through the same
|
|
21
|
+
contract. A `database_id` adopted from the environment is logged, since it
|
|
22
|
+
retargets the whole build.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `convert_timezone` returned the UTC instant unshifted; it now converts via
|
|
27
|
+
`to_local_time()` (DST-aware, non-UTC sources verified) and defaults a
|
|
28
|
+
falsy `source_tz` to UTC.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Docs and comments now use the product branding — HotSQL for the SQL
|
|
33
|
+
surface, Hotdata for engine behavior — and the README documents the
|
|
34
|
+
cross-database macros and the SQL dialect story.
|
|
35
|
+
- Dev lockfile resolves dbt-core 1.12.3 (sqlparse 0.6.0), clearing the
|
|
36
|
+
open Dependabot alerts; the supported floor stays dbt-core 1.10.
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2026-07-27
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Initial dbt adapter for Hotdata managed databases (`type: hotdata`). Models
|
|
43
|
+
run server-side and the results load back with native modes — no local
|
|
44
|
+
engine, no DDL, pure HTTPS.
|
|
45
|
+
- Materializations: `table`, `incremental` (`append`, or `merge` as a native
|
|
46
|
+
upsert by `unique_key`), `seed` (numbers stay exact), `ephemeral`. Views and
|
|
47
|
+
snapshots fail up front with actionable errors.
|
|
48
|
+
- dbt unit tests, data tests, `dbt show`, source freshness, and
|
|
49
|
+
`dbt docs generate`.
|
|
50
|
+
- Id-first database addressing: pin `database_id` in the profile, or let the
|
|
51
|
+
first run create a database and print its id.
|
|
52
|
+
- Cross-database macros for HotSQL: `dateadd`, `datediff`,
|
|
53
|
+
`convert_timezone`.
|
|
54
|
+
- Transient API errors (409/429/5xx) retry for ~42s via the shared
|
|
55
|
+
`hotdata-framework` client; terminal errors fail the node immediately.
|
|
56
|
+
- CI: lint, format, type-check, offline tests (Python 3.11/3.12), and a
|
|
57
|
+
wheel-contents check.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hotdata Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dbt-hotdata
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: dbt adapter for Hotdata instant databases.
|
|
5
|
+
Project-URL: Homepage, https://hotdata.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/hotdata-dev/dbt-hotdata
|
|
7
|
+
Project-URL: Changelog, https://github.com/hotdata-dev/dbt-hotdata/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/hotdata-dev/dbt-hotdata/issues
|
|
9
|
+
Author-email: 669988+eddietejeda@users.noreply.github.com
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: dbt-adapters<2,>=1.16
|
|
14
|
+
Requires-Dist: dbt-common<2,>=1.14
|
|
15
|
+
Requires-Dist: dbt-core<2,>=1.10
|
|
16
|
+
Requires-Dist: hotdata-framework<0.10,>=0.9.0
|
|
17
|
+
Requires-Dist: hotdata<0.9,>=0.8.0
|
|
18
|
+
Requires-Dist: pyarrow>=14
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# dbt-hotdata
|
|
22
|
+
|
|
23
|
+
[](https://pypi.org/project/dbt-hotdata/)
|
|
24
|
+
[](https://www.getdbt.com)
|
|
25
|
+
[](LICENSE)
|
|
26
|
+
|
|
27
|
+
Transform data in [Hotdata](https://hotdata.dev) instant databases with [dbt](https://www.getdbt.com).
|
|
28
|
+
|
|
29
|
+
Hotdata is a managed analytics engine speaking [HotSQL](https://www.hotdata.dev/docs/sql) (standard SQL with analytics extensions — if you know Postgres, you already know most of it) with **no DDL surface**: tables are created by loading data, not by `CREATE TABLE`. This adapter embraces that. Every model runs as the Chain pattern, entirely against the API:
|
|
30
|
+
|
|
31
|
+
1. the model's compiled `SELECT` executes **server-side**,
|
|
32
|
+
2. the result streams back as Arrow,
|
|
33
|
+
3. a native load (`replace` / `append` / `upsert`) applies it to the managed table.
|
|
34
|
+
|
|
35
|
+
No local database engine, no driver, no version matching — pure Python over HTTPS. The same project runs unchanged on a laptop, in CI, or in a serverless function.
|
|
36
|
+
|
|
37
|
+
## Contents
|
|
38
|
+
|
|
39
|
+
- [Requirements](#requirements)
|
|
40
|
+
- [Install](#install)
|
|
41
|
+
- [Quickstart](#quickstart)
|
|
42
|
+
- [How materializations work](#how-materializations-work)
|
|
43
|
+
- [Feature support](#feature-support)
|
|
44
|
+
- [Configuration](#configuration)
|
|
45
|
+
- [How it relates to hotdata-dlt-destination](#how-it-relates-to-hotdata-dlt-destination)
|
|
46
|
+
- [Development](#development)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Python **3.11+**, dbt-core **1.10+** (tested through 1.12)
|
|
52
|
+
- A [Hotdata](https://hotdata.dev) workspace, an API key, and its workspace ID — from your Hotdata dashboard or the [Hotdata CLI](https://github.com/hotdata-dev/sdk-python).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install dbt-hotdata
|
|
58
|
+
# or
|
|
59
|
+
uv add dbt-hotdata
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quickstart
|
|
63
|
+
|
|
64
|
+
**profiles.yml**
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
my_project:
|
|
68
|
+
target: dev
|
|
69
|
+
outputs:
|
|
70
|
+
dev:
|
|
71
|
+
type: hotdata
|
|
72
|
+
workspace_id: your_workspace_id
|
|
73
|
+
# database_id: db_abc123 # pin after the first run — see below
|
|
74
|
+
schema: public
|
|
75
|
+
threads: 1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Set your API key in the environment (it's a secret; the workspace ID is routing, not a credential):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export HOTDATA_API_KEY=your_api_key
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**dbt_project.yml** — Hotdata has no views, and dbt's default materialization is `view`, so set the project default to `table`:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
models:
|
|
88
|
+
my_project:
|
|
89
|
+
+materialized: table
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
dbt run
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
On first run, an instant database labelled `dbt` is created automatically and its **id** is printed:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
hotdata: created instant database db_abc123 (name='dbt'). Pin it for future runs by
|
|
102
|
+
setting database_id: db_abc123 in profiles.yml.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Instant databases are addressed by id — Hotdata database names are not unique, so a name can't identify one. Pin `database_id` in the profile to keep building into the same database; without it, each run creates a fresh one (useful for CI or per-branch runs — databases can be set to expire).
|
|
106
|
+
|
|
107
|
+
### An incremental model
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- models/events_rollup.sql
|
|
111
|
+
{{ config(
|
|
112
|
+
materialized='incremental',
|
|
113
|
+
incremental_strategy='merge',
|
|
114
|
+
unique_key='event_id'
|
|
115
|
+
) }}
|
|
116
|
+
|
|
117
|
+
select event_id, user_id, count(*) as touches, max(occurred_at) as last_seen
|
|
118
|
+
from {{ source('app', 'events') }}
|
|
119
|
+
{% if is_incremental() %}
|
|
120
|
+
where occurred_at > (select max(last_seen) from {{ this }})
|
|
121
|
+
{% endif %}
|
|
122
|
+
group by event_id, user_id
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`merge` runs as a **native server-side upsert** matched on `unique_key` — updates matches, inserts the rest, no full-table read. `append` (the default strategy) adds the new rows. First runs and `--full-refresh` load with `replace`.
|
|
126
|
+
|
|
127
|
+
## How materializations work
|
|
128
|
+
|
|
129
|
+
| Materialization | What happens |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `table` | Model SQL runs server-side → result loads with native `replace`. No temp table, no rename swap (there is no rename). |
|
|
132
|
+
| `incremental` | Same, with `append` (default) or `upsert` (`incremental_strategy: merge` + `unique_key`, composite keys supported). |
|
|
133
|
+
| `seed` | The CSV becomes Arrow (numbers stay exact — integers and decimals, never silently floats), then a `replace` load. `column_types:` are applied as Arrow casts. |
|
|
134
|
+
| `ephemeral` | Standard dbt — inlined into consumers, nothing built. |
|
|
135
|
+
| `view` | ❌ Fails up front: Hotdata has no views. The error tells you to set `+materialized: table`. |
|
|
136
|
+
| `snapshot` | ❌ Fails up front: merges update rows in place, so past versions aren't kept. Keep history with an `append` incremental model. |
|
|
137
|
+
|
|
138
|
+
Tests, `dbt show`, analyses, and source freshness all run as plain SELECTs on the server. `dbt docs generate` builds the catalog from the managed-table API plus Arrow schema probes.
|
|
139
|
+
|
|
140
|
+
Schema evolution is additive and automatic: a model that starts producing a new column just includes it in the next load — existing data is never touched, and types can widen but never silently shrink. (`on_schema_change` is therefore ignored.)
|
|
141
|
+
|
|
142
|
+
### SQL dialect
|
|
143
|
+
|
|
144
|
+
Write models in [HotSQL](https://www.hotdata.dev/docs/sql). It is Postgres-familiar, so SQL written for Postgres mostly runs unchanged, and the adapter overrides the cross-database macros (`dateadd`, `datediff`, `convert_timezone`) where HotSQL differs. Hotdata's query API also accepts the Postgres, DuckDB, and Snowflake dialects (translated to HotSQL server-side), but this adapter always submits model SQL as native HotSQL.
|
|
145
|
+
|
|
146
|
+
## Feature support
|
|
147
|
+
|
|
148
|
+
| Feature | Support | Notes |
|
|
149
|
+
|---|:-:|---|
|
|
150
|
+
| `table`, `incremental`, `seed`, `ephemeral` | ✅ | See above |
|
|
151
|
+
| Incremental strategies | ⚠️ | `append`, `merge` (native upsert by `unique_key`). No `delete+insert`, no `microbatch` |
|
|
152
|
+
| `view`, `snapshot` | ❌ | Clear error up front |
|
|
153
|
+
| Tests (generic + singular) | ✅ | Run server-side; `store_failures` supported |
|
|
154
|
+
| `dbt docs generate` | ✅ | Catalog from the managed-table API |
|
|
155
|
+
| Source freshness | ✅ | `loaded_at_field` queries run server-side |
|
|
156
|
+
| Cross-database macros | ✅ | `dateadd`, `datediff`, `convert_timezone` implemented for [HotSQL](https://www.hotdata.dev/docs/sql) (`convert_timezone` is DST-aware) |
|
|
157
|
+
| Hooks (`pre-hook`/`post-hook`, `on-run-*`) | ⚠️ | Run server-side — SELECT-shaped SQL only (no DDL exists) |
|
|
158
|
+
| Python models | ❌ | |
|
|
159
|
+
| Model contracts / constraints | ❌ | No DDL; dbt warns they are unenforced |
|
|
160
|
+
| Grants | ❌ | Ignored with a warning — access is governed by workspace API keys |
|
|
161
|
+
| Transactions | ❌ | `begin`/`commit` are no-ops (Hotdata has no transactions) |
|
|
162
|
+
| Query cancellation | ❌ | An in-flight HTTPS query can't be interrupted client-side |
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
| Profile field | Env variable | Default | Description |
|
|
167
|
+
|---|---|---|---|
|
|
168
|
+
| `api_key` | `HOTDATA_API_KEY` | required | API key (a secret — prefer the env var or `"{{ env_var('HOTDATA_API_KEY') }}"`) |
|
|
169
|
+
| `workspace_id` | `HOTDATA_WORKSPACE` | required | Workspace ID (routing, not a secret) |
|
|
170
|
+
| `database_id` | `HOTDATA_DATABASE` | — | Id of the instant database to build into. **This is how a database is targeted** — names aren't unique. Printed on first-run create; pin it to reuse |
|
|
171
|
+
| `database_name` | — | `dbt` | Display label used **only when creating** a new database (never to look one up) |
|
|
172
|
+
| `schema` | — | `public` | Schema inside the instant database |
|
|
173
|
+
| `create_database_if_missing` | — | `true` | Create a database on first run when no `database_id` is pinned |
|
|
174
|
+
| `api_base_url` | `HOTDATA_API_URL` | `https://api.hotdata.dev` | API endpoint |
|
|
175
|
+
| `max_retries` | — | `8` | Retry budget for transient errors (409/429/5xx). Loads take a catalog-level lock per database; ~42s of linear backoff outlasts a concurrent writer |
|
|
176
|
+
| `retry_backoff_seconds` | — | `1.5` | Initial retry wait (grows linearly) |
|
|
177
|
+
| `threads` | — | `1` | Loads into one database serialize server-side (contention is retried); more threads still help when models spend most of their time in query execution |
|
|
178
|
+
|
|
179
|
+
`database:` stays unset — inside an instant database the SQL catalog is always literally `default` (relations render as `"default"."schema"."table"`), and the adapter rejects any other value up front.
|
|
180
|
+
|
|
181
|
+
Fields left unset in the profile resolve from the platform's own `HOTDATA_*` environment variables (explicit profile values always win). These are the Hotdata CLI conventions — under any orchestrator that sets them, the adapter needs no profile fields at all beyond `type: hotdata`. A `database_id` adopted from the environment is logged, since it retargets the whole build.
|
|
182
|
+
|
|
183
|
+
## How it relates to hotdata-dlt-destination
|
|
184
|
+
|
|
185
|
+
[hotdata-dlt-destination](https://github.com/hotdata-dev/hotdata-dlt-destination) loads external data **into** Hotdata (the EL); this adapter transforms it **inside** Hotdata (the T). They share the same conventions — `HOTDATA_API_KEY` from the environment, `workspace_id` as a plain parameter, id-first `database_id` addressing, the same retry classification — and the same underlying SDK (`hotdata` + `hotdata-framework`). Point dbt at the `database_id` your dlt pipeline prints, add sources for the loaded tables, and build models on top.
|
|
186
|
+
|
|
187
|
+
### Running after a dlt load
|
|
188
|
+
|
|
189
|
+
[hotdata-dlt-destination](https://github.com/hotdata-dev/hotdata-dlt-destination) ships a dbt bridge: after a pipeline run, one helper call executes a dbt package against the exact instant database the load just wrote — no profiles.yml to author, credentials and routing reused from the pipeline. See “Transform with dbt” in that repo's README. This adapter itself knows nothing about dlt; the bridge drives it through the `HOTDATA_*` environment contract above.
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
The project uses [uv](https://docs.astral.sh/uv/) for dependency management.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git clone https://github.com/hotdata-dev/dbt-hotdata.git
|
|
197
|
+
cd dbt-hotdata
|
|
198
|
+
|
|
199
|
+
uv sync # install deps (including dev group)
|
|
200
|
+
|
|
201
|
+
uv run pytest # run the test suite (offline — no credentials needed)
|
|
202
|
+
uv run ruff check # lint
|
|
203
|
+
uv run ruff format # format
|
|
204
|
+
uv run mypy # type-check
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The test suite runs entirely offline: adapter logic is exercised against an in-memory fake client, and a real `dbt parse` verifies plugin registration and every macro.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
[MIT](LICENSE) © Hotdata Inc.
|
|
212
|
+
|
|
213
|
+
## Resources
|
|
214
|
+
|
|
215
|
+
- [Hotdata Python SDK](https://github.com/hotdata-dev/sdk-python) · [hotdata-framework](https://github.com/hotdata-dev/sdk-python-framework)
|
|
216
|
+
- [hotdata-dlt-destination](https://github.com/hotdata-dev/hotdata-dlt-destination)
|
|
217
|
+
- [dbt adapter documentation](https://docs.getdbt.com/docs/connect-adapters)
|
|
218
|
+
- [Changelog](CHANGELOG.md) · [Architecture](docs/architecture.md)
|