Skip to content

Canonicalize registry keys to uppercase platform names - #295

Merged
jtdub merged 2 commits into
nextfrom
issue-284-canonical-registry-keys
Aug 5, 2026
Merged

Canonicalize registry keys to uppercase platform names#295
jtdub merged 2 commits into
nextfrom
issue-284-canonical-registry-keys

Conversation

@jtdub

@jtdub jtdub commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #284. Targets next for v4.0.0 — an API-shape decision that is free to make now and breaking to make later.

The driver registry keyed entries by Platform | str, which leaked into the public API two ways: get_registered_platforms() returned an accidentally-mixed tuple, and custom names were silently uppercased. Exploration also surfaced a verified latent bug: Platform is a str-Enum with auto() values, so members hash/compare as stringified counters (Platform.CISCO_IOS == "3") — register_driver("3", X) silently overwrote the built-in CISCO_IOS entry and get_hconfig_driver("3") resolved to the IOS driver.

  • _BUILTIN_DRIVERS / _registry are now dict[str, type[HConfigDriverBase]] keyed on canonical uppercase names; _normalize() converts Platform members via .name and uppercases strings — a member and its name are fully interchangeable in register_driver / unregister_driver / get_hconfig_driver.
  • get_registered_platforms() keeps its tuple[Platform | str, ...] signature with now-deterministic, documented semantics: Platform members for enum-known names (via exact-name Platform.__members__ lookup — never value lookup), canonical uppercase strings for custom names.
  • Value strings like "3" no longer collide with or resolve as platforms — they raise DriverNotFoundError.
  • DriverNotFoundError messages now echo the caller's spelling rather than the normalized form.

No public signatures changed. Out of scope per the issue: register_driver validation, thread-safety, exporting resolve_driver.

Testing

TDD: five new tests in tests/unit/test_registry.py; the collision test and the value-lookup test were observed failing on the pre-change code (custom driver returned for Platform.CISCO_IOS; "3" resolving instead of raising). The other three pin the listing semantics (custom names appear as uppercase strings, builtins as enum members) and member/name interchangeability with builtin restore. All existing registry tests pass unmodified.

  • poetry run ./scripts/build.py lint-and-test exits 0 (762 passed, coverage ≥95%)
  • poetry run mkdocs build --strict passes

Docs & changelog

  • docs/dev/architecture.md — registry section describes canonical uppercase-name keying and the listing semantics
  • docs/admin/custom-drivers.md — member/name interchangeability and listing-return notes
  • hier_config/registry.py docstrings (rendered into docs/dev/api-reference.md via mkdocstrings) document the canonicalization
  • CHANGELOG.md — Changed (key canonicalization) + Fixed (value-string collision) entries (v4: Canonicalize registry platform keys before 4.0.0 final #284)

Closes #284

The registry keyed entries by Platform | str, so listing semantics
were accidental and, because Platform is a str-Enum whose members
hash as their auto() counter values, register_driver("3", X)
silently overwrote the built-in CISCO_IOS entry and "3" resolved as
a platform lookup.

Key everything on canonical uppercase names: Platform members
convert via .name at the boundary, making a member and its name
interchangeable, and get_registered_platforms() converts enum-known
names back to members while custom names stay uppercase strings.
Error messages now echo the caller's spelling.

Closes #284
@jtdub
jtdub requested a review from aedwardstx as a code owner August 5, 2026 01:31
Reuse the module-level _CustomDriver instead of a copy-pasted nested
IOS subclass, name the value-string constant once, and keep cleanup
in finally with the assertion outside the try. The not-overridden
error now formats the canonical name because pre-3.11 f-strings
render a str-Enum member as its meaningless value string.
@jtdub
jtdub merged commit 503ac28 into next Aug 5, 2026
6 checks passed
@jtdub
jtdub deleted the issue-284-canonical-registry-keys branch August 5, 2026 01:44
jtdub added a commit that referenced this pull request Aug 5, 2026
* Align agent instruction files with repo reality

AGENTS.md claimed model fields must be immutable collections, but
HConfigDriverRules deliberately uses list fields so built-in rules and
callbacks can be removed by identity (#286); an agent following the rule
literally would break that API. Document the carve-out everywhere the
rule is stated (AGENTS.md, copilot-instructions, code-style.md, review
skill, new-driver skill).

The review skill diffed against master, which on any next-based branch
pulls in all of v4 and makes the report meaningless; switch to
merge-base against the actual base branch. Add the branching strategy
to AGENTS.md (previously only CLAUDE.md had it, so non-Claude agents
and Copilot would target master for v4 work) and drop the now-duplicate
section from CLAUDE.md.

Also surface facts agents had no way to learn: the CI Python matrix
(3.10-3.14), the unconditional docs --strict job and its separate
docs/requirements.txt, the formats layer (JSON/XML/NETCONF/gNMI),
future_with_report(), registry key canonicalization (#284/#295), and
per-driver unit test expectations. Fix CONTRIBUTING.md stale content
(nonexistent test file, missing yamllint/flynt, missing changelog
requirement, no branch-base guidance).

* Fix broken doc examples and stale user-facing docs

Several documented examples did not run or showed wrong output:
getting-started referenced a fixture that does not exist, tags.md
filtered on an 'ntp' tag its fixture never defines (actual output was
empty), remediation-workflows called a nonexistent delete_child()
method, config-views mixed switchport and ip address config and claimed
outputs the view never produces, and the hierarchical JunOS example was
missing six trailing set lines. All replacement outputs were verified
by running the snippets against the real fixtures.

Remove the hardcoded prerelease pin from install.md (it had already
drifted from pyproject) and add --pre to the README install so its
Quick Start, which uses the v4-only API, can actually run. Propagate
Aruba AOS-CX into the architecture driver table and config-views lists,
correct the HConfigViewBase abstract-member lists (dot1q_mode_from_vlans
is a concrete static helper), and align the interface-view example with
the in-tree base-class pattern.

Fill reference gaps: formats module, future_with_report/FutureReport,
resolve_driver, view data models, and the full built-in post-load
callback table in api-reference/rule-reference; glossary entries for
the registry, future reports, list_keys, callbacks, and
RemediationPlugin; rules-from-files loader constraints (one criterion
per lineage entry, Platform-only). Point the legacy utilities.md
redirects at admin/rules-from-files.md where that content actually
lives.

* Restructure CHANGELOG Unreleased section per Keep a Changelog

The Unreleased block had accumulated duplicate Added/Changed/Fixed
headings from successive merges, Added-type entries filed under Fixed,
a non-standard 'v4 design decisions' heading, and a stale claim that
JSON/XML ingestion was post-4.0 roadmap work when the same section
documents it as shipped. Merge to one heading per category, re-file the
misfiled entries, drop the contradictory sentence, and keep the design
decisions as an intro note. All 59 entries and every (#NNN) reference
are preserved.

Point the migration guide at the Unreleased section instead of a 4.0.0
CHANGELOG section that does not exist yet. Correct the per-file ignore
path for the benchmarks file, which moved to tests/benchmarks/ without
the lint config following; with the ignore active again, the inline
print suppressions it replaces became unused and were removed.

* Fix stale WorkflowRemediation docstring; document public members

The WorkflowRemediation class docstring, which renders into the public
API reference, still showed a v3-era example importing the removed
get_hconfig from the nonexistent hier_config.model module, and claimed
__init__ raises ValueError when it raises IncompatibleDriverError.

Add attribute docs to the FutureReport fields (the user-facing contract
of future_with_report) and docstrings to 33 previously undocumented
public members on autodoc'd classes — driver extension points
(idempotent_for, sectional_exit, prefixes, config_preprocessor), the
HConfigViewBase contract, and the HConfigBase/HConfigChild/HConfig/
HConfigChildren members that doc examples tell users to call. Also add
this PR's changelog entry (#297).
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.

1 participant