Skip to content

Releases: dathere/qsv-client

0.4.0

Choose a tag to compare

@jqnatividad jqnatividad released this 15 Oct 22:06
102a0fd

qsv 25.0.0 is the first qsv release whose binaries (qsv, qsvlite, qsvdp, qsvmcp) all answer --capabilities and report errors as JSON. qsv-client 0.4.0 makes it the minimum and drops the code that supported older qsv.

⚠️ Behavior change: qsv 25.0.0 or later is required

Before its first command, every client now runs qsv --capabilities. Previously this check only happened when you passed min_version=. A qsv that is too old raises QsvVersionError instead of running:

qsv = Qsv("/usr/local/bin/qsv")   # qsv 24.0.0
qsv.count("data.csv")
# QsvVersionError: /usr/local/bin/qsv does not support --capabilities;
#                  qsv 25.0.0 or later is required
  • MIN_QSV_VERSION ("25.0.0") is exported. min_version= can still raise the floor, but not lower it.
  • The check uses the run's timeout (at most 60s), separately from the command. So a client's first run can take up to about twice its timeout. Later runs aren't affected.
  • If the check fails for another reason, such as a crash or a startup error, you get the usual QsvError with qsv's stderr, not a version error.

Upgrade qsv before upgrading qsv-client. (#9)

⚠️ Behavior change: QsvVersionError is now a QsvError

Because any run() can now raise it, QsvVersionError subclasses QsvError, with kind version. So except QsvError around run() catches a too-old binary too.

try:
    qsv.run("stats", "data.csv")
except QsvVersionError:   # must come first: a later `except QsvVersionError` is never reached
    ...
except QsvError:
    ...

Generic QsvError handlers now see version errors as well. Retry logic shouldn't retry them, because a too-old qsv will never succeed. The error's exit_code, args_run and stderr describe the --capabilities check. (#10)

Capabilities are checked once per process

--capabilities now runs at most once per qsv binary per Python process, and every Qsv and AsyncQsv shares the result. So creating a client per call (for example, one per web request) doesn't start an extra qsv each time.

  • Concurrent first calls from threads, or from coroutines on the same event loop, share one check.
  • A binary replaced in place (new size or modification time) is checked again.
  • Each client still applies its own min_version and gets its own copy of the result.
  • Values in raw that depend on the environment, such as max_jobs, come from the client that checked first.

(#9, #10)

Removed

  • The --version / --list fallback, including qsv_client.capabilities.from_version_output. If --capabilities succeeds but its output can't be used, capabilities raises QsvError. (#9)
  • Capabilities.supports_json_errors and Capabilities.error_formats. Every supported qsv reports JSON errors. The value is still in raw["error_formats"]. (#10)
  • Capabilities.raw is no longer optional. It's always the parsed --capabilities output, and Capabilities fields are now keyword-only. (#10)

Under the hood

  • Errors are still classified by exit code when qsv dies without writing a JSON error line, for example after a panic or a signal.
  • CI no longer runs the integration tests against a pinned qsv 24.0.0. Unit tests simulate a qsv that rejects --capabilities, and the integration tests check that every binary variant reports JSON errors.

Full changelog: 0.3.0...0.4.0

0.3.0

Choose a tag to compare

@jqnatividad jqnatividad released this 10 Oct 18:16
3105e48

⚠️ Behavior change: relative stdout_path now follows cwd=

A relative stdout_path is now resolved against the client's cwd=, the same way a relative --output path given to qsv is. Previously it was resolved against Python's working directory.

qsv = Qsv(cwd="/data")
qsv.run("stats", "in.csv", stdout_path="out.csv")
# 0.2.0: writes ./out.csv (relative to where Python runs)
# 0.3.0: writes /data/out.csv (next to in.csv)

This raises no error. If your code combines cwd= with a relative stdout_path, its output file will be written somewhere else after you upgrade. To keep the old location, pass an absolute path. (#7)

Fixed

  • AsyncQsv timeouts keep stderr. QsvTimeout.stderr now includes what qsv wrote before the timeout, as it already did for Qsv. Previously, stderr already read when the timeout fired was lost. (#6)
  • No bare ValueError from capability detection. If the binary's version can't be determined, capabilities (and run with min_version) raise QsvError. If --capabilities returns JSON of an unexpected shape or without a usable version, the client falls back to --version. (#7, #8)
  • Windows: binary= / QSV_BIN paths without an extension. C:/tools/qsv now finds C:/tools/qsv.exe, trying each PATHEXT extension. (#8)
  • The QsvError docstring now names the argv attribute correctly: args_run. (#7)

Under the hood

  • New tests exercise qsv's JSON error format and --capabilities end to end, plus binary discovery. The latest qsv release (24.0.0) supports neither format yet, so these paths were previously only tested at the parser level.
  • CI also runs the integration tests against a pinned qsv 24.0.0, so the text-error and --version fallbacks stay covered once newer qsv releases support JSON.
  • The publish workflow refuses to upload if the release tag doesn't match the package version.

Full changelog: 0.2.0...0.3.0

0.2.0

Choose a tag to compare

@jqnatividad jqnatividad released this 09 Oct 17:25

Added

  • Windows support. qsv is started suspended and placed in its own Job Object before it runs,
    so a timeout, cancellation or Ctrl-C stops qsv and everything it started:
    CTRL_BREAK_EVENT first, then TerminateJobObject after kill_grace. Previously only
    qsv.exe itself was killed. With inherit_env=False, SYSTEMROOT is still passed on
    Windows, since many programs fail to start without it. CI runs the full test suite on
    Windows. (#4,
    #5)

Changed

  • The sdist now contains only the package, tests, README, CHANGELOG and LICENSE. 0.1.0's also
    included repository tooling files (CLAUDE.md, .serena/, uv.lock, .github/). The wheel
    is unchanged. (#2)
  • Tested on Python 3.14 as well as 3.10–3.13. (#3)

Fixed

  • A timeout now raises within a few kill_grace periods even when a process qsv started has
    left the process group and still holds its output pipes, including when qsv ignores SIGTERM.
    Previously the run waited for that process to exit. Output it still holds is dropped, and
    AsyncQsv closes its pipe file descriptors instead of keeping them open until it exits.
    (#3)
  • Cancelling an AsyncQsv run now always kills qsv and everything it started, even when the
    cancellation arrives during timeout cleanup or comes from asyncio.run() shutting down. A
    cancelled run finishes its teardown before the cancellation propagates.
    (#3)
  • A second Ctrl-C while a sync run is waiting out kill_grace no longer skips the forced kill,
    which left a qsv that ignores the polite stop running. A Ctrl-C right after qsv starts is
    handled the same way. (#5)
  • On Windows with Python 3.10–3.12, a run whose child stops reading a large stdin= payload
    no longer hangs past its timeout. (#5)
  • A failed run whose stdout is not valid UTF-8 raises the typed QsvError instead of
    UnicodeDecodeError. (#3)
  • count() and headers() work with stdout_path= and text=False instead of raising
    IndexError. (#3)
  • A relative binary path now works together with cwd=. find_qsv returns absolute paths.
    (#3)

Full Changelog: 0.1.0...0.2.0

0.1.0

Choose a tag to compare

@jqnatividad jqnatividad released this 08 Oct 17:45

Initial qsv-client 0.1.0: sync + asyncio clients for running qsv #1

Full Changelog: https://github.com/dathere/qsv-client/commits/0.1.0