Skip to main content

dvice

Reusable supervision of unreliable audio devices. dvice queries PortAudio in an isolated helper process, publishes current input-device snapshots, and restarts a stalled or failed query helper with bounded backoff. It also provides bounded process shutdown for applications that isolate audio streams in child processes.

dvice does not decide what to record, how to name tracks, or where to write media. Its first consumer is recs.

query_devices() returns a list on successful enumeration, including an empty list when no devices exist. Spawn, backend, timeout, and protocol failures raise exceptions instead of implying device removal. The streaming poller discards malformed observations without treating them as healthy updates. Both query paths reject output exceeding 1 MiB. One-shot output collection is nonblocking and deadline-limited, including a helper that leaves its stdout pipe open. Timeouts and interrupts kill and reap the direct child before propagating the exception, subject to the OS being able to terminate it.

Polling and lifecycle

Import DevicePoller from dvice.poller. It publishes only input-capable devices, as a list in enumeration order. Every endpoint is preserved, including duplicate names and endpoints exposed by different host APIs. Descriptions retain the backend's host API and any supplied identity fields. List positions and PortAudio indices identify only the current enumeration, not persistent hardware. Positions in this input-only list are not PortAudio indices; output-only endpoints have been filtered out. dvice does not invent missing endpoint identifiers. dvice does not match, merge, or deduplicate endpoints across refreshes; renaming and reordering appear as the backend reports them. Consumers must define their own matching policy and must not assume display names are unique. Read poller.status for a non-consuming DiscoveryStatus. Its devices is None before the first successful observation and [] after a successful enumeration with no inputs. Every reader gets an independent copy; modifying a returned list or description cannot change another reader's observation. Observations are latest-only, not a journal of every unplug/replug.

DiscoveryStatus, DiscoveryHealth, and DiscoveryFailure are defined in dvice.health. Status contains:

  • sequence: increases on each accepted observation or status transition. Reads do not advance it. Track this per reader to avoid processing the same state twice.
  • observed_at: host-monotonic receipt time of the last successful observation, or None. It is not the time of a physical device change or PortAudio refresh.
  • generation: helper generation that produced the cached devices, initially zero. During restart it still identifies the old observation, not the new helper.
  • health: idle, starting, healthy, refreshing, retrying, or stopped. Cached devices are current only while health is healthy.
  • failure: spawn, exited, stalled, protocol, reader, or cleanup, or None. Detailed errors remain in logs.
  • next_retry: host-monotonic scheduled retry time, or None when no retry is scheduled, including when cleanup has not completed.

Failures, refreshes, and stop retain the last successful devices and their receipt time, marked non-current by health. Never treat that cached list as a new removal observation. stopped means stop was requested; a cleanup failure means resource cleanup still needs retrying. Successful observations clear prior failures. Status reads perform no process creation, lifecycle waits, or supervision work, and do not acquire lifecycle locks. The polling thread advances supervision; manual DeviceQueryStream users must still call devices() regularly. Health is the supervisor's last assessment, not an independent watchdog or proof that an audio stream is delivering samples. Use receipt age when assessing freshness.

The former consumptive DevicePoller.latest() is replaced by status; there is no compatibility delivery path. For standalone DeviceQueryStream, status contains all endpoints rather than the poller's input-only view.

Use a finite positive polling interval (seconds). It is the delay between consumer polls, not the helper's enumeration rate. A poller is single-use: start it once, call stop() to cancel polling and clean up its helper, then join() to wait for the thread. join(timeout) only waits; it does not stop the helper or extend that timeout with additional cleanup. Interval waits are interruptible by stop(). The inherited context manager owns normal cleanup.

DeviceQueryStream.devices() consumes a validated update and advances helper supervision. stop() disables implicit starts; explicit start() re-enables them when backoff permits. restart() schedules replacement with backoff rather than guaranteeing an immediate new process. Backoff resets only after five seconds of continuous valid observations, not after one line in a crash loop. Old-generation and dead-helper observations are not delivered as fresh data. Healthy helpers are replaced every five seconds, without failure backoff, to refresh PortAudio initialization. No nested enumeration subprocess is needed. Discovery latency includes helper startup and the consumer polling interval; five seconds is a refresh cadence, not a sample-exact detection deadline.

Discovery cost measurements and a repeatable, discovery-only benchmark are in discovery-performance.md, including complete streaming lifetimes and concurrent helpers. Prefer one discovery owner per application; dispatch its observations rather than creating a helper for every consumer. Local macOS results do not establish cost or recovery bounds for other backends, and a slow consumer delays supervision rather than providing a cheap independent watchdog.

Terminate, kill, and reader cleanup share a five-second budget per cleanup operation. OS process creation and waiting for a concurrent lifecycle operation are outside that budget. If exit cannot be verified, the stream retains its handles and does not start another helper; callers can retry stop(). Reader messages are capped at 1 MiB, including unterminated output. Helper stderr is inherited so import and backend failures remain visible without accumulating an unbounded diagnostic buffer in dvice.

devices_json() queries PortAudio in the caller and is intended for the worker; it is not the isolated, timed API. stream_devices() is the blocking worker stdout loop, not an iterator or an audio stream.

Boundaries

dvice supervises discovery, not audio delivery. A listed endpoint can still be unresponsive. Consumers own stream opening/recovery, callback-progress monitoring, recording policy, and resource cleanup. dvice does not reset or reboot hardware. Snapshot coalescing can hide brief remove/reappear transitions, so enumeration alone cannot establish that an existing audio stream survived a device reboot.

Physical hot-plug and audio-service restart qualification on macOS, Windows, and Linux is separate from the unit suite; mocked snapshots do not prove hardware recovery. Helpers are trusted local commands, not a sandbox for arbitrary code. Custom commands must implement the newline-delimited device-list protocol in a single process and must not spawn descendants. dvice owns only its direct helper. The built-in helper watches a private stdin pipe held open by its parent and exits immediately on EOF, even if native enumeration is blocked. This also covers abrupt parent termination or a crash; it depends on the OS closing the parent's handles and scheduling the helper's watcher. It is not a hard real-time guarantee and cannot overcome an OS-uninterruptible process or a backend holding Python's interpreter lock indefinitely. Do not share or inherit the lifeline write handle. Custom helpers receive no lifeline and have no parent-death guarantee. Hosts must still arrange graceful cleanup, including retrying stop() if an interrupt aborts cleanup. Arbitrary process-tree ownership remains unsupported.

For standalone streaming supervision, keep ownership explicit:

from dvice.poller import DeviceQueryStream

stream = DeviceQueryStream()
try:
    stream.start()
    # Repeatedly call stream.devices() from the host's control loop.
finally:
    stream.stop()

DeviceDict, device_key(), and STABLE_DEVICE_ID_FIELDS are defined in dvice.device. The key helper prefers a supplied persistent ID but falls back to a display name, which is not guaranteed unique. threa supplies the inherited thread API. Release/version policy is unchanged; this checkout's lockfile is not a downstream application's dependency pin.

join_process() in dvice.supervision accepts a started, unclosed multiprocessing process and finite non-negative timeouts. Its nominal wait budget is timeout + 2 * stop_timeout. On verified exit it returns whether termination was forced; it raises TimeoutError if the process survives the final kill wait. The caller retains ownership on failure. Forced termination can corrupt consumer queues or leave locks held, so discard/recreate affected IPC before recovery. This utility does not repair arbitrary consumer state or terminate descendants.

Metadata

Release files for dvice 0.2.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 dvice 0.2.0
File Size Uploaded Provenance Actions
dvice-0.2.0.tar.gz 54.1 kB Without provenance Details for dvice-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dvice 0.2.0
File Interpreter ABI Platform Actions
dvice-0.2.0-py3-none-any.whl Python 3 none any Details for dvice-0.2.0-py3-none-any.whl

Total release size: 66.1 kB

Release files / dvice-0.2.0.tar.gz

Download URL dvice-0.2.0.tar.gz
Size 54.1 kB
Tags Source
SHA-256 checksum
How to use checksums
39b99b7c64e1fb41fc958cf5e78f018a211eca6f2be0a7f53acb777ad415e2a2
BLAKE2b-256 checksum
How to use checksums
75d725bddf5303606faae2f30cc0a312aa6b0b6fd2d093851be590a49c122321
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / dvice-0.2.0-py3-none-any.whl

Download URL dvice-0.2.0-py3-none-any.whl
Size 12.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0b5b7d1870cf1bbd6a081c19faa6779481a654b6d674989278c90210fa46abe2
BLAKE2b-256 checksum
How to use checksums
b62984defacfdbafa8904c870d8300c03ca7780c5a9ea37c3eb4afa49125f821
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for dvice 0.2.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

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