Scientific test & measurement toolbox
Project description
PyTestLab
Modern, async-first Python toolbox for laboratory
test-and-measurement automation, data management and analysis.
✨ Key Features
- Async by design – non-blocking instrument I/O with
async/await. - Unified driver layer – consistent high-level API across oscilloscopes, PSUs, DMMs, VNAs, AWGs, spectrum & power meters, DC loads, …
(see
pytestlab.instruments.*). - Plug-and-play profiles – YAML descriptors validated by Pydantic & JSON-schema.
Browse ready-made Keysight profiles in
pytestlab/profiles/keysight. - Simulation mode – develop anywhere using the built-in
SimBackend(no hardware required, deterministic outputs for CI). - Record & Replay – record real instrument sessions and replay them exactly for reproducible measurements, offline analysis, and regression testing with strict sequence validation.
- Bench descriptors – group multiple instruments in one
bench.yaml, define safety limits, automation hooks, traceability and measurement plans. - High-level measurement builder – notebook-friendly
MeasurementSessionfor parameter sweeps that stores data as Polars DataFrames and exports straight to the experiment database. - Rich database – compressed storage of experiments & measurements with full-text search (
MeasurementDatabase). - Powerful CLI –
pytestlab …commands to list/validate profiles, query instruments, convert benches to simulation, replay sessions, etc. - Extensible back-ends – VISA, Lamb server, pure simulation; drop-in new transports via the
AsyncInstrumentIOprotocol. - Docs & examples – Jupyter tutorials, MkDocs site, and 40+ ready-to-run scripts in
examples/.
🚀 Quick Start
1. Install
pip install pytestlab # core
pip install pytestlab[full] # + plotting, uncertainties, etc.
Need VISA? Install NI-VISA or Keysight IO Libraries, then
pip install pyvisa.
2. Hello Oscilloscope (simulated)
import asyncio
from pytestlab.instruments import AutoInstrument
async def main():
scope = AutoInstrument.from_config("keysight/DSOX1204G", simulate=True)
await scope.connect_backend()
# simple façade usage
await scope.channel(1).setup(scale=0.5).enable()
await scope.trigger.setup_edge(source="CH1", level=0.2)
trace = await scope.read_channels(1) # Polars DataFrame
print(trace.values.head())
await scope.close()
asyncio.run(main())
3. Build a Bench
# bench.yaml (excerpt)
bench_name: "Power-Amp Characterisation"
simulate: false # set to true for dry-runs / CI
instruments:
psu:
profile: "keysight/EDU36311A"
address: "TCPIP0::172.22.1.5::inst0::INSTR"
safety_limits:
channels:
1: {voltage: {max: 6.0}, current: {max: 3}}
dmm:
profile: "keysight/34470A"
address: "USB0::0x0957::0x1B07::MY56430012::INSTR"
import asyncio, pytestlab
async def run():
async with await pytestlab.Bench.open("bench.yaml") as bench:
v = await bench.dmm.measure_voltage_dc()
print("Measured:", v.values, v.units)
asyncio.run(run())
4. Record & Replay Sessions
Record real instrument interactions and replay them exactly:
# Record a measurement session
pytestlab replay record my_measurement.py --bench bench.yaml --output session.yaml
# Replay the recorded session
pytestlab replay run my_measurement.py --session session.yaml
Perfect for reproducible measurements, offline analysis, and catching script changes!
🔄 Record & Replay Mode
PyTestLab's Record & Replay system enables you to capture real instrument interactions and replay them with exact sequence validation. This powerful feature supports reproducible measurements, offline development, and regression testing.
Core Benefits
- 🎯 Reproducible Measurements – Exact same SCPI command sequences every time
- 🛡️ Measurement Integrity – Scripts cannot deviate from validated sequences
- 🔬 Offline Analysis – Run complex measurements without real hardware
- 🧪 Regression Testing – Catch unintended script modifications immediately
How It Works
-
Recording Phase: The
SessionRecordingBackendwraps your real instrument backends and logs all commands, responses, and timestamps to a YAML session file. -
Replay Phase: The
ReplayBackendloads the session and validates that your script executes the exact same command sequence. Any deviation triggers aReplayMismatchError.
Usage Examples
Basic Recording & Replay
# Record a measurement with real instruments
pytestlab replay record voltage_sweep.py --bench lab_bench.yaml --output sweep_session.yaml
# Replay the exact sequence (simulated)
pytestlab replay run voltage_sweep.py --session sweep_session.yaml
Programmatic Usage
import asyncio
from pytestlab.instruments import AutoInstrument
from pytestlab.instruments.backends import ReplayBackend
async def main():
# Load a recorded session
replay_backend = ReplayBackend("recorded_session.yaml")
# Create instrument with replay backend
psu = AutoInstrument.from_config(
"keysight/EDU36311A",
backend_override=replay_backend
)
await psu.connect_backend()
# This will replay the exact recorded sequence
await psu.set_voltage(1, 5.0)
voltage = await psu.read_voltage(1)
await psu.close()
asyncio.run(main())
Session File Format
psu:
profile: keysight/EDU36311A
log:
- type: query
command: '*IDN?'
response: 'Keysight Technologies,EDU36311A,CN61130056,K-01.08.03-01.00-01.08-02.00'
timestamp: 0.029241038020700216
- type: write
command: 'VOLT 5.0, (@1)'
timestamp: 0.8096857140189968
- type: query
command: 'MEAS:VOLT? (@1)'
response: '+4.99918100E+00'
timestamp: 1.614894539990928
Error Detection
If your script deviates from the recorded sequence:
# During recording: set_voltage(1, 5.0)
# During replay: set_voltage(1, 3.0) # ← Different value!
# Raises: ReplayMismatchError: Expected 'VOLT 5.0, (@1)' but got 'VOLT 3.0, (@1)'
Advanced Features
- Multi-instrument sessions – Record PSU, oscilloscope, DMM interactions simultaneously
- Timestamp preservation – Exact timing information for analysis
- Automatic error checking – Captures instrument
:SYSTem:ERRor?queries - CLI integration – Full command-line workflow support
- Backend flexibility – Works with VISA, LAMB, and custom backends
See examples/replay_mode/ for complete working examples and tutorials.
📚 Documentation
| Section | Link |
|---|---|
| Installation | docs/installation.md |
| 10-minute tour (Jupyter) | docs/tutorials/10_minute_tour.ipynb |
| User Guide | docs/user_guide/* |
| Async vs. Sync | docs/user_guide/async_vs_sync.md |
| Bench descriptors | docs/user_guide/bench_descriptors.md |
| API reference | docs/api/* |
| Instrument profile gallery | docs/profiles/gallery.md |
| Tutorials | |
| Compliance and Audit | docs/tutorials/compliance.ipynb |
| Custom Validations | docs/tutorials/custom_validations.ipynb |
| Profile Creation | docs/tutorials/profile_creation.ipynb |
HTML docs hosted at https://pytestlab.readthedocs.io (builds from docs/).
🧑💻 Contributing
Pull requests are welcome! See CONTRIBUTING.md and the Code of Conduct.
Run the test-suite (pytest), type-check (mypy), lint/format (ruff), and keep commits conventional (cz c).
🗜️ License
MIT © 2022–2025 Emmanuel Olowe & contributors.
Commercial support / custom drivers? Open an issue or contact support@pytestlab.org.
Built with ❤️ by scientists, for scientists.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytestlab-0.2.1.tar.gz.
File metadata
- Download URL: pytestlab-0.2.1.tar.gz
- Upload date:
- Size: 13.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1ff62dd80a936fd4d2836d544d915d2d2e96b62500cd8a24cfb74c491029ace
|
|
| MD5 |
a607824350aec46f92d48ad3db7e3385
|
|
| BLAKE2b-256 |
29cafaef8a6201cf6dd0d883e1d069fdcf646af962c60e56072af0bc334dae7b
|
Provenance
The following attestation bundles were made for pytestlab-0.2.1.tar.gz:
Publisher:
publish_release.yml on labiium/pytestlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytestlab-0.2.1.tar.gz -
Subject digest:
a1ff62dd80a936fd4d2836d544d915d2d2e96b62500cd8a24cfb74c491029ace - Sigstore transparency entry: 357947739
- Sigstore integration time:
-
Permalink:
labiium/pytestlab@d7f8abcfe3aa1cca786a604070fb0b825977e2b9 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/labiium
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish_release.yml@d7f8abcfe3aa1cca786a604070fb0b825977e2b9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytestlab-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pytestlab-0.2.1-py3-none-any.whl
- Upload date:
- Size: 189.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c374e9407ba2bac67889bf0577f3642c083daa8824643ab61b5d1ed3f0a0d77
|
|
| MD5 |
21e15b749fad3b26423882a6ccfd117f
|
|
| BLAKE2b-256 |
ba8231c53eae5c37922daa21511febf08c8e289165a7d254dcada4a4f98d73ec
|
Provenance
The following attestation bundles were made for pytestlab-0.2.1-py3-none-any.whl:
Publisher:
publish_release.yml on labiium/pytestlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytestlab-0.2.1-py3-none-any.whl -
Subject digest:
5c374e9407ba2bac67889bf0577f3642c083daa8824643ab61b5d1ed3f0a0d77 - Sigstore transparency entry: 357947758
- Sigstore integration time:
-
Permalink:
labiium/pytestlab@d7f8abcfe3aa1cca786a604070fb0b825977e2b9 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/labiium
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish_release.yml@d7f8abcfe3aa1cca786a604070fb0b825977e2b9 -
Trigger Event:
push
-
Statement type: