microspec
A micro specification parser and data validator for TCP protocols. The protocol can be defined either inside the code or as a standalone JSON which can be loaded via Protocol.from_dict(...). Then, all the data payloads for all the defined endpoints are simply validated via protocol.validate_endpoint(endpoint, payload) -> ValidationError | None.
Usage:
- Via pip:
pip install microspec - From source code:
git clone https://gitlab.com/meehai/microspec # clone the source code
cd microspec # go in the cloned directory
python -m venv .venv && source .venv/bin/activate # make a virtual env, optional but useful
python -m pip install -e . # install microspec in this virtual env
python -m pytest test/ # run the unit & integration tests to verify installation
python microspec/microspec.py test/integration/protocol.json # smoke run: parse + validate the bundled spec
Docs: meehai.gitlab.io/microspec — built by
docs/build_docs.sh (pdoc; no sphinx/config). Build locally with
bash docs/build_docs.sh and open the printed file:// link.
Usage
Protocol:
{
"move": {
"input": {"control_input": {"dtype": "float32", "shape": [6], "range": [-100, 100]}},
"output": {"status": {"dtype": "str_enum", "enum": ["move_applied"]}, "new_state": {"dtype": "dict"}}
}
}
import json
from microspec import Protocol
# Can also be defined here manually via the `Endpoint`, `Field` classes and `Dtype` enum from the library.
protocol = Protocol.from_dict(json.load(open("test/integration/protocol.json")), n_max_robots=10)
err = protocol.validate_endpoint("move", {"control_input": [5, 5, 5, 3, 3, 3]})
if err is not None: # err is of type ValidationError (has .error, .endpoint, .field for context)
raise ValueError(f"payload is not valid: {err.endpoint}: {err.error}")
Spec format
Each command is an input / output map of name -> field. Errors are not per-command: every
endpoint shares one error shape, declared once as Protocol's error_field (default Field("error",
Dtype.STR)).
{
"move": {
"input": {"control_input": {"dtype": "float32", "shape": [6], "range": [-100, 100]}},
"output": {"status": {"dtype": "str_enum", "enum": ["move_applied"]}, "new_state": {"dtype": "dict"}}
},
"robot_get_state": {
"input": {"robot_ix": {"dtype": "int32", "range": [0, "${n_max_robots}"]}},
"output": {"robot": {"dtype": "dict"}}
}
}
Field schema
| key | applies to | meaning |
|---|---|---|
dtype |
required | str int32 float32 bool dict bytes str_enum int_enum |
shape |
array dtypes | e.g. [6]; null = free first axis ([null, 6]) |
range |
int32 / float32 |
[min, max], inclusive (NaN always rejected; ±Inf only if outside the range) |
enum |
str_enum / int_enum |
non-empty list of allowed values (required for enum dtypes) |
min_len |
arrays with free axis | minimum length of the null axis |
fields |
dict |
optional nested name -> field map; omit for an opaque dict |
Notes: a shape key means "array" (numpy, exact dtype — np.float32, not int64); scalars have no
shape. A dict with fields is validated recursively (keys must match exactly, nested arrays are
list→ndarray converted); without fields it is opaque (any dict passes). ${var} is a single bare
variable filled at parse time (e.g. n_max_robots). The spec file is plain JSON.
Public API
Protocol, Endpoint, Field, Dtype, ValidationError. Everything else is internal.