Command line#
pyxel-config # the wordmark, and what it can be asked to do
pyxel-config validate my.yaml # check it against the bundled schema
pyxel-config validate my.yaml --deep # and hand it to pyxel.load() too
pyxel-config validate my.yaml --format sarif # or json / codequality, for CI
pyxel-config explain my.yaml # the same checks, with a summary and a next step each
pyxel-config fix my.yaml -w # repair mistyped and misplaced model names
pyxel-config models charge_generation
pyxel-config params load_image
pyxel-config search "dark current"
pyxel-config doctor # what is installed, bundled, and missing
pyxel-config mcp runs the same server as pyxel-config-mcp, so an assistant
and a person are reaching identical code.
The exit code is the verdict, which is what makes this usable in CI without parsing any output:
Code |
Meaning |
|---|---|
|
valid, or nothing to complain about |
|
the config has errors, including text that is not valid YAML or not saved as UTF-8 |
|
the command line itself was wrong |
|
could not run: a file that cannot be opened, YAML that |
|
no such tutorial, model or stage, or a search with no hits |
A config that cannot be read (a syntax error, a key written twice, a file
saved as UTF-16) is a finding in the report like any other, so one broken file
never hides the verdict on the rest. Pyxel reads a config file as UTF-8 only,
so a file Windows PowerShell 5.1 wrote with > (UTF-16) is an error; its
content is still checked.
# .gitlab-ci.yml
validate-configs:
image: python:3.12-slim
script:
- pip install --quiet pyxel-config-core
# The same verdict as a merge-request widget. The report is written first,
# and `|| true` keeps a failing verdict from stopping the job before the
# next line, so the report exists whatever the configs hold.
- pyxel-config validate configs/*.yaml --format codequality > pyxel-config.json || true
- pyxel-config validate configs/*.yaml
artifacts:
# Uploaded when the job fails too, which is when the widget matters.
when: always
reports:
codequality: pyxel-config.json
Machine-readable reports#
validate and explain take --format {text,json,sarif,codequality}; --json
is the short spelling of --format json.
json: one entry per config, always a list, withvalid,diagnostics,pyxel_version,deep_checkedand (forexplain)summary.sarif: a SARIF 2.1.0 log with one rule per diagnostic category (pyxel-config/schema,/func,/shape,/advisory,/pyxel-rule,/pyxel-load,/yaml), one result per diagnostic, and the line of the offending key found by walking the YAML. GitHub code scanning, Azure DevOps and most SAST dashboards read it.codequality: the GitLab Code Quality list, with a stablefingerprintper (path, message, file) so a finding is reported once and cleared when it goes away. Root-shape problems,pyxel.load()failures and text that cannot be read as a config areblocker; a bad field ismajor; a warning isminor.
Both reports name each file relative to the working directory, with forward
slashes on every system (SARIF percent-encodes it, and gives a file outside
the working directory as a file: URI), so run the check from the
repository root and the report points at the files the merge request shows.
The deep check#
The schema is a description of Pyxel; Pyxel is the thing. --deep (or
validate_config(config, deep=True)) writes the config to a temporary file and
hands it to pyxel.load(), so anything the loader rejects is reported too: a
readout with times: [2.0, 1.0] satisfies the schema and the run rules, and
only Pyxel knows its times must be strictly increasing. The result names the
installed Pyxel whose loader ran (deep_pyxel_version, and “and loads in Pyxel
3.0.2” in the summary), which can differ from the release the bundled schema
came from. A warning Pyxel raises while loading is reported as a warning
finding; its own deprecation notices, which Python hides from a user too, are
left out.
Loading runs part of the config. Pyxel evaluates a readout’s times and an
observation’s values as Python when they are written as text, evaluates an APD
converter’s function (and unpickles one written as binary data), and imports
the module each model’s func names. It also opens a readout’s
times_from_file, a custom observation’s from_file and an APD converter’s
filename, which can be URLs. So before it loads anything, the deep check reads
every such value and refuses to run the config unless each is plain data or a
known harmless call: a list of numbers,
numpy.linspace/arange/logspace/geomspace or range() with plain numbers
and at most a million values, a one-argument lambda of arithmetic and math
functions, and model functions under pyxel.. Binary data (YAML !!binary)
and files to open are always refused. A refusal is a warning that names the
value and why.
For a config you wrote yourself, --trust-config (or trusted=True) skips that
reading. Never use it on a config from someone else, such as a merge request
from a fork.
On the MCP server the deep check is off: an assistant can pass in a config from
an issue, an email or a web page. Whoever starts the server can allow it with
pyxel-config mcp --allow-deep, and the refusal rules above still apply.
When the loader cannot run (Pyxel not installed, or installed but failing to
import, which the warning names), deep_checked stays false and the command
line exits with 3, “could not run”, rather than 0. A config the schema already
rejects still exits with 1.
As a pre-commit hook#
The repository exposes a pyxel-config-validate hook, so staged YAML configs
are checked before the commit lands. By default it checks every YAML file
except the CI and tooling files that are never Pyxel configs (.gitlab-ci.yml,
GitHub workflows, pre-commit’s own files, docker-compose, mkdocs, conda
environments, Read the Docs, codecov, yamllint); narrowing files to where the
configs live is still the surest choice:
# .pre-commit-config.yaml
repos:
- repo: https://gitlab.com/dobybaxter127/pyxel-config-lab
rev: core-v0.6.1
hooks:
- id: pyxel-config-validate
files: ^configs/.*\.ya?ml$ # narrow it to where the configs live
stdout carries the answer, stderr carries the furniture. The banner and any progress note go to stderr, so a pipe stays clean with no flag at all:
pyxel-config models --json | jq -r '.models[].func'
pyxel-config fix my.yaml -o - > repaired.yaml # config out, report on stderr
Colour follows ESA: institutional blue and white carry the frame and the labels,
and red and amber are kept for the things that are actually wrong. Palette
entries are named for the job they do, not the hue, so a repaint never reaches
a call site. It steps down on its own: truecolor to 256 to 16 to none,
Unicode glyphs to ASCII when the output encoding cannot carry them, and a
wordmark that shrinks through two smaller lockups rather than wrapping. --json,
--no-color, --ascii, --banner and -q override any of that;
NO_COLOR is honoured.