Summary
The CI matrix in .github/workflows/ansible-test.yml tests Python 3.10 against stable-2.16 and stable-2.17, but the collection's declared Python floor is 3.11+. The matrix should be the intersection of cisco/nd's floor and ansible-core's supported Python range — i.e., drop the two 3.10 cells.
Current state
pyproject.toml line 6 declares the floor:
requires-python = ">=3.11"
CLAUDE.md mirrors it:
Python >= 3.11 required
But .github/workflows/ansible-test.yml matrix (lines 60–74) still includes:
# Ansible 2.17: supports Python 3.7 - 3.12
- ansible_version: "stable-2.17"
python_version: "3.12"
- ansible_version: "stable-2.17"
python_version: "3.11"
- ansible_version: "stable-2.17"
python_version: "3.10" # below declared floor
# Ansible 2.16: supports Python 3.10 - 3.12
- ansible_version: "stable-2.16"
python_version: "3.12"
- ansible_version: "stable-2.16"
python_version: "3.11"
- ansible_version: "stable-2.16"
python_version: "3.10" # below declared floor
ansible-core itself supports 3.10 in those versions, but cisco/nd does not.
How it surfaced
On the nd_interface_ethernet_access stack, CI unit tests failed only on the two 3.10 cells with:
AttributeError: 'ModelPrivateAttr' object has no attribute 'match'
This is a real Pydantic v2 behavior: leading-underscore class attributes on a BaseModel (e.g. _INTERFACE_NAME_PREFIX_RE: ClassVar = re.compile(...)) get wrapped in ModelPrivateAttr regardless of the ClassVar annotation. The Pydantic build that resolves under Python 3.10 doesn't unwrap the descriptor on class access, so cls._INTERFACE_NAME_PREFIX_RE.match(...) raises AttributeError. On Python 3.11+ the bundled Pydantic does unwrap and the call works. Every cell at or above our declared floor (3.11/3.12/3.13 across 2.16–2.19) was green.
The right response to "an unsupported configuration fails" is to stop testing the unsupported configuration, not to work around it in the source — the workaround silently extends de-facto support without updating pyproject.toml, and ages poorly.
Proposed change
Edit .github/workflows/ansible-test.yml:
Blast radius
Workflow-only change. Every open PR's next CI run will lose two job entries but no existing job semantics change:
- PRs green today stay green.
- PRs red only on the two 3.10 cells turn green — correct, since those configurations are unsupported.
Alternative considered
Raise the declared floor commitment to include 3.10 (update pyproject.toml and CLAUDE.md, keep the matrix) and apply Pydantic workarounds in the source. Rejected because it extends maintenance burden to an older Python without a stated reason and because the cisco/nd codebase already uses Python 3.11+ syntax features (PEP 604 unions, X | None) per CLAUDE.md.
Summary
The CI matrix in
.github/workflows/ansible-test.ymltests Python 3.10 againststable-2.16andstable-2.17, but the collection's declared Python floor is 3.11+. The matrix should be the intersection of cisco/nd's floor and ansible-core's supported Python range — i.e., drop the two 3.10 cells.Current state
pyproject.tomlline 6 declares the floor:CLAUDE.mdmirrors it:But
.github/workflows/ansible-test.ymlmatrix (lines 60–74) still includes:ansible-core itself supports 3.10 in those versions, but cisco/nd does not.
How it surfaced
On the
nd_interface_ethernet_accessstack, CI unit tests failed only on the two 3.10 cells with:This is a real Pydantic v2 behavior: leading-underscore class attributes on a
BaseModel(e.g._INTERFACE_NAME_PREFIX_RE: ClassVar = re.compile(...)) get wrapped inModelPrivateAttrregardless of theClassVarannotation. The Pydantic build that resolves under Python 3.10 doesn't unwrap the descriptor on class access, socls._INTERFACE_NAME_PREFIX_RE.match(...)raisesAttributeError. On Python 3.11+ the bundled Pydantic does unwrap and the call works. Every cell at or above our declared floor (3.11/3.12/3.13 across 2.16–2.19) was green.The right response to "an unsupported configuration fails" is to stop testing the unsupported configuration, not to work around it in the source — the workaround silently extends de-facto support without updating
pyproject.toml, and ages poorly.Proposed change
Edit
.github/workflows/ansible-test.yml:Drop matrix entry
ansible_version: "stable-2.17" + python_version: "3.10"(line 65–66)Drop matrix entry
ansible_version: "stable-2.16" + python_version: "3.10"(line 73–74)Fix the comment on line 60: ansible-core 2.17 needs Python 3.10+, not 3.7+ — the comment currently reads "Ansible 2.17: supports Python 3.7 - 3.12" which is wrong
Update the remaining comments to note cisco/nd's 3.11 floor, e.g.:
Blast radius
Workflow-only change. Every open PR's next CI run will lose two job entries but no existing job semantics change:
Alternative considered
Raise the declared floor commitment to include 3.10 (update
pyproject.tomlandCLAUDE.md, keep the matrix) and apply Pydantic workarounds in the source. Rejected because it extends maintenance burden to an older Python without a stated reason and because the cisco/nd codebase already uses Python 3.11+ syntax features (PEP 604 unions,X | None) perCLAUDE.md.