Skip to content

Commit 453014b

Browse files
fix: address CLI UX feedback — file input, region hints, comment handling (#12)
## Summary Addresses CLI feedback from a marketing data engineer building a semantic layer with Dremio Cloud EU + Claude Code: - **`--file` and stdin support for `dremio query run`** — Multi-line SQL (CREATE VIEW, CTEs) no longer requires shell escaping. Supports `--file query.sql`, piped stdin, and `dremio query run -` for reading from stdin. - **401 region hint for EU users** — When a 401 comes from the default US endpoint (`api.dremio.cloud`), the error now suggests checking the EU endpoint. Previously took ~45 min to diagnose. - **Fix `--` SQL comments being swallowed** — The breadcrumb comment (`/* dremio-cli: submitter=cli */`) is now on its own line, so `--` inline comments in user SQL no longer comment out the rest of the query. - **Config path in `dremio setup --help`** — Help text now shows where the config file is written and that region (US/EU) is prompted. ## Test plan - [ ] `dremio query run "SELECT 1"` — inline arg still works - [ ] `dremio query run --file query.sql` — reads and executes SQL from file - [ ] `echo "SELECT 1" | dremio query run -` — reads from stdin - [ ] `dremio query run` with no args — shows helpful error - [ ] `dremio query run --file missing.sql` — shows file-not-found error - [ ] `dremio query run "-- comment\nSELECT 1"` — comment doesn't break query - [ ] Verify 401 against US endpoint includes EU region hint in error message - [ ] `dremio setup --help` shows config file path and region info 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 39f8411 commit 453014b

5 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/drs/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ async def delete_project(self, project_id: str) -> dict:
164164

165165
async def submit_sql(self, sql: str, context: list[str] | None = None) -> dict:
166166
"""Submit a SQL query. Returns job metadata including job_id."""
167-
body: dict[str, Any] = {"sql": f"/* dremio-cli: submitter=cli */ {sql}"}
167+
body: dict[str, Any] = {"sql": f"/* dremio-cli: submitter=cli */\n{sql}"}
168168
if context:
169169
body["context"] = context
170170
return await self._post(self._v0("/sql"), json=body)

src/drs/commands/query.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from __future__ import annotations
1919

2020
import asyncio
21-
from typing import Any
21+
from pathlib import Path
22+
from typing import Annotated, Any
2223

2324
import httpx
2425
import typer
@@ -131,7 +132,20 @@ async def _execute():
131132

132133
@app.command("run")
133134
def cli_run(
134-
sql: str = typer.Argument(help="SQL query to execute"),
135+
sql: str | None = typer.Argument(None, help="SQL query to execute (use '-' to read from stdin)"),
136+
file: Annotated[
137+
Path | None,
138+
typer.Option(
139+
"--file",
140+
exists=True,
141+
file_okay=True,
142+
dir_okay=False,
143+
writable=False,
144+
readable=True,
145+
allow_dash=True,
146+
help="Path to a SQL file to execute (use '-' for stdin)",
147+
),
148+
] = None,
135149
context: str = typer.Option(None, help="Dot-separated default schema context (e.g., myspace.folder)"),
136150
fmt: OutputFormat = typer.Option(OutputFormat.json, "--output", "-o", help="Output format"),
137151
fields: str = typer.Option(
@@ -140,10 +154,31 @@ def cli_run(
140154
) -> None:
141155
"""Execute a SQL query, wait for completion, and return results.
142156
157+
SQL can be provided as:
158+
159+
dremio query run "SELECT 1" # inline argument
160+
161+
dremio query run --file query.sql # from a file
162+
163+
cat query.sql | dremio query run - # from stdin
164+
143165
Submits the query, polls until the job completes, then fetches all result
144166
rows. For long-running queries, use 'drs query run' to submit and
145167
'drs query status' to check progress separately.
146168
"""
169+
# Resolve SQL from argument, --file, or stdin
170+
if file is not None:
171+
if sql is not None:
172+
error("Cannot specify both a SQL argument and --file.")
173+
raise typer.Exit(1)
174+
sql = file.read_text().strip()
175+
elif sql is not None:
176+
sql = sql.strip()
177+
178+
if not sql:
179+
error("SQL query is empty. Provide SQL as an argument, --file path, or pipe via stdin (use '-' for stdin).")
180+
raise typer.Exit(1)
181+
147182
client = _get_client()
148183
ctx = context.split(".") if context else None
149184

src/drs/commands/setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ def _prompt_project_id(app_url: str) -> str:
147147
def setup_command(
148148
ctx: typer.Context,
149149
) -> None:
150-
"""Interactive setup wizard — configure credentials for Dremio Cloud."""
150+
"""Interactive setup wizard — configure credentials for Dremio Cloud.
151+
152+
Writes configuration to ~/.config/dremioai/config.yaml (or the path
153+
specified with --config). Prompts for region (US/EU), PAT, and project ID.
154+
"""
151155
if not sys.stdin.isatty():
152156
err_console.print(
153157
"[bold]dremio setup[/bold] requires an interactive terminal.\n\n"

src/drs/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ def handle_api_error(exc: httpx.HTTPStatusError) -> DremioAPIError:
202202

203203
if status == 401:
204204
hint = "Authentication failed — check your PAT token"
205+
if "api.dremio.cloud" in url and "api.eu.dremio.cloud" not in url:
206+
hint += ". If you are on Dremio Cloud EU, set uri: https://api.eu.dremio.cloud in your config"
205207
elif status == 403:
206208
hint = "Permission denied — insufficient privileges for this operation"
207209
elif status == 404:

tests/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ async def _capture(request: httpx.Request) -> httpx.Response:
119119

120120
await client.submit_sql("SELECT 1")
121121

122-
assert captured["body"]["sql"] == "/* dremio-cli: submitter=cli */ SELECT 1"
122+
assert captured["body"]["sql"] == "/* dremio-cli: submitter=cli */\nSELECT 1"
123123

124124
@pytest.mark.asyncio
125125
async def test_submit_sql_breadcrumb_with_context(self, client: DremioClient) -> None:

0 commit comments

Comments
 (0)