qsv-client
Run the qsv CSV toolkit from Python, without the usual subprocess pitfalls:
- Timeouts that actually stop the work. On timeout or task cancellation, the run's whole process group gets SIGTERM, then SIGKILL after a grace period. That includes children qsv started itself, which would otherwise keep the output pipe open and hang the caller.
- Typed errors instead of stderr scraping. A failure raises a
QsvErrorsubclass (QsvUsageError,QsvCsvError,QsvIOError,QsvNoMatch,QsvTimeout, ...) that carries the kind, message, exit code and command. Newer qsv builds report these as structured JSON (QSV_ERROR_FORMAT=json). Older ones are classified from their exit code. - Capability detection.
Qsv().capabilitiestells you the binary (qsv,qsvdp,qsvlite,qsvmcp), its version, compiled-in features and installed commands. It readsqsv --capabilitieswhere available and falls back to parsing--versionand--list. - Secrets stay off the command line. LLM settings for
describegptare passed as environment variables, so an API key never shows up inpsoutput. - Sync and asyncio clients with the same API. No runtime dependencies. Python 3.10+.
qsv itself is not bundled. Install it from the qsv releases or a package manager.
Install
pip install qsv-client
Usage
from qsv_client import Qsv, QsvCsvError, QsvTimeout
qsv = Qsv(timeout=600) # finds $QSV_BIN, else qsv/qsvmcp/qsvdp/qsvlite on PATH
qsv.count("data.csv") # -> 1000
qsv.headers("data.csv") # -> ["id", "name", ...]
stats = qsv.stats("data.csv", "--everything") # one dict per column
freq = qsv.frequency("data.csv", "--limit", "20")
# anything else: run(command, *args)
res = qsv.run("sqlp", "data.csv", "select count(*) from data")
res.stdout, res.stderr, res.exit_code, res.duration
res.csv_rows() # or res.json()
# large output: stream stdout to a file instead of holding it in memory
qsv.run("stats", "big.csv", "--everything", stdout_path="big.stats.csv")
try:
qsv.run("select", "a", "ragged.csv", timeout=30)
except QsvCsvError as e:
print(e.kind, e.exit_code, e.message)
except QsvTimeout:
...
check=False returns the result even when qsv fails. Exit codes 0 and 255 (qsv's "warning",
e.g. a broken pipe) count as success. stdin= feeds data on stdin; otherwise stdin is closed.
describegpt without exposing your key
import os
qsv = Qsv(
llm_api_key=os.environ["OPENROUTER_API_KEY"], # -> QSV_LLM_APIKEY
llm_base_url="https://openrouter.ai/api/v1", # -> QSV_LLM_BASE_URL
llm_model="google/gemini-2.5-flash-lite", # -> QSV_LLM_MODEL
)
dictionary = qsv.describegpt("data.csv", "--all") # adds --format json, returns parsed JSON
asyncio
from qsv_client import AsyncQsv
qsv = AsyncQsv(timeout=600)
n = await qsv.count("data.csv")
caps = await qsv.capabilities()
Cancelling the awaiting task kills the run's process group.
Capabilities and version checks
qsv = Qsv(min_version="24.0.0") # QsvVersionError on first use if older
caps = qsv.capabilities
caps.binary, caps.version, caps.features, caps.commands
caps.has_feature("polars"), caps.has_command("describegpt"), caps.supports_json_errors
Errors
| Exception | qsv error kind | Typical exit code |
|---|---|---|
QsvUsageError |
usage |
2 |
QsvCsvError |
csv |
1 |
QsvIOError |
io |
1 |
QsvNoMatch |
no_match (e.g. search found nothing) |
1 |
QsvNetworkError |
network |
3 |
QsvOutOfMemory |
out_of_memory |
4 |
QsvEncodingError |
encoding |
5 |
QsvInferenceError |
inference (an LLM call failed) |
1 |
QsvWriteError |
write (e.g. disk full) |
1 |
QsvTimeout |
timeout (killed by this client) |
124 |
QsvError |
anything else; base class of all of the above |
QsvError.structured is True when the error came from qsv's JSON error line. With qsv
versions that predate it, only the exit-code categories (usage, network, out of memory,
encoding) can be told apart; other failures are QsvError with kind unknown, and the
message is taken from stderr.
QsvNotFound (no binary) and QsvVersionError (binary too old) are raised before anything
runs.
Platform notes
Process-group cleanup uses POSIX sessions (Linux, macOS). On Windows, qsv is started in a new process group and killed directly; its own children are not reaped.
Development
uv sync
uv run pytest # integration tests need qsv: QSV_BIN=/path/to/qsv
uv run ruff check . && uv run mypy
License
Metadata
Release files for qsv-client 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qsv_client-0.1.0.tar.gz | 74.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qsv_client-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 101.2 kB
Release files / qsv_client-0.1.0.tar.gz
| Download URL | qsv_client-0.1.0.tar.gz |
|---|---|
| Size | 74.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee00c1b23b1c82d2aabe3c8f9ba1efc005af79a16417b051a5fb13c79e73cc92
|
|
BLAKE2b-256 checksum How to use checksums |
699f9b7e1dc62f6514e85f9d63ce518210cc31da5ffb499c4c122c599703785f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / qsv_client-0.1.0-py3-none-any.whl
| Download URL | qsv_client-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a2a04a93ee9f689a294fd659c4f2a627cc5462d5a618ab34edfe7cdc3802030
|
|
BLAKE2b-256 checksum How to use checksums |
67c5eb90cab38ba04bb698dfe1963d6ed1eb4b5278a3888520057aedbe8132da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency log