hotdata-langchain 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.
@@ -0,0 +1,9 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: uv
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ allow:
8
+ - dependency-name: hotdata
9
+ - dependency-name: hotdata-runtime
@@ -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
@@ -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
@@ -0,0 +1,17 @@
1
+ name: Dependabot auto-merge
2
+
3
+ on: pull_request
4
+
5
+ permissions:
6
+ contents: write
7
+ pull-requests: write
8
+
9
+ jobs:
10
+ auto-merge:
11
+ runs-on: ubuntu-latest
12
+ if: github.actor == 'dependabot[bot]'
13
+ steps:
14
+ - name: Enable auto-merge
15
+ run: gh pr merge --squash --auto "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
16
+ env:
17
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -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-langchain
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
@@ -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
@@ -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
@@ -0,0 +1,44 @@
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
+
11
+ ## [0.2.1] - 2026-06-22
12
+
13
+ ### Changed
14
+
15
+ - Pin `hotdata-runtime` to `>=0.3.0` (adds the typed-error API:
16
+ `HotdataError`/`HotdataTransientError`/`HotdataTerminalError`/`classify_sdk_error`).
17
+ No code adoption was required: this package has no SDK error-handling call sites — its
18
+ runtime calls are thin pass-throughs exposed as LangChain `StructuredTool`s, which let
19
+ exceptions propagate to the LangChain runtime by design.
20
+
21
+ ## [0.2.0] - 2026-06-22
22
+
23
+ ### Changed
24
+
25
+ - Upgrade `hotdata` SDK pin to `>=0.4.1` and `hotdata-runtime` to `>=0.2.4`.
26
+ - Raise `langchain-core` floor to `>=1.0` (verified against the test suite).
27
+
28
+ ### Added
29
+
30
+ - Ruff and mypy tooling configuration in `pyproject.toml`, plus `ruff` and `mypy`
31
+ dev dependencies. Applied `ruff check --fix` and `ruff format` cleanup across the
32
+ codebase.
33
+
34
+ ## [0.1.1] - 2026-06-01
35
+
36
+ ### Changed
37
+
38
+ - Release 0.1.1
39
+
40
+ ## [0.1.0] - 2026-05-19
41
+
42
+ ### Added
43
+
44
+ - Initial release with LangChain tools for Hotdata managed databases.
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: hotdata-langchain
3
+ Version: 0.2.1
4
+ Summary: LangChain tools for Hotdata runtime
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: hotdata-runtime>=0.3.0
8
+ Requires-Dist: hotdata>=0.4.1
9
+ Requires-Dist: langchain-core>=1.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # hotdata-langchain
13
+
14
+ Give your [LangChain](https://python.langchain.com/) agents access to [Hotdata](https://hotdata.dev) — run SQL against your workspace connections and work with managed databases.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install hotdata-langchain
20
+ ```
21
+
22
+ ## Authentication
23
+
24
+ Set `HOTDATA_API_KEY` in your environment. Optionally set `HOTDATA_WORKSPACE` to pin a specific workspace (the first available workspace is used if unset).
25
+
26
+ ## Quickstart
27
+
28
+ ```python
29
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
30
+ import hotdata_langchain as hl
31
+
32
+ client = hl.from_env()
33
+ tools = hl.make_hotdata_tools(client)
34
+
35
+ agent = create_tool_calling_agent(llm=your_llm, tools=tools, prompt=your_prompt)
36
+ executor = AgentExecutor(agent=agent, tools=tools)
37
+ result = executor.invoke({"input": "How many rows are in the orders table?"})
38
+ ```
39
+
40
+ ## Tools
41
+
42
+ `make_hotdata_tools(client)` returns a list of LangChain `StructuredTool` objects ready to pass to any agent:
43
+
44
+ | Tool | What it does |
45
+ |------|-------------|
46
+ | `hotdata_execute_sql` | Run a SQL query and return rows as JSON |
47
+ | `hotdata_list_managed_databases` | List available managed databases |
48
+ | `hotdata_create_managed_database` | Create a new managed database |
49
+ | `hotdata_load_managed_table` | Load a parquet file into a managed table |
50
+
51
+ ## Calling tools directly
52
+
53
+ You can also invoke tools outside of an agent loop:
54
+
55
+ ```python
56
+ tools = {t.name: t for t in hl.make_hotdata_tools(client)}
57
+
58
+ result = tools["hotdata_execute_sql"].invoke({"sql": "SELECT * FROM orders LIMIT 10"})
59
+ print(result) # JSON rows
60
+
61
+ tools["hotdata_create_managed_database"].invoke({
62
+ "name": "sales",
63
+ "schema_name": "public",
64
+ "tables": "orders,customers",
65
+ })
66
+
67
+ tools["hotdata_load_managed_table"].invoke({
68
+ "database": "sales",
69
+ "table": "orders",
70
+ "file": "/path/to/orders.parquet",
71
+ })
72
+ ```
73
+
74
+ ## Scoping queries to a managed database
75
+
76
+ Pass `database=` so all SQL the agent runs resolves against a specific managed database:
77
+
78
+ ```python
79
+ tools = hl.make_hotdata_tools(client, database="sales")
80
+ ```
81
+
82
+ ## Controlling result size
83
+
84
+ Limit how many rows are returned to the LLM. Useful for keeping responses within context limits (default: 100):
85
+
86
+ ```python
87
+ tools = hl.make_hotdata_tools(client, max_rows=50)
88
+ ```
89
+
90
+ ## Run the examples
91
+
92
+ ```bash
93
+ uv run python examples/langchain_basic.py
94
+ uv run python examples/langchain_managed_db.py
95
+ ```
96
+
97
+ ## Development
98
+
99
+ ```bash
100
+ uv sync --locked
101
+ uv run pytest
102
+ ```
@@ -0,0 +1,91 @@
1
+ # hotdata-langchain
2
+
3
+ Give your [LangChain](https://python.langchain.com/) agents access to [Hotdata](https://hotdata.dev) — run SQL against your workspace connections and work with managed databases.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install hotdata-langchain
9
+ ```
10
+
11
+ ## Authentication
12
+
13
+ Set `HOTDATA_API_KEY` in your environment. Optionally set `HOTDATA_WORKSPACE` to pin a specific workspace (the first available workspace is used if unset).
14
+
15
+ ## Quickstart
16
+
17
+ ```python
18
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
19
+ import hotdata_langchain as hl
20
+
21
+ client = hl.from_env()
22
+ tools = hl.make_hotdata_tools(client)
23
+
24
+ agent = create_tool_calling_agent(llm=your_llm, tools=tools, prompt=your_prompt)
25
+ executor = AgentExecutor(agent=agent, tools=tools)
26
+ result = executor.invoke({"input": "How many rows are in the orders table?"})
27
+ ```
28
+
29
+ ## Tools
30
+
31
+ `make_hotdata_tools(client)` returns a list of LangChain `StructuredTool` objects ready to pass to any agent:
32
+
33
+ | Tool | What it does |
34
+ |------|-------------|
35
+ | `hotdata_execute_sql` | Run a SQL query and return rows as JSON |
36
+ | `hotdata_list_managed_databases` | List available managed databases |
37
+ | `hotdata_create_managed_database` | Create a new managed database |
38
+ | `hotdata_load_managed_table` | Load a parquet file into a managed table |
39
+
40
+ ## Calling tools directly
41
+
42
+ You can also invoke tools outside of an agent loop:
43
+
44
+ ```python
45
+ tools = {t.name: t for t in hl.make_hotdata_tools(client)}
46
+
47
+ result = tools["hotdata_execute_sql"].invoke({"sql": "SELECT * FROM orders LIMIT 10"})
48
+ print(result) # JSON rows
49
+
50
+ tools["hotdata_create_managed_database"].invoke({
51
+ "name": "sales",
52
+ "schema_name": "public",
53
+ "tables": "orders,customers",
54
+ })
55
+
56
+ tools["hotdata_load_managed_table"].invoke({
57
+ "database": "sales",
58
+ "table": "orders",
59
+ "file": "/path/to/orders.parquet",
60
+ })
61
+ ```
62
+
63
+ ## Scoping queries to a managed database
64
+
65
+ Pass `database=` so all SQL the agent runs resolves against a specific managed database:
66
+
67
+ ```python
68
+ tools = hl.make_hotdata_tools(client, database="sales")
69
+ ```
70
+
71
+ ## Controlling result size
72
+
73
+ Limit how many rows are returned to the LLM. Useful for keeping responses within context limits (default: 100):
74
+
75
+ ```python
76
+ tools = hl.make_hotdata_tools(client, max_rows=50)
77
+ ```
78
+
79
+ ## Run the examples
80
+
81
+ ```bash
82
+ uv run python examples/langchain_basic.py
83
+ uv run python examples/langchain_managed_db.py
84
+ ```
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ uv sync --locked
90
+ uv run pytest
91
+ ```
@@ -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.
@@ -0,0 +1,21 @@
1
+ """Minimal LangChain tool usage with hotdata-langchain."""
2
+
3
+ import hotdata_langchain as hl
4
+
5
+
6
+ def main() -> None:
7
+ client = hl.from_env()
8
+ tools = hl.make_hotdata_tools(client)
9
+ by_name = {tool.name: tool for tool in tools}
10
+
11
+ sql_tool = by_name["hotdata_execute_sql"]
12
+ print(sql_tool.invoke({"sql": "SELECT 1 AS ok"}))
13
+
14
+ list_tool = by_name["hotdata_list_managed_databases"]
15
+ print(list_tool.invoke({}))
16
+
17
+ client.close()
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
@@ -0,0 +1,38 @@
1
+ """Managed database tools for LangChain agents."""
2
+
3
+ import hotdata_langchain as hl
4
+
5
+
6
+ def main() -> None:
7
+ client = hl.from_env()
8
+ tools = hl.make_hotdata_tools(client)
9
+ by_name = {tool.name: tool for tool in tools}
10
+
11
+ create = by_name["hotdata_create_managed_database"]
12
+ print(
13
+ create.invoke(
14
+ {
15
+ "name": "demo_sales",
16
+ "schema_name": "public",
17
+ "tables": "orders\ncustomers",
18
+ }
19
+ )
20
+ )
21
+
22
+ load = by_name["hotdata_load_managed_table"]
23
+ print(
24
+ load.invoke(
25
+ {
26
+ "database": "demo_sales",
27
+ "table": "orders",
28
+ "file": "/path/to/orders.parquet",
29
+ "schema_name": "public",
30
+ }
31
+ )
32
+ )
33
+
34
+ client.close()
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main()
@@ -0,0 +1,38 @@
1
+ """LangChain tools for Hotdata runtime."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("hotdata-langchain")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0+unknown"
9
+
10
+ from hotdata_runtime import HotdataClient, QueryResult, from_env
11
+
12
+ from hotdata_langchain.databases import (
13
+ create_managed_database,
14
+ list_managed_databases_json,
15
+ load_managed_table,
16
+ load_result_summary,
17
+ managed_database_summary,
18
+ )
19
+ from hotdata_langchain.tools import (
20
+ execute_sql_json,
21
+ make_hotdata_tools,
22
+ result_rows_for_llm,
23
+ )
24
+
25
+ __all__ = [
26
+ "HotdataClient",
27
+ "QueryResult",
28
+ "__version__",
29
+ "create_managed_database",
30
+ "execute_sql_json",
31
+ "from_env",
32
+ "list_managed_databases_json",
33
+ "load_managed_table",
34
+ "load_result_summary",
35
+ "make_hotdata_tools",
36
+ "managed_database_summary",
37
+ "result_rows_for_llm",
38
+ ]
@@ -0,0 +1,60 @@
1
+ """Managed database helpers for LangChain agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from hotdata_runtime import (
9
+ DEFAULT_SCHEMA,
10
+ HotdataClient,
11
+ LoadManagedTableResult,
12
+ ManagedDatabase,
13
+ )
14
+
15
+
16
+ def list_managed_databases_json(client: HotdataClient) -> str:
17
+ rows = [
18
+ {
19
+ "description": db.description,
20
+ "id": db.id,
21
+ "sql_prefix": f"{db.id}.{{schema}}.{{table}}",
22
+ }
23
+ for db in client.list_managed_databases()
24
+ ]
25
+ return json.dumps(rows, indent=2)
26
+
27
+
28
+ def create_managed_database(
29
+ client: HotdataClient,
30
+ *,
31
+ name: str,
32
+ schema: str = DEFAULT_SCHEMA,
33
+ tables: list[str] | None = None,
34
+ ) -> ManagedDatabase:
35
+ return client.create_managed_database(description=name, schema=schema, tables=tables)
36
+
37
+
38
+ def load_managed_table(
39
+ client: HotdataClient,
40
+ *,
41
+ database: str,
42
+ table: str,
43
+ file: str,
44
+ schema: str = DEFAULT_SCHEMA,
45
+ ) -> LoadManagedTableResult:
46
+ return client.load_managed_table(database, table, schema=schema, file=file)
47
+
48
+
49
+ def managed_database_summary(db: ManagedDatabase) -> dict[str, str]:
50
+ return {"id": db.id, "description": db.description or db.id}
51
+
52
+
53
+ def load_result_summary(result: LoadManagedTableResult) -> dict[str, Any]:
54
+ return {
55
+ "connection_id": result.connection_id,
56
+ "schema_name": result.schema_name,
57
+ "table_name": result.table_name,
58
+ "row_count": result.row_count,
59
+ "full_name": result.full_name,
60
+ }