pyusb
Cross-platform Python library for direct USB device access via a backend-neutral libusb/OpenUSB API.
Repository Health
Technical Analysis
PyUSB lets Python programs talk to USB devices directly, without shelling out to platform-specific drivers or writing C extensions. It exposes a frontend-backend architecture: the usb.core, usb.util, and usb.control modules provide a single Pythonic API (device enumeration, configuration/interface/endpoint descriptors, control/bulk/interrupt/isochronous transfers), while an IBackend interface routes calls into whichever native USB stack is installed on the host — libusb 1.0, libusb 0.1, or OpenUSB.
This makes it the de facto choice for hardware automation, custom device drivers, test rigs, and instrument control scripts written in Python: usb.core.find() locates a device by vendor/product ID or a custom matcher, and the rest of the API mirrors the USB specification closely enough that anyone with basic protocol knowledge can read and write descriptors, claim interfaces, and move data without fighting the library itself.
What You Get
usb.core.find()for locating devices by vendor/product ID or a custom predicate function, includingfind_allfor enumerating every match- Full descriptor object model — Device, Configuration, Interface, and Endpoint classes that mirror the USB specification
- Support for all four USB transfer types (control, bulk, interrupt, isochronous) through a uniform read/write API
- Pluggable backend layer (
usb.backend) with built-in support for libusb 1.0, libusb 0.1, and OpenUSB, selectable at runtime or overridable with a custom backend - A
usb.legacycompatibility module for code still written against the pre-1.0 PyUSB API - String descriptor helpers (
usb.util.get_string,get_langids) and request-type builders (usb.util.build_request_type) for constructing raw control transfers
Common Use Cases
- Writing Python-based automation and test scripts that drive USB instruments, sensors, or manufacturing hardware
- Building custom drivers for USB peripherals that have no vendor-supplied Python SDK
- Reverse-engineering or debugging USB device protocols by inspecting raw descriptors and issuing manual control transfers
- Prototyping firmware-adjacent tooling before committing to a compiled driver, using PyUSB’s fast Python iteration loop
- Cross-platform device automation where the same script needs to run against libusb on Linux/macOS and Windows
Under The Hood
Architecture
PyUSB splits cleanly into a frontend and a backend. The frontend (usb.core, usb.util, usb.control) exposes the public object model — Device, Configuration, Interface, and Endpoint — plus usb.core.find() for discovery, while every actual USB operation is delegated through the IBackend interface implemented separately for libusb 1.0, libusb 0.1, and OpenUSB in usb/backend/. Supporting modules keep the frontend thin: _objfinalizer.py handles deterministic resource cleanup for device handles, _lookup.py holds USB descriptor lookup tables, and usb.legacy re-exposes the pre-1.0 API on top of the same core so old scripts keep working. Because every backend implements the same small IBackend contract, swapping libusb1 for OpenUSB (or a custom backend) changes zero frontend code — the abstraction has stayed stable since the 1.0 rewrite.
Tech Stack
Pure Python 3.9+ with no compiled extension modules — native USB access goes through ctypes bindings straight to the host’s libusb/OpenUSB shared library, so there’s nothing to build at install time, only a system library to have present. Packaging uses plain setuptools with setuptools_scm for git-tag-derived versioning, and the test matrix (Python 3.9 through 3.14) is driven by tox and run in GitHub Actions via a single run_tox.yml workflow.
Code Quality
Tests live under tests/ as stdlib unittest suites (test_core.py-style coverage of backend, control, find, util, legacy, and interop behavior), run through tests/testall.py and exercised across all six supported Python versions by tox in CI — a real, if not exhaustive, safety net. There’s no type-checking or linter configuration in the repo (no mypy/ruff/flake8 config, no CONTRIBUTING.md), and naming intentionally breaks PEP8 in places (bLength, bDescriptorType) to mirror USB specification field names rather than Python convention, which is a deliberate readability trade-off for anyone cross-referencing the USB spec.
API Design
The library’s strongest design choice is hiding three incompatible native USB stacks behind one small, stable interface — usb.core.find() plus a descriptor object model that mirrors the USB specification closely enough that protocol knowledge transfers directly into API knowledge, with minimal PyUSB-specific ceremony to learn on top.