Skip to main content

Driver Server client with an embeddable connector for config injection at runtime.

Project description

driverclient

Embeddable client for a Driver Server local repo. Scans a Windows machine's hardware, resolves and installs drivers from the repo, and captures drivers back up to it. Designed to be driven programmatically from a host application (for example a PyQt front-end) via a small connector class.

Install

pip install driverclient

Usage

Create one DriverClient per process and call .run():

from driverclient import DriverClient

client = DriverClient(
    local_repo_url="http://REPO_HOST:8000",
    node_key="YOUR_NODE_KEY",
)
result = client.run("capture-all")   # or client.run() for the configured default

Configuration

Config can be supplied three ways and is merged with this priority (highest wins):

DEFAULTS  <  DS_CLIENT_CONFIG env file  <  config_path file  <  keyword args
  • Args only — pass keys directly:
    DriverClient(local_repo_url="http://x:1", node_key="k")
    
  • File only — pass a JSON file used as the base:
    DriverClient(config_path="/etc/driverclient.json")
    
  • Both — file as the base, individual keys overridden by kwargs:
    DriverClient(config_path="/etc/driverclient.json", node_key="override")
    

Passing nothing preserves the default behavior (env file → packaged config.json → built-in defaults).

A dummy config.json ships inside the package as a template. Replace REPLACE_WITH_YOUR_NODE_KEY and point local_repo_url at your repo — or, better, supply real values at runtime through the connector.

Commands

scan, resolve, resolve-and-install, capture-all, capture-missing, wu-update, wu-full, automate.

client.run() with no argument uses DS_CLIENT_COMMAND, then the default_command from config.

Progress events

Every operation reports progress in real time through a structured event stream (added in 0.2.0). Pass an on_event callback to run() and it is invoked synchronously for each step:

def on_event(ev):        # ev is a driverclient.ClientEvent
    print(ev.phase, ev.status, ev.message)

client.run("automate", on_event=on_event)

When no callback is given, events fall back to print + the driverclient logger, so the terminal/log experience is unchanged.

Each ClientEvent (frozen dataclass, ev.to_dict() for transport) has:

field type meaning
phase str scan resolve install capture windows_update dump upload pipeline done
status str start progress ok warn error done
message str human-readable line (what the fallback prints)
current int | None counter position (e.g. driver 3 of 12)
total int | None counter total
percent float | None 0–100 for long single operations
data dict structured specifics (driver_name, hwid, path, error, …)
ts float time.time() at emit

phase and status are a fixed, documented vocabulary — this is the interface a GUI binds to; treat changes to it as an API change.

Reboots (the client does not reboot — the caller decides)

The client is stateless and single-shot: it installs/captures in one pass and returns. It never reboots and keeps no state across reboots. When a driver installs but needs a reboot to take effect (installer exit code 3010/1641), that is reported as success with a reboot flag, not a failure:

  • InstallResult.reboot_required / AutomateResult.reboot_requiredbool
  • the per-driver install event carries data["reboot_required"]

The embedding app owns the reboot + resume loop. Typical convergence:

result = client.run("automate", on_event=cb)
if result.reboot_required:
    persist_state(); schedule_resume(); reboot()      # then run "automate" again
elif not result.made_progress:
    proceed_to_next_step()                             # nothing new + no reboot = converged

AutomateResult.made_progress is True when the pass installed or captured anything, so not made_progress and not reboot_required means the machine has converged. Drivers for devices that only appear after a reboot are picked up on the next pass; run a capture-missing / wu-full pass post-reboot to catch packages Windows Update only stages during the reboot.

Events are emitted on whatever thread the op is running on — and the parallel download/export/upload pools mean some events arrive on worker threads. The Qt pattern below (queued cross-thread signals) is safe regardless.

PyQt consumer pattern

Run the (minutes-long, blocking) client.run(...) on a QThread, never the UI thread. The on_event callback must not touch widgets — it emits a Qt signal carrying event.to_dict(); a slot on the UI thread updates the widgets. Qt delivers cross-thread signals via the receiver's event loop (queued), which is the thread-safe way to drive the UI.

from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from driverclient import DriverClient


class Worker(QObject):
    event    = pyqtSignal(dict)     # carries ClientEvent.to_dict()
    finished = pyqtSignal(object)   # carries the op's result

    def __init__(self, command):
        super().__init__()
        self.command = command

    @pyqtSlot()
    def run(self):
        client = DriverClient(local_repo_url="http://REPO_HOST:8000",
                              node_key="YOUR_NODE_KEY")
        # Called on THIS worker thread — only emit a signal, never touch widgets.
        result = client.run(self.command, on_event=lambda ev: self.event.emit(ev.to_dict()))
        self.finished.emit(result)


class Panel:
    """Wire a worker onto a thread and bind its signals to UI-thread slots."""
    def start(self, command):
        self.thread = QThread()
        self.worker = Worker(command)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.event.connect(self.on_event)        # queued → runs on UI thread
        self.worker.finished.connect(self.on_finished)
        self.thread.start()

    @pyqtSlot(dict)
    def on_event(self, ev):
        # Safe: this runs on the UI thread. Update widgets here.
        self.status_label.setText(ev["message"])
        if ev["phase"] == "install" and ev["status"] in ("ok", "error"):
            self.step_list.addItem(ev["message"])
        if ev["total"] and ev["current"] is not None:
            self.progress_bar.setMaximum(ev["total"])
            self.progress_bar.setValue(ev["current"])
        elif ev["percent"] is not None:
            self.progress_bar.setValue(int(ev["percent"]))

    @pyqtSlot(object)
    def on_finished(self, result):
        self.thread.quit()
        self.thread.wait()

Requirements

  • Python >= 3.10
  • Windows target for the actual driver operations (pnputil, WMI, DISM).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

driverclient-0.2.2.tar.gz (31.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

driverclient-0.2.2-py3-none-any.whl (38.2 kB view details)

Uploaded Python 3

File details

Details for the file driverclient-0.2.2.tar.gz.

File metadata

  • Download URL: driverclient-0.2.2.tar.gz
  • Upload date:
  • Size: 31.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for driverclient-0.2.2.tar.gz
Algorithm Hash digest
SHA256 6c96bce8f3de9589ba51aa6ad46ca4d1120f0da2b2e13ca64a5daa9b967d9137
MD5 eb8205c8e828c86a1d6333ad612a3abb
BLAKE2b-256 3e2d2d0880a32d3573fd4464b43631b91c0678a401dfa5540196bf77906755ba

See more details on using hashes here.

File details

Details for the file driverclient-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: driverclient-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 38.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for driverclient-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7dc8ee0554e721ec5f5ced9ad5161c5f40e9bac4baf3d4d284d63e7579cb2598
MD5 f726d8faa8b106b9ee5542e97226dcc3
BLAKE2b-256 cda3e7cf2f58e72a67918d151972fab6444a53aba1ef52dd12a2dfcd7e1d0c96

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page