plesty-sdk · Gate d1 · June 2026

Device API Pipeline

The automated mock-hardware test suite that every PLESTY device module must pass before reaching CI.

plesty-sdk [Device] tier
What is Gate d1?

Gate d1 is a new device-tier validation gate in plesty check. It is automatically activated when your module declares module_type = "device" in [tool.plesty] of pyproject.toml. For all other module types the gate is silently skipped.

When active, Gate d1 collects all test node IDs in your tests/ directory and verifies that all eight standard mock-gate functions are present and pass — without any real hardware connected. It uses pytest under the hood with a filter that runs only the pipeline functions, so your broader test suite is unaffected.

The goal is to guarantee that every device module in the PLESTY HUB exposes a consistent, contract-compliant API that experiment authors can depend on — regardless of the underlying vendor protocol.

The 8 Mock Gates
1
test_schema_integrity
Both schema_param.json and schema_func.json are present, parseable JSON, and structurally valid.
2
test_param_key_resolution
Every key returned by get_config_list() resolves successfully through get_config(key) on the mock device.
3
test_params_mock
All config parameters round-trip through the mock SCPI solver: set_config(key, val) followed by get_config(key) returns the same value.
4
test_funcs_mock
Every operation in schema_func.json is callable on the mock device and returns a dict response.
5
test_lifecycle
The device works as a context manager: connect → is_operatable == True → disconnect completes cleanly without raising.
6
test_identity
identity() returns a non-empty string (e.g. the IDN response), proving the device has a recognisable self-description.
7
test_check_errors
check_errors() returns an empty list [] on a healthy mock device — not None, not a raised exception.
8
test_state_coverage
dev.state is a dict whose keys are a superset of get_config_list(), ensuring the state snapshot captures all declared parameters.
Tutorial — Adding Gate d1 to your device module
1
Declare module_type = "device" in pyproject.toml
This single line activates Gate d1. Without it, plesty check will print Device API Pipeline (N/A — not a device module) and skip the gate entirely.
# pyproject.toml [tool.plesty] standard = "quantum" module_type = "device" # ← activates Gate d1
2
Create a VISA-free mock device class
Subclass your real Device and replace the transport with a MagicMock. Use a unique UUID-based address per instance to prevent resource-registry collisions between tests. The mock transport's send_command returns a safe default response.
tests/test_mydevice.py
from __future__ import annotations import uuid from typing import Any from unittest.mock import MagicMock from plesty.lib.solver.scpi import SCPISolver from plesty.lib.test.device_pipeline import DevicePipeline from plesty.mydevice.device import Device from plesty.mydevice.base_device import _MyDeviceOpSolver class _MockMyDevice(Device): """Device subclass replacing VISA with an in-memory mock transport.""" def __init__(self) -> None: super().__init__(address=f"USB::MOCK::{uuid.uuid4().hex[:8].upper()}::INSTR") self._mock_tm: MagicMock = MagicMock() self._mock_tm.is_open = True self._mock_tm.send_command.return_value = '0,"No error"' self._tm = self._mock_tm def init(self, main: Any = None) -> None: self._param_solver = SCPISolver(qfmt="{cmd}?", wfmt="{cmd} {value}") self.bind_op_solver(_MyDeviceOpSolver(self._mock_tm)) def connect(self) -> bool: if self._param_solver is None: self.init() self._mock_tm.is_open = True self._tm = self._mock_tm return True def disconnect(self) -> None: self._tm = None self._param_solver = None def identity(self) -> str: return "VENDOR,MODEL,SN00001,1.0.0"
3
Instantiate the pipeline
Create one PIPELINE constant at module level. Pass your mock device class and paths to both schema files. The paths can be relative to the project root or use importlib.resources.files() for package-internal assets.
# Directly below the mock class, still in tests/test_mydevice.py PIPELINE = DevicePipeline( _MockMyDevice, param_schema="plesty/mydevice/schema_param.json", op_schema="plesty/mydevice/schema_func.json", )
4
Write the 8 gate test functions
Each function is a one-liner that delegates to the pipeline. The function names must match exactly — Gate d1 checks for them by name. Add a docstring for Gate 7 (documentation gate) compliance.
def test_schema_integrity() -> None: """Gate 1: param and op schema files are well-formed JSON.""" PIPELINE.test_schema_integrity() def test_param_key_resolution() -> None: """Gate 2: every key in get_config_list() resolves via get_config().""" PIPELINE.test_param_key_resolution() def test_params_mock() -> None: """Gate 3: all config params round-trip through the mock solver.""" PIPELINE.test_params_mock() def test_funcs_mock() -> None: """Gate 4: all registered operations return a dict with the mock solver.""" PIPELINE.test_funcs_mock() def test_lifecycle() -> None: """Gate 5: context manager completes; is_operatable is True after connect.""" PIPELINE.test_lifecycle() def test_identity() -> None: """Gate 6: identity() returns a non-empty string.""" PIPELINE.test_identity() def test_check_errors() -> None: """Gate 7: check_errors() returns [] on a healthy mock device.""" PIPELINE.test_check_errors() def test_state_coverage() -> None: """Gate 8: dev.state keys are a superset of get_config_list().""" PIPELINE.test_state_coverage()
5
Run plesty check and verify Gate d1 passes
Run the full quantum check from your device module directory. Gate d1 appears after Gate 11 (Docs Build) in the output.
Terminal
uv run plesty check --standard quantum
Expected output
Metadata & Namespace Code Hygiene (lint) Code Hygiene (format) Code Hygiene (types) API Interface Matching Data Layer Compliance Documentation Completeness Dependency Coexistence Automated Test Coverage (≥80%) Semantic Versioning Licensing Compliance (static) Vulnerability Audit (no known CVEs) Docs Build Device API Pipeline (d1 — 8 mock gates pass) All checks passed.
What happens without Gate d1
⚠ Without it, API contracts are unenforceable

Before Gate d1, two device modules could both pass plesty check while exposing completely different APIs — one using connect(), another using open(); one returning dicts from operations, another returning raw strings. Experiment authors had no guarantee.

Gate d1 makes this a hard CI failure. If the 8 functions are missing or one throws, you see:

Error: [Device API] Missing required DevicePipeline gate test(s): - test_schema_integrity - test_param_key_resolution Add these test functions via plesty.lib.test.device_pipeline.DevicePipeline.

With Gate d1: every device module in the PLESTY HUB guarantees connect/disconnect, identity(), check_errors()[], schema round-trips, and state coverage before it ever reaches CI. Experiment authors can trust the contract.