pySerial
A cross-platform Python library that exposes a single, uniform class-based API for reading, writing, and configuring serial ports on Windows, macOS, Linux, and BSD.
Repository Health
Technical Analysis
pySerial encapsulates serial port access behind one Serial class whose interface is identical no matter which operating system it runs on. It auto-selects a POSIX or Windows backend at import time, so code that opens a port, sets baud rate/parity/stop bits, and reads or writes bytes never has to branch on platform.
Beyond the core Serial class, the package bundles a URL-based connection scheme (serial_for_url()) that can address loopback ports, RFC 2217 remote serial bridges, or custom protocol handlers through a connection string instead of a raw device path, a serial.threaded module for event-driven reads via a Protocol/Transport pattern, cross-platform port enumeration through serial.tools.list_ports, and a built-in terminal program (pyserial-miniterm).
It has shipped as the de facto standard serial library for the Python ecosystem for over two decades, and is packaged directly by most major Linux distributions (Debian, Fedora, Arch, Gentoo) as well as conda-forge.
What You Get
- A
Serialclass with the same constructor, properties (baudrate, bytesize, parity, stopbits, timeout, rtscts, xonxoff), and file-likeread/write/readlinemethods across every supported platform. serial_for_url()— open loopback (loop://), RFC 2217 remote (rfc2217://), hardware-grep (hwgrep://), or byte-spying (spy://) pseudo-ports through a connection string, extensible with custom protocol handlers.serial.tools.list_portsfor cross-platform enumeration of available serial devices, returning descriptions and hardware IDs, plus apyserial-portsconsole entry point.pyserial-miniterm, a built-in console terminal client for interacting with a serial device directly from the command line.serial.threaded— a Protocol/Transport abstraction (connection_made/data_received/connection_lost) for running serial I/O on a background reader thread without hand-rolled polling loops.- An experimental RFC 2217 client implementation (
serial.rfc2217) for accessing serial ports exposed over a network by a remote server.
Common Use Cases
- Talking to embedded devices, microcontrollers, or industrial equipment connected via USB-to-serial or native RS-232.
- Building a Python-based terminal or logging tool for hardware debugging using
pyserial-minitermor a customserial.threaded.Protocol. - Enumerating and auto-detecting connected serial devices (e.g. Arduino boards, GPS receivers, modems) at application startup.
- Bridging a physical serial port over a network with the RFC 2217 client/server so remote software can access hardware attached to a different machine.
Under The Hood
Architecture
pySerial wraps platform-specific backends (serialposix.py, serialwin32.py) behind a common SerialBase defined in serialutil.py, with serial/__init__.py selecting the right implementation at import time via an os.name check. A URL-dispatch layer (serial_for_url()) provides a plugin-style mechanism where serial/urlhandler/protocol_*.py modules (loop, rfc2217, hwgrep, spy, cp2110, socket, alt) register handlers for different connection schemes, letting callers swap transports by changing a connection string rather than instantiation code. Additional layers sit on top of the core class: serial/threaded/ for event-driven reads via reader threads and Protocol/Packetizer/LineReader classes, serial/tools/ for port enumeration and the bundled terminal, and serial/rfc2217.py implementing the RFC 2217 telnet-based remote serial protocol. Because every backend and URL handler subclasses or wraps SerialBase, it is the module’s single point of contract enforcement.
Tech Stack
The library is pure Python with no compiled extensions, targets Python 3.10+ per pyproject.toml, and builds via setuptools with a dynamically resolved version pulled from serial.__version__. An optional hidapi dependency enables CP2110 USB-HID-to-UART bridge support. Testing runs on pytest (configured in pyproject.toml, testpaths = ["test"]) with coverage.py branch coverage tracking, flake8 linting is configured via setup.cfg, and GitHub Actions (.github/workflows/test.yaml) runs CI on each change. Documentation is authored in reStructuredText and published through ReadTheDocs.
Code Quality
The test/ directory holds roughly fifteen focused test modules (context managers, RS-485, RFC 2217, timeout handling, threaded reads, URL dispatch, and more) runnable via pytest or test/run_all_tests.py, and CI enforces they pass on every push. Error handling goes through a dedicated SerialException hierarchy (SerialException, SerialTimeoutException, PortNotOpenError) rather than raw OS errors, and newer modules like serialutil.py’s Timeout class and list_ports.py carry modern type annotations, though type-hint coverage is not uniform across the older platform backends. Naming is consistently snake_case, and every source file carries an SPDX license header.
API Design
pySerial’s defining design choice is that the Serial class’s constructor signature and property-based configuration are identical on every supported platform, so application code needs zero conditional branching for OS differences. The URL-scheme mechanism extends that same class to loopback, RFC-2217-bridged, and byte-spying transports without changing call sites, and third-party protocol handlers can be registered by appending to protocol_handler_packages. serial.threaded mirrors an asyncio-style Protocol/Transport pattern for callback-driven reads instead of requiring hand-written polling loops, and the library ships both a port-enumeration API and a full terminal client (miniterm) in the same package — a combination few comparable serial libraries bundle together.