Skip to content

Commit 43938a4

Browse files
authored
feat: LlamaIndex tools for Hotdata managed databases with database= scoping (#2)
* feat: add LlamaIndex tools for Hotdata managed databases * ci: add PyPI publish workflow on version tags * chore: pin explicit hotdata>=0.2.0 dependency Require the latest SDK directly alongside hotdata-runtime for clearer version alignment. * ci: add standardized release script and automation Add scripts/release.sh for version bumps, changelog updates, tagging, and GitHub Release creation via CI. Enforce changelog checks on version PRs. * ci: add pytest workflow and demo notebook smoke tests Run locked uv sync and pytest on pull requests. Validate examples/demo.ipynb structure and optional live smoke when HOTDATA_API_KEY is set. * fix: rewrite changelog updater for Keep a Changelog format Avoid duplicate [Unreleased] headings and keep the preamble intact when preparing releases. Add unit tests and harden release workflow output. * test: fix changelog unit test imports in CI * feat: add database= parameter to execute_sql for managed database scoping Pass database= to client.execute_sql() so queries are scoped to a managed database via the X-Database-Id header (hotdata-runtime>=0.2.1). Also updates ManagedDatabase constructor calls to use description= and default_connection_id= fields introduced in hotdata-runtime v0.2.0. * fix: bump hotdata-runtime constraint to >=0.2.1 --------- Co-authored-by: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com>
1 parent f564d3c commit 43938a4

24 files changed

Lines changed: 3831 additions & 1 deletion
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Check release metadata
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'pyproject.toml'
7+
- 'CHANGELOG.md'
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
check:
14+
name: Verify changelog matches version bump
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
18+
with:
19+
fetch-depth: 0
20+
21+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
22+
with:
23+
python-version: '3.12'
24+
25+
- name: Check release metadata
26+
run: python scripts/check-release.py

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main", "master"]
6+
pull_request:
7+
8+
concurrency:
9+
group: ci-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
test:
17+
name: Test (Python 3.12)
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
21+
22+
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v6
23+
with:
24+
enable-cache: true
25+
26+
- name: Set up Python
27+
run: uv python install 3.12
28+
29+
- name: Install dependencies
30+
run: uv sync --locked
31+
32+
- name: Test
33+
run: uv run pytest -v

.github/workflows/publish.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]*'
7+
8+
concurrency:
9+
group: pypi-publish-${{ github.ref_name }}
10+
cancel-in-progress: false
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
build:
17+
name: Build distribution
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
21+
22+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
23+
with:
24+
python-version: '3.12'
25+
26+
- name: Install build tooling
27+
run: python -m pip install --upgrade build twine
28+
29+
- name: Verify tag matches pyproject version
30+
run: |
31+
if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9] ]]; then
32+
echo "Release tag '$GITHUB_REF_NAME' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2
33+
exit 1
34+
fi
35+
tag="${GITHUB_REF_NAME#v}"
36+
pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
37+
if [ "$tag" != "$pkg_version" ]; then
38+
echo "Release tag ($tag) does not match pyproject.toml version ($pkg_version)" >&2
39+
exit 1
40+
fi
41+
42+
- name: Build sdist and wheel
43+
run: python -m build
44+
45+
- name: Check distribution metadata
46+
run: python -m twine check --strict dist/*
47+
48+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
49+
with:
50+
name: dist
51+
path: dist/
52+
53+
publish:
54+
name: Publish to PyPI
55+
needs: build
56+
runs-on: ubuntu-latest
57+
environment:
58+
name: pypi
59+
url: https://pypi.org/p/hotdata-llamaindex
60+
permissions:
61+
id-token: write
62+
steps:
63+
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
64+
with:
65+
name: dist
66+
path: dist/
67+
68+
- name: Publish via Trusted Publishing
69+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0

.github/workflows/release.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: GitHub Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
name: Create GitHub Release
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
17+
18+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
19+
with:
20+
python-version: '3.12'
21+
22+
- name: Read package metadata
23+
id: meta
24+
run: |
25+
pkg_name=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['name'])")
26+
pkg_version="${GITHUB_REF_NAME#v}"
27+
echo "name=${pkg_name}" >> "$GITHUB_OUTPUT"
28+
echo "version=${pkg_version}" >> "$GITHUB_OUTPUT"
29+
30+
- name: Extract changelog notes
31+
id: notes
32+
run: |
33+
set -euo pipefail
34+
version="${GITHUB_REF_NAME#v}"
35+
if [[ -f CHANGELOG.md ]]; then
36+
body="$(python scripts/extract-changelog.py "$version")"
37+
else
38+
body="Release ${version}."
39+
fi
40+
delimiter="EOF_${RANDOM}_${RANDOM}"
41+
{
42+
echo "body<<${delimiter}"
43+
echo "$body"
44+
echo "${delimiter}"
45+
} >> "$GITHUB_OUTPUT"
46+
47+
- name: Create GitHub Release
48+
uses: softprops/action-gh-release@1e812e8210a4a8a0b23075e5795f2a4e2b2a0b7 # v2.2.2
49+
with:
50+
tag_name: ${{ github.ref_name }}
51+
name: ${{ steps.meta.outputs.name }} ${{ steps.meta.outputs.version }}
52+
body: ${{ steps.notes.outputs.body }}
53+
generate_release_notes: false
54+
make_latest: true

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
7+
# Virtual environments
8+
.env
9+
.venv
10+
env/
11+
venv/
12+
13+
# Testing
14+
.pytest_cache/
15+
.coverage
16+
htmlcov/
17+
18+
# Packaging
19+
*.egg-info/
20+
dist/
21+
build/
22+
23+
.DS_Store

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
## [Unreleased]
9+
10+
## [0.1.0] - 2026-05-19
11+
12+
### Added
13+
14+
- Initial release with LlamaIndex tools for Hotdata managed databases.

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,60 @@
11
# hotdata-llamaindex
2-
LlamaIndex tools for Hotdata runtime
2+
3+
LlamaIndex tools for [Hotdata](https://hotdata.dev), built on **hotdata-runtime**.
4+
5+
## Features
6+
7+
- **SQL tool** — run workspace SQL and return JSON rows for agents
8+
- **Managed database tools** — list, create, and load parquet into Hotdata-owned catalogs (replaces legacy dataset uploads)
9+
10+
## Install
11+
12+
```bash
13+
pip install hotdata-llamaindex
14+
```
15+
16+
Requires `HOTDATA_API_KEY`. Optionally set `HOTDATA_WORKSPACE`, `HOTDATA_API_URL`, or `HOTDATA_SANDBOX`.
17+
18+
## Usage
19+
20+
```python
21+
import hotdata_llamaindex as hli
22+
23+
client = hli.from_env()
24+
tools = hli.make_hotdata_tools(client)
25+
26+
for tool in tools:
27+
print(tool.metadata.name, tool.metadata.description)
28+
```
29+
30+
Managed database example:
31+
32+
```python
33+
tools = {tool.metadata.name: tool for tool in hli.make_hotdata_tools(client)}
34+
35+
tools["hotdata_create_managed_database"].call(
36+
name="sales",
37+
schema_name="public",
38+
tables="orders",
39+
)
40+
41+
tools["hotdata_load_managed_table"].call(
42+
database="sales",
43+
table="orders",
44+
file="/path/to/orders.parquet",
45+
)
46+
```
47+
48+
## Examples
49+
50+
```bash
51+
uv run python examples/llamaindex_basic.py
52+
uv run python examples/llamaindex_managed_db.py
53+
```
54+
55+
## Development
56+
57+
```bash
58+
uv sync --locked
59+
uv run pytest
60+
```

RELEASING.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Releasing
2+
3+
Every release uses `./scripts/release.sh`. Do not bump versions, tag, or create GitHub Releases manually.
4+
5+
## One-time setup
6+
7+
- Install [GitHub CLI](https://cli.github.com/) (`gh`) and authenticate.
8+
- Ensure PyPI [trusted publishing](https://docs.pypi.org/trusted-publishers/) is configured for this repo (`publish.yml` uses the `pypi` GitHub environment).
9+
10+
## Release steps
11+
12+
1. Add user-facing notes under `## [Unreleased]` in `CHANGELOG.md`.
13+
2. Prepare the release PR:
14+
15+
```bash
16+
./scripts/release.sh prepare patch # or minor | major | 1.2.3
17+
```
18+
19+
3. Merge the PR after CI passes (including the changelog check).
20+
4. Publish from a clean default branch checkout:
21+
22+
```bash
23+
git checkout main # or master for hotdata-marimo
24+
git pull
25+
./scripts/release.sh publish
26+
```
27+
28+
## What happens automatically
29+
30+
Pushing a `vX.Y.Z` tag triggers two workflows:
31+
32+
| Workflow | Purpose |
33+
|----------|---------|
34+
| `publish.yml` | Build wheel/sdist and publish to PyPI |
35+
| `release.yml` | Create the GitHub Release with notes from `CHANGELOG.md` |
36+
37+
## Enforcement
38+
39+
- **PR check** (`check-release.yml`): if `pyproject.toml` version changes, `CHANGELOG.md` must contain a matching `## [X.Y.Z]` section.
40+
- **Tag check** (`publish.yml`): the tag (without `v`) must match `[project].version` in `pyproject.toml`.
41+
- **Publish guard** (`release.sh publish`): refuses to tag if the changelog section is missing.
42+
43+
Together, these make it hard to ship a version without changelog notes or a GitHub Release.

examples/llamaindex_basic.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Minimal LlamaIndex tool usage with hotdata-llamaindex."""
2+
3+
import hotdata_llamaindex as hli
4+
5+
6+
def main() -> None:
7+
client = hli.from_env()
8+
tools = hli.make_hotdata_tools(client)
9+
by_name = {tool.metadata.name: tool for tool in tools}
10+
11+
sql_tool = by_name["hotdata_execute_sql"]
12+
print(sql_tool.call(sql="SELECT 1 AS ok"))
13+
14+
list_tool = by_name["hotdata_list_managed_databases"]
15+
print(list_tool.call())
16+
17+
client.close()
18+
19+
20+
if __name__ == "__main__":
21+
main()

examples/llamaindex_managed_db.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Managed database tools for LlamaIndex agents."""
2+
3+
import hotdata_llamaindex as hli
4+
5+
6+
def main() -> None:
7+
client = hli.from_env()
8+
tools = hli.make_hotdata_tools(client)
9+
by_name = {tool.metadata.name: tool for tool in tools}
10+
11+
create = by_name["hotdata_create_managed_database"]
12+
print(
13+
create.call(
14+
name="demo_sales",
15+
schema_name="public",
16+
tables="orders\ncustomers",
17+
)
18+
)
19+
20+
load = by_name["hotdata_load_managed_table"]
21+
print(
22+
load.call(
23+
database="demo_sales",
24+
table="orders",
25+
file="/path/to/orders.parquet",
26+
schema_name="public",
27+
)
28+
)
29+
30+
client.close()
31+
32+
33+
if __name__ == "__main__":
34+
main()

0 commit comments

Comments
 (0)