pyco-de-gallo
pyco-de-gallo exposes Pico de Gallo to Python as the pyco_de_gallo module.
It is built with PyO3 + maturin, and its API is intentionally boring in the
best way: open a device, call methods, get Python values back.
The key design point is that the Python surface is synchronous. Each
PycoDeGallo instance owns an internal Tokio runtime and drives the underlying
async Rust client for you.
That means you get Python-friendly code without giving up the Rust transport layer underneath.
Installation
pyproject.toml declares requires-python = ">=3.8".
From PyPI
When wheels are published, install it like any other Python package:
$ pip install pyco-de-gallo
From source with maturin
$ cd crates/pyco-de-gallo
$ python -m pip install maturin
$ maturin develop --release
If you want a wheel instead of an editable/development install:
$ cd crates/pyco-de-gallo
$ maturin build --release
Opening a Device
At module level you get five entry points:
list_devices()open()— lazy; failures surface on the first RPCopen_with_serial_number(serial_number)— lazy, selects by serialopen_strict()— validates the firmware’s schema version before returning; raisesRuntimeErroron mismatch or device-not-foundopen_strict_with_serial_number(serial_number)— strict variant with serial selection (recommended for production)
import pyco_de_gallo as gallo
for dev in gallo.list_devices():
print(dev.serial_number, dev.manufacturer, dev.product)
pg = gallo.open()
# or:
# pg = gallo.open_with_serial_number("E6633861A34B8C24")
The returned object is PycoDeGallo.
The PycoDeGallo Class
PycoDeGallo mirrors the Rust library closely.
- methods are synchronous from Python,
- Rust async work runs on an internal runtime,
- the GIL is released while the binding waits on USB I/O,
- most transport and endpoint failures become Python
RuntimeError.
That gives you a straightforward, script-friendly surface:
import pyco_de_gallo as gallo
pg = gallo.open()
print(pg.ping(123))
print(pg.version().major, pg.version().minor, pg.version().patch)
print(pg.device_info().hw_version)
Enums and Value Types
The public Python names intentionally do not carry a Py prefix. You use
plain Python-facing names like:
I2cFrequencySpiPhaseSpiPolarityGpioDirectionGpioPullGpioEdgeVersionInfoDeviceInfoUartConfigurationInfoSpiConfigurationInfoPwmDutyCycleInfoPwmConfigurationInfoAdcConfigurationInfo
Example:
import pyco_de_gallo as gallo
pg = gallo.open()
pg.i2c_set_config(gallo.I2cFrequency.Fast)
pg.spi_set_config(
1_000_000,
gallo.SpiPhase.CaptureOnFirstTransition,
gallo.SpiPolarity.IdleLow,
)
Example: I2C Register Read
import pyco_de_gallo as gallo
pg = gallo.open()
pg.i2c_set_config(gallo.I2cFrequency.Fast)
data = pg.i2c_write_read(0x48, [0x00], 2)
raw = int.from_bytes(data, byteorder="big")
print(f"raw=0x{raw:04x}")
Example: GPIO Blink
import time
import pyco_de_gallo as gallo
pg = gallo.open()
pg.gpio_set_config(0, gallo.GpioDirection.Output, gallo.GpioPull.Disabled)
for _ in range(10):
pg.gpio_put(0, True)
time.sleep(0.1)
pg.gpio_put(0, False)
time.sleep(0.1)
Example: ADC Read
import pyco_de_gallo as gallo
pg = gallo.open()
raw = pg.adc_read(0)
config = pg.adc_get_config()
voltage_mv = raw * config.nominal_reference_mv / 4095
print(f"ADC0 raw={raw} ~{voltage_mv:.1f} mV")
GPIO Event Subscriptions
GPIO push events are exposed through subscribe_gpio_events() and
gpio_subscribe().
import pyco_de_gallo as gallo
pg = gallo.open()
sub = pg.subscribe_gpio_events(depth=16)
pg.gpio_subscribe(0, gallo.GpioEdge.Any)
event = sub.poll(timeout=1.0)
if event is not None:
print(event.pin, event.edge, event.state)
pg.gpio_unsubscribe(0)
sub.close()
The subscription object also supports iteration and context-manager cleanup.
Error Handling
Rust-side errors are converted to RuntimeError.
import pyco_de_gallo as gallo
pg = gallo.open()
try:
pg.uart_set_config(0)
except RuntimeError as exc:
print(f"operation failed: {exc}")
That includes transport failures, schema-validation failures, and peripheral errors reported by the firmware.
When to Use Python
Reach for pyco-de-gallo when you want quick experiments, production-test glue,
lab automation, or notebook-style investigation without writing a Rust binary.
If you outgrow the synchronous Python surface, the next layer down is
pico-de-gallo-lib, which exposes the full async Rust API.