Changelog#
All notable changes to pyxel-config-core are recorded here. The format follows Keep a Changelog, and the project uses Semantic Versioning.
Releases up to 0.5.0 were published to PyPI before the repository tagged its
releases, so they link to PyPI. From 0.6.0 on, each release is published from a
core-v tag.
Unreleased#
0.6.1 - 2026-10-14#
Added#
A documentation site at https://pyxel-config-lab-ede25c.gitlab.io/docs/: installation, a quickstart, guides to the command line, the library, the MCP server and semantic search, how validation works, and an API reference read from the docstrings. Its guide pages include sections of the README, so the two cannot say different things, and the quickstart’s printed output is a test (
tests/test_docs_quickstart.py).NumPy-style docstrings on every public function and result type.
A
docsextra with what builds the site.A pipeline step without
funcis refused: Pyxel builds every step from it, a disabled one included, although its schema leaves it optional.A focal-plane tile with more than one detector block is refused, as Pyxel’s tile loader refuses it, and only Pyxel’s four detector keys count as a detector block.
model_parameters(model, stage=None),pyxel-config paramsand theget_pyxel_model_parametersMCP tool cover every model the bundled schema accepts (glow, the CMOS noise models,load_detectorandsave_detectoramong the 19 that had nothing). Each argument carries its type,required,has_default, Pyxel’s owndefaultand theexamplevalue Pyxel’s example configs use.stagepicks between the two stages a name such assimple_collectionexists in.
Changed#
The package’s Documentation link points at the documentation site instead of the README.
Text that cannot be read as a config (a YAML syntax error, a key written twice, an alias bomb, nesting hundreds of levels deep, text that is not UTF-8) is a finding in the
validateandexplainreports, with exit code 1, rather than stopping the run with exit code 3. One such file no longer leaves a SARIF or Code Quality report empty. A file that cannot be opened is listed in the report too, and the run still exits with 3.A file saved as UTF-16 or UTF-32 (as Windows PowerShell 5.1’s
>writes it) is an error, since Pyxel reads a config file as UTF-8 only. Its content is still checked.ParameterInfo.defaultis Pyxel’s own default rather than the catalogue’s example value, which is nowParameterInfo.example. A step that leaves an argument out gets the default; writing the example overrides it (forsimple_conversion’squantum_efficiency, the detector’s own value). Types are spelled as Python spells them:float,list[int] | null.A number written as text, such as
1e3, is reported only where text is not what the field takes: a step’snameor an output folder is left alone, while a number field, or one that takes a number or a file name (charge_to_volt’svalue), still is.fixrefuses-wand-o FILEtogether (exit code 2) instead of quietly repairing in place. With-oor to stdout, a config with nothing to repair is written exactly as it was, comments included.
Fixed#
.nanand.infin a detector field Pyxel bounds (temperature, quantum efficiency, full well capacity, the sizes) are refused, as Pyxel’s own range checks refuse them.A top-level key that is not text (
yes:,1:,~:) no longer crashesvalidate_config,explain_configorautofix.SARIF and Code Quality reports read each file once: a report with 3,000 findings took over four minutes and now takes well under a second. A finding inside a mapping merged with
<<: *anchorlands on its own line, and a file outside the working directory is afile:URI in SARIF.The deep check hands Pyxel the file’s text exactly as written, comments included, and the deep check and
fixwrite files as bytes: a config saved with Windows line endings no longer arrives with every line break doubled.A semantic search tried before the index is built no longer makes the index look built to
doctorand to every later search.The semantic search guide installs
"pyxel-config-core[rag]"from PyPI rather than an editable install that only works in a clone, and the README says which engine each tutorial search uses.
Security#
The deep check’s safety gate refuses binary data (YAML
!!binary) anywhere in a config: Pyxel unpickles an APD converter function given that way, which can run any code. Without trust it also refuses the files Pyxel opens while it loads (times_from_file, a custom observation’sfrom_file, an APD converter’sfilename, URLs included) and a value range of more than a million values.--trust-config(ortrusted=True) lifts all three for a config you wrote yourself; on the MCP server they apply whenever it was started with--allow-deep.
0.6.0 - 2026-10-03#
Added#
validate_config(config, deep=True)hands the YAML topyxel.load()when Pyxel is installed and reports anything the loader rejects, which catches what the JSON schema cannot express (readout times out of order, for example).ValidationResult.deep_checkedsays whether the loader actually ran. The same switch is--deepon thevalidateandexplaincommands anddeep=trueon thevalidate_pyxel_configandexplain_pyxel_configMCP tools.Loading runs part of a config: Pyxel evaluates text such as a readout’s
timesas Python and imports each model’s module. Before it loads, the deep check reads every such value and refuses to run the config unless each is plain data or a known harmless call (pyxel_config_core/expressions.py).--trust-config(ortrusted=True) skips that reading for a config you wrote yourself. On the MCP server the deep check is off unless the server is started withpyxel-config mcp --allow-deep.The deep check names the Pyxel that judged the config:
ValidationResult.deep_pyxel_versionandExplanation.deep_pyxel_versioncarry the installed release whose loader ran, the summary says “and loads in Pyxel 3.0.2”, and the CLI’s report and JSON say the same. Warnings Pyxel raises while loading (those Python shows a user, not its own deprecation notices) are reported as warning findings.--deepthat cannot run (Pyxel missing, or installed but failing to import, with the real import error named) exits with 3, “could not run”, instead of passing with 0. Any unexpected error also exits with 3, never with 1, which means “the config has errors”.explain_configreturns anExplanation:valid, a one-sentencesummary(“This CCD exposure config has 2 problems in the charge_generation stage”), and the diagnostics, each carrying a concretenext_step(the model name to rename to, the accepted types, the section to add, the release that renamed a field).pyxel_config_core/config_shape.py, the Python port of the browser app’sconfigShape.js: it names the running mode and detector a config uses, reports a missing or duplicated one, and offers a did-you-mean for a misspelled top-level key (ccd_detecor,exposre). The root-level anyOf chorus (“‘ccd_detector’ is a required property”, four times) is gone.--format {text,json,sarif,codequality}onvalidateandexplain.--jsonremains as the short spelling of--format json. SARIF 2.1.0 and GitLab Code Quality reports carry the line of the offending key, found by walking the YAML.pyxel_config_core/data/BUNDLE.json, a manifest naming the Pyxel release the bundled schema was generated from and the schema’s SHA-256.schema_version()exposes it,ValidationResult.pyxel_versionandExplanation.pyxel_versioncarry it on every verdict, anddoctorprints it alongside whether the shipped schema still matches the manifest.Diagnostic.category(schema,func,shape,advisory,pyxel-load), which is the SARIF rule id and the Code Quality check name.search_pyxel_tutorialstakes an explicitsemantic: bool = False; it was documented as keyword search but silently used the vector index when one existed.py.typed,[tool.ruff]and[tool.mypy]configuration, adevextra, and Python 3.13 in the classifiers.A
.pre-commit-hooks.yamlat the repo root exposingpyxel-config-validate.scripts/refresh_bundle.pyat the repo root: the one reproducible command for upgrading every bundled artifact to a new Pyxel release, with--checkfor CI.Run rules (
run_rules.py): what Pyxel 3.0.2 refuses at load or stops on at run time although its schema accepts it, reported as errors in the newpyxel-rulecategory. A readout with bothtimesandtimes_from_file, a calibration with bothweightsandweights_from_file, an observation with no parameters,simple_measurementwith neither againnor a detectorcharge_to_volt,simple_amplifierwithoutpre_amplification, andsimple_adcwithout its ADC settings. The browser applies the same rules, and CI runs a fixture for each through the real Pyxel.A run rule for whole numbers written as decimals where Pyxel refuses one: a detector’s
roworcoland a CCD’simage_roworoverscan_rowstoppyxel.load()when written128.0, and an exposure’spipeline_seedstops the run. Fields Pyxel takes a decimal in (adc_bit_resolution, a wavelength’sresolution) are left alone.A run rule for a model whose own code refuses the config’s detector class: 19 of Pyxel 3.0.2’s models check the class and raise a TypeError on any other (
cdmon a CMOS detector,apd_gainon a CCD). Each was found by calling the model on every class, and CI repeats that against the installed Pyxel. Models Pyxel’s metadata restricts but whose code runs on any detector are left alone.Focal plane arrays, which the Config Lab exports and the bundled schema has no branch for, are validated instead of refused as a config missing its detector and pipeline (
pyxel_config_core/fpa.py). The plane is checked against Pyxel’s focal-plane loader as the browser checks it (one running mode, exposure only; a physical geometry with one scale above 0; tile IDs 1..N; positions as mappings with an angle of 0, 90, 180 or 270; a plane-wide scene; nothing besidefocal_plane_array), and each tile is validated against the schema and the run rules as the flat config Pyxel builds it from. Every finding names its tile and its path in the plane; a finding in the plane’s running mode is said once.fixrepairs model names inside tiles,explainsays which tiles hold the problems, and the deep check reads every tile and the plane-wide scene before Pyxel loads the plane. The pre-commit hook and the MCP tools follow.
Changed#
Schema errors are grouped by path. The anyOf alternatives of a single field collapse into one line naming every accepted type (“‘x’ is not one of the accepted types: number, object, null”), at most eight paths are listed, and a closing diagnostic counts what was left out. Previously the first five unique (path, message) pairs were shown and the rest silently dropped.
A pipeline step whose
funcis known is validated against that model’s own definition, so a mistyped argument reports as itself instead of alongside the required arguments of every other model in the stage.The stale-detector heuristic in the advisory warnings now looks for any
*_detectorkey rather than requiring ageometryblock under it.searchexits 4 on zero hits with--jsontoo; it was 0.rag.semantic_searchreuses one embedder and one Qdrant client per index directory instead of building both on every call.The bundled
tests/galaxy_config.yamlfixture is valid under Pyxel 3.0.2 (charge_to_voltinstead of the 2.xcharge_to_volt_conversion).
Deprecated#
The
pyxel-config-indexconsole script. It prints a notice and forwards topyxel-config index, which is the same operation.
Fixed#
A model of the user’s own (a
funcoutsidepyxel., which Pyxel’s schema accepts through its generic ModelFunction branch) is no longer reported as unknown, andfixno longer rewrites it into the built-in it resembles. A typo underpyxel.is still caught.pyxel-config fix --json -wandfix --json -o FILEnever wrote the repaired config; the JSON branch returned before the write.autofixon a misspelled stage key moved the steps to the owning stage but left the empty misspelled key behind, so the “fixed” config still failed with “Additional properties are not allowed”.Text that cannot be read as a config (a YAML syntax error, or a document that is not a mapping) made
validate_config,explain_configandautofixraise, so an MCP client got a bare tool failure with no reason. Each now returns a finding in the newyamlcategory that says what is wrong and on which line.A few hundred bytes of nested YAML aliases load into a structure that walks as tens of millions of values, and validating it kept the CLI, the pre-commit hook or the MCP server busy for minutes. Text that expands past its own length (or 100,000 values, whichever is larger) is now refused before anything walks it (
pyxel_config_core/yaml_read.py); no config without aliases can reach that.A reader that closed the pipe early (
pyxel-config validate x.yaml | head) turned a failing check into exit 0. The verdict’s exit code now stands.A config saved by Windows PowerShell 5.1’s
>orOut-File(UTF-16 with a byte order mark) crashed the command line with exit 1. Files and stdin are decoded by their byte order mark (UTF-8, UTF-16 or UTF-32), and other text that is not UTF-8 is refused with exit 3, naming the byte that failed.fix -wwrote back a config with nothing to repair, which dropped its comments and formatting. Such a config is now left exactly as it is.fix -o -andtutorial --rawwrote through the console’s code page, so on Windows a redirect turned characters outside it into question marks. Both write UTF-8.A key written twice in one mapping (a second
pipeline:block, say) was accepted, while Pyxel keeps only the last. The reader now refuses it and names both lines; keys a merge (<<) brings in may still be overridden.fixreported no remaining problems for any config without a rootpipeline, so it exited 0 on a config missing its pipeline. It now reports the full verdict after its repairs, whatever the config holds.SARIF and Code Quality reports named each file as typed on the command line, so a report written on Windows carried backslashes and pointed at nothing on a CI runner, and stdin was “-”. Files are now named relative to the working directory with forward slashes (a
file:URI in SARIF when outside it, percent-encoded in SARIF either way), and stdin is “stdin”.The
ragextra allowed qdrant-client 1.7, but semantic search callsquery_points(added in 1.10), so with 1.7 to 1.9 every search fell back to keywords with only “AttributeError” as the reason. The floor is now 1.10, an older client is named with the command that fixes it, and a degraded result carries the cause’s message.The pre-commit hook checked every YAML file by default, so a repository’s own
.gitlab-ci.yml(or a GitHub workflow) failed the commit. It now leaves the common CI and tooling YAML files alone.The sdist shipped the test suite without the three configs it reads, so its tests stopped at collection. A MANIFEST.in now adds them and the conftest; the cross-language tests skip outside the repository.
The bundled schema and tutorials reached the wheel only because setuptools guessed at their folders, with a warning that a later release may stop guessing. Both folders are now declared packages, and a test holds every file in the package to the declared list.
The README’s GitLab CI recipe ran the plain check first, so a failing verdict stopped the job before the Code Quality report was written. The report now comes first and is uploaded with
when: always.
0.5.0 - 2026-09-19#
Added#
The
pyxel-configcommand line:validate,explain,fix,models,params,tutorial,search,index,mcp,doctorandbanner, with the exit code as the verdict and stdout kept clean for pipes.