Skip to content

fix(configtree): upgrade YAML parser to 1.2 to prevent implicit type coercions - #549

Open
smrutisenapati wants to merge 4 commits into
develfrom
fix/configtree-yaml-1-2
Open

fix(configtree): upgrade YAML parser to 1.2 to prevent implicit type coercions#549
smrutisenapati wants to merge 4 commits into
develfrom
fix/configtree-yaml-1-2

Conversation

@smrutisenapati

Copy link
Copy Markdown
Contributor

Summary

  • Replace PyYAML (YAML 1.1) with ruamel.yaml (YAML 1.2) across the entire configtree import/export pipeline
  • YAML 1.1 silently coerced yes/on/no/offbool and 0777 → octal 511 before values ever reached the DB or etcd; YAML 1.2 preserves these as strings/decimal integers
  • config_ui.py already used ruamel.yaml — this aligns the CLI with that decision

Changes

File What changed
pyproject.toml / uv.lock Added ruamel-yaml>=0.17.0 dependency
riocli/configtree/import_keys.py _load_yaml_file() helper uses YAML(typ="safe") instead of benedict(f, format="yaml") for YAML inputs; JSON path unchanged
riocli/configtree/util.py combine_metadata() _yaml_reader.load() replaces yaml.safe_load() — stored value "yes" now exports as "yes" instead of "true"
riocli/configtree/util.py export_to_files() ruamel.yaml writer replaces benedict.to_yaml(); _to_plain() helper normalises benedict/CommentedMap subclasses before dumping (ruamel's representer requires exact dict/list types)
tests/unit/configtree/test_util.py Added test_yaml11_booleans_are_preserved_as_strings and test_octal_0777_is_decimal_777 — assertions built from observed ruamel.yaml output, not the issue's table

Test plan

  • All 150 unit tests pass (uv run pytest tests/unit/ -v)
  • New tests explicitly assert yes/no/on/offstr and 0777int 777
  • Existing test_non_yaml_value_is_kept_as_raw_string confirms logging format strings still survive parse errors gracefully

Closes rapyuta-robotics/rapyuta_io#2045

🤖 Generated with Claude Code

…coercions

Replace PyYAML (YAML 1.1) with ruamel.yaml (YAML 1.2) in the configtree
import/export pipeline. YAML 1.1 silently coerces yes/no/on/off → bool and
0777 → octal 511 before values reach the DB or etcd; YAML 1.2 preserves
these as strings/decimal integers.

Fixes:
- import_keys.py: _load_yaml_file() uses YAML(typ="safe") instead of
  benedict(f, format="yaml") for YAML inputs (JSON path unchanged)
- util.py combine_metadata(): _yaml_reader.load() replaces yaml.safe_load()
  so stored values like "yes" export as "yes", not "true"
- util.py export_to_files(): ruamel.yaml writer replaces benedict.to_yaml()
  for YAML output; _to_plain() normalises benedict/CommentedMap subclasses
  before dumping since ruamel's representer requires exact dict/list types

Closes rapyuta-robotics/rapyuta_io#2045

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 19, 2026 07:55
@smrutisenapati
smrutisenapati requested review from a team as code owners June 19, 2026 07:55
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

🤖 Pull Request Artifacts (#27817697081) 🎉

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the configtree YAML import/export path from YAML 1.1 parsing behavior to YAML 1.2 by replacing PyYAML/benedict YAML parsing with ruamel.yaml, preventing implicit coercions like yes/no/on/off → bool and 0777 → octal.

Changes:

  • Added ruamel-yaml as a dependency (and locked it in uv.lock).
  • Updated configtree YAML read/write paths to use ruamel.yaml for parsing/dumping while keeping JSON behavior unchanged.
  • Added unit tests asserting YAML 1.1 boolean-like scalars remain strings and 0777 parses as decimal 777.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyproject.toml Adds ruamel-yaml>=0.17.0 runtime dependency.
uv.lock Locks ruamel-yaml (0.19.1) into the resolved environment.
riocli/configtree/import_keys.py Switches YAML file loading to ruamel.yaml safe loader for import + overrides.
riocli/configtree/util.py Switches YAML parsing/dumping in combine/export helpers to ruamel.yaml.
tests/unit/configtree/test_util.py Adds regression tests for YAML 1.1 implicit coercion differences.

Comment thread riocli/configtree/util.py Outdated
Comment thread riocli/configtree/util.py Outdated

import yaml
from benedict import benedict
from ruamel.yaml import YAML
Comment on lines +327 to +331
if file_format == "json":
loaded = benedict(f, format="json")
else:
loaded = benedict(_load_yaml_file(f))
override.merge(loaded.unflatten(separator="/"))
smrutisenapati and others added 3 commits June 19, 2026 17:14
Add parse_configtree_value() to util.py and use it in put_key_in_revision
so that the put path matches the import path's type detection:

  - '{a: 1}' → {"a": 1}  (normalised to valid JSON, not raw YAML shorthand)
  - '[1,2]'  → [1, 2]    (spacing normalised via json.dumps)
  - 'true'   → bool True → "true"  (same as import)
  - 'yes'    → str "yes"           (YAML 1.2: not coerced to bool)
  - '300'    → int 300  → "300"    (same as import)

For simple scalars (int/float/bool/null/plain string) the stored bytes are
identical to before. The visible fix is for YAML shorthand dicts/lists and
spacing normalisation, where raw string passthrough produced non-JSON output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sort third-party imports in util.py and import_keys.py (ruff I001)
- Import YAMLError explicitly; narrow except clauses from Exception to
  YAMLError in combine_metadata() and parse_configtree_value()
- Fix --override Click option default from None to () so iterating over
  it never raises TypeError when the flag is omitted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants