🟧 AUSerial
Truly async serial port for Linux/macOS using epoll/kqueue
Why AUSerial?
AUSerial (Async Unix Serial) is a minimal, dependency-free async serial port
for asyncio applications. It relies only on the standard library (os, termios,
asyncio) and plugs directly into the event loop via add_reader / add_writer —
which under the hood use epoll (Linux) or kqueue (macOS).
Comparison to existing librairies
| Library | Backend | Cost |
|---|---|---|
pyserial |
Blocking reads | Freezes the event loop |
aioserial |
run_in_executor around pyserial |
One thread per I/O operation |
pyserial-asyncio |
Transport/Protocol callback API | Verbose, subclass boilerplate |
| AUSerial | Direct add_reader / add_writer |
Zero threads, zero polling |
Features
- 🪶 ~80 lines, no external dependencies — just the standard library
- ⚡ Truly non-blocking — no thread pool, no busy loop
- 🔒 Concurrency-safe — internal locks prevent concurrent read/write conflicts
- 🧹 Clean resource management — async context manager + idempotent
close() - 🧯 Proper error propagation through
Futures (no silent failures) - 🧵 Pending operations are cancelled cleanly on close
Installation
pip install auserial
Or from source:
git clone https://github.com/papyDoctor/auserial.git
cd auserial
pip install -e .
Quick Start
import asyncio
from auserial import AUSerial
async def main():
async with AUSerial("/dev/ttyUSB0") as serial:
await serial.write(b"AT\r\n")
data = await serial.read()
print(f"Received: {data!r}")
asyncio.run(main())
Custom baudrate
import termios
from auserial import AUSerial
async with AUSerial("/dev/ttyUSB0", baudrate=termios.B9600) as serial:
...
Timeout
import asyncio
from auserial import AUSerial
async def main():
async with AUSerial("/dev/cu.usbmodem21301") as serial:
await serial.write(b"AT\r\n")
try:
data = await asyncio.wait_for(serial.read(), timeout=1.0)
except TimeoutError:
print("No response within 1s")
else:
print(f"Received: {data!r}")
asyncio.run(main())
Discovering ports
from auserial import list_ports
for p in list_ports():
print(p.path, p.description, p.hwid)
# /dev/cu.usbmodem21301 Raspberry Pi Pico USB VID:PID=2E8A:0008 SER=E660B4400765AB25
list_ports() is synchronous and returns list[PortInfo]. On Linux it reads
USB metadata from /sys/class/tty/<name>/device/. On macOS it parses ioreg
output and links each /dev/cu.* to its USB ancestor (Bluetooth and debug
consoles are filtered out). Pure stdlib, no extra dependency.
API
| Method / function | Description |
|---|---|
AUSerial(path, baudrate=...) |
Opens the tty in non-blocking mode |
await serial.open() |
Binds the instance to the current event loop |
await serial.read(n_bytes=64) |
Waits until data is available, returns bytes |
await serial.read_until(terminator=b"\n") |
Reads until the terminator is found, returns bytes |
await serial.write(data) |
Waits until writable, returns bytes written |
serial.close() |
Cancels pending I/O and closes the fd |
list_ports() -> list[PortInfo] |
Enumerate available serial ports (sync) |
PortInfo(path, description, hwid) |
NamedTuple returned by list_ports() |
The AUSerial class also implements __aenter__ / __aexit__, so
async with is the recommended usage pattern.
Limitations
- Unix-only. Relies on
termiosandadd_reader, which require an epoll/kqueue-compatible file descriptor. Windows needs a different implementation (IOCP). - A single call to
write()issues oneos.write— short writes are returned as-is (caller retries with the remainder if needed).
Examples
More usage patterns live in examples/.
License
Metadata
Release files for auserial 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| auserial-1.0.1.tar.gz | 18.7 kB | Without provenance | Details for auserial-1.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| auserial-1.0.1-py3-none-any.whl | Python 3 | none | any | Details for auserial-1.0.1-py3-none-any.whl |
Total release size: 28.6 kB
Release files / auserial-1.0.1.tar.gz
| Download URL | auserial-1.0.1.tar.gz |
|---|---|
| Size | 18.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ff5ca0f48e09071305c777b1b3f192c4e29e0ab94ca92aec94f6cc607ae6cd12
|
|
BLAKE2b-256 checksum How to use checksums |
69b3e05839a4000725a5b61a3b95266f97eaa93801f61ae0954b74dcba54ce75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / auserial-1.0.1-py3-none-any.whl
| Download URL | auserial-1.0.1-py3-none-any.whl |
|---|---|
| Size | 9.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e7a5bb5018ee5781aa8ab09b048b5660db3c8d0f9c0ea16f1a11169e36301ca4
|
|
BLAKE2b-256 checksum How to use checksums |
9280f3f4a532807f3e98fe981c0a5ca70edee509373f2f803152caf607436bb4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for auserial 1.0.1
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.