Skip to main content

qsv-client

PyPI

Run the qsv CSV toolkit from Python, without the usual subprocess pitfalls:

  • Timeouts that actually stop the work. On a timeout, task cancellation or Ctrl-C, the client stops qsv and every process qsv started: a polite stop first, then a forced kill after a grace period. Without that, a child qsv started would keep the output pipe open and hang the caller. See Platform notes for how this works on each OS.
  • Typed errors instead of stderr scraping. A failure raises a QsvError subclass (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().capabilities tells you the binary (qsv, qsvdp, qsvlite, qsvmcp), its version, compiled-in features and installed commands. It reads qsv --capabilities where available and falls back to parsing --version and --list.
  • Secrets stay off the command line. LLM settings for describegpt are passed as environment variables, so an API key never shows up in ps output.
  • Sync and asyncio clients with the same API. No runtime dependencies. Python 3.10+ on Linux, macOS and Windows.

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. text=False keeps stdout as bytes in res.stdout_bytes, for binary output such as snappy compress.

Client options

qsv = Qsv(
    "/opt/qsv/qsvlite",  # binary: path or name; default $QSV_BIN, then qsv/qsvmcp/qsvdp/qsvlite
    timeout=600,  # default per-run timeout in seconds; run(..., timeout=) overrides it
    kill_grace=5.0,  # seconds between the polite stop and the forced kill
    env={"QSV_MAX_JOBS": "4"},  # extra environment for every run; run(..., env=) adds more
    inherit_env=True,  # False starts from an empty environment (keeping SYSTEMROOT on Windows)
    cwd="/data",  # working directory for every run
)

A relative binary path is resolved against the current directory when the client is created, so it still works with cwd=. A relative stdout_path is resolved against cwd=, like a relative --output path given to qsv.

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 stops qsv and everything it started. This holds even when the cancellation comes from asyncio.run() shutting down. The teardown finishes before the cancellation propagates, so expect up to a few kill_grace periods.

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. If the binary's --version output isn't recognized (and --capabilities gave nothing usable), capabilities (and run with min_version) raise a plain QsvError.

Platform notes

On a timeout, cancellation or Ctrl-C, the client stops qsv and every process qsv started:

  • Linux, macOS: each run gets its own session. The client sends SIGTERM to the process group, then SIGKILL after kill_grace. A process that leaves the group (by calling setsid) survives. The timeout still fires on schedule, and any output that process still holds is dropped.
  • Windows: qsv is started suspended, put in its own Job Object, and only then resumed, so every process it starts is in the job and can't leave it. The client sends CTRL_BREAK_EVENT to the run's process group, then calls TerminateJobObject after kill_grace.

Development

uv sync
uv run pytest                        # integration tests need qsv: QSV_BIN=/path/to/qsv
uv run ruff format . && uv run ruff check .
uv run mypy && uv run mypy --platform win32   # also type-checks the Windows-only code

License

AGPL-3.0-or-later

Metadata

Release files for qsv-client 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qsv-client 0.3.0
File Size Uploaded
qsv_client-0.3.0.tar.gz 42.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qsv-client 0.3.0
File Interpreter ABI Platform
qsv_client-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 74.0 kB

Release files / qsv_client-0.3.0.tar.gz

Download URL qsv_client-0.3.0.tar.gz
Size 42.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f6f8598fb614305e13726c7d021b70cc7b859595aea8b8c533ffc897402372a3
BLAKE2b-256 checksum
How to use checksums
c45c9ee10459a01b9cbeb42e58353ca0063819e05a0df340f30c5e1cb941caed
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 10, 2026.

Transparency log

Release files / qsv_client-0.3.0-py3-none-any.whl

Download URL qsv_client-0.3.0-py3-none-any.whl
Size 32.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0b7ffb3faa88ce3d0dcb2bab87fe0008d2597c4285595016061ccc726dff5ba9
BLAKE2b-256 checksum
How to use checksums
43fe54d1fbc0c9679afef699c9156daa9b75d2b16e9a40b55d94433f9e1a6af4
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 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page