Add JSON/XML config ingestion and rendering (#232)#279
Merged
Conversation
New hier_config/formats.py maps structured configs onto the standard
HConfig tree and back:
- HConfig.from_json() ingests JSON objects or text (e.g. OpenConfig),
with keyed lists identified via list_keys (default ("name", "id")).
- HConfig.from_xml() ingests XML documents (e.g. NETCONF payloads);
attributes map to @name leaves, text content to value leaves, and
repeated sibling elements are identified via list_keys.
- HConfig.to_json() / to_xml() invert the mappings, so structured
configs can be diffed, predicted with future(), and rendered back in
their source format. Round-trip fidelity is pinned by tests.
- The structured-format detection error now points at the new
constructors instead of only suggesting CLI conversion.
Known limits (documented): a single-item JSON scalar list renders back
as a bare scalar, and NETCONF edit-config operation attributes carry no
remediation semantics yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An empty JSON object collapsed to null on to_json() because a
single-word childless node was read as a valueless leaf. from_json only
produces such nodes for empty objects (scalar leaves always carry a
value word, including `key null`), so they now invert to {}. Pinned by
a round-trip test alongside tests for the DuplicateChildError behavior
on duplicate list items/identities; the module docstring now documents
the empty-list drop and duplicate-item caveats.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from four parallel cleanup reviews applied:
- Promote driver resolution to a public registry.resolve_driver();
constructors and formats share it instead of carrying private copies
of the same Platform|str|driver dispatch.
- Type the JSON recursion with a recursive JsonValue alias, removing
the annotated-assignment + pyright-ignore narrowing workarounds and
the parsed-shadow variable.
- Single source for the ("name", "id") default: the HConfig
classmethods take list_keys=None and formats resolves it against
DEFAULT_LIST_KEYS.
- Merge _xml_children_into into _xml_element_into (one recursion, tag
counting via collections.Counter) and classify children before
splitting in _node_to_xml_element, removing the double text split
per rendered node.
- Merge the duplicated "### Added" heading in the Unreleased changelog.
- Document that to_json()/to_xml() output is undefined for trees built
by other constructors, and mark the @/#text line encoding as an
implementation detail; drop issue tags from API docstrings.
Skipped per review guidance: single-scan _xml_identity_suffix (changes
list_keys priority semantics) and the _store_json_member double-lookup
micro-nit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the remaining scope of #232 (set-style support and graceful XML/JSON rejection shipped in #278; this adds actual ingestion and rendering).
New
hier_config/formats.pywith a documented, invertible tree mapping:HConfig.from_json(platform, data, list_keys=("name", "id"))— JSON objects/text (e.g. OpenConfig). Scalars becomekey <json>leaves, objects become branches, scalar lists become repeated leaves, and keyed lists (OpenConfig-style) become one branch per entry named by its identifying member — so entries diff structurally, exactly what the tree engine needs.HConfig.from_xml(platform, source, list_keys=...)— XML documents (e.g. NETCONF payloads). Attributes map to@nameleaves, text content to value leaves, repeated sibling elements are identified vialist_keys, and mixed content uses#textleaves.HConfig.to_json()/to_xml()— invert the mappings. Structured configs can be diffed withremediation(), predicted withfuture(), and the result rendered back in the source format. Round-trip fidelity (parse → render → reparse == original) is pinned by tests for both formats.Design notes:
hostname "router1",port 123) so types survive the round trip.xml.etree.ElementTreeis safe here: it does not resolve external entities (XXE) — theS405suppression is annotated.edit-configoperation attributes have no remediation semantics yet (tracked as the final piece of v4: Graceful handling of structured config formats (XML, JSON, set-style) #232).Test plan
list_keys, error cases (invalid JSON/XML, non-object root, whitespace keys, unidentifiable list entries, multi-root XML), diff andfuture()over structured configs with JSON re-rendering, and detection-message updates.poetry run ./scripts/build.py lint-and-test— all linters pass (ruff 0.15, mypy strict, pyright strict, pylint), 689 tests, ≥95% coverage.🤖 Generated with Claude Code