add a mypy tox environment and CI job

Introduce a mypy static type checking target. The mypy settings in
pyproject.toml are deliberately permissive so the whole tree passes
today: missing stubs for untyped dependencies are ignored, untyped
globals are allowed, and modules with pre-existing errors are
enumerated in temporary ignore_errors override lists. Each relaxation
is commented as temporary scaffolding to be walked back incrementally
as modules are cleaned up; strict_optional stays enabled since
None-safety is the main property we want from mypy.

The tox env pins mypy (it is in global-requirements but not in
upper-constraints, so the version would otherwise float) and installs
types-PyYAML, since mypy does not honor the global
ignore_missing_imports setting for packages with typeshed stubs. The
new ironic-tox-mypy zuul job runs the env in check and gate.

Assisted-By: Claude Fable 5
Change-Id: Ia5ec0d52a5812214096a463346bc89525601c662
Signed-off-by: Doug Goldstein <cardoe@cardoe.com>
This commit is contained in:
Doug Goldstein
2026-07-06 22:31:40 -05:00
parent 4596f43a01
commit c92aa00375
4 changed files with 90 additions and 0 deletions
+65
View File
@@ -220,6 +220,71 @@ quiet-level = 4
ignore-words-list = "selectin,exept,taks,ser,wit,cna,myraid,dum,assertin,checkin,authenticatin,burnin"
skip = "AUTHORS,ChangeLog,*.pyc,*.inv,*.svg,*.png,*.sample,./doc/build/*,./api-ref/build/*,./releasenotes/build/*,./api-ref/build/*,./build/*"
# NOTE(cardoe): mypy is being introduced incrementally. Every relaxation in
# this section and in the override blocks below is TEMPORARY scaffolding so
# that "mypy ironic" passes on the tree as it exists today. The goal is to
# walk these back over time: remove entries from the override lists as
# modules are cleaned up, and eventually tighten the global settings
# (e.g. disallow_untyped_defs) module by module. Do NOT add new modules to
# the ignore lists; fix new code instead.
[tool.mypy]
# Check against the oldest Python we support (requires-python above), so
# annotations stay valid across every supported interpreter.
python_version = "3.10"
# TEMPORARY: most of our dependencies (oslo.*, sushy, pecan, futurist,
# stevedore, ...) ship neither inline types nor stubs, so their imports
# would each be an error. Treating them as Any is the standard first step.
# Walk back by replacing this with per-module overrides as dependencies
# gain typing or as types-* stubs are added.
ignore_missing_imports = true
# TEMPORARY: don't require annotations for module- and class-level
# variables that mypy cannot fully infer (e.g. "FOO = None" at class
# scope). Removing this today produces errors across the tree; walk it
# back once the common patterns are annotated.
allow_untyped_globals = true
# NOTE: strict_optional (None-safety) is deliberately left at its default
# of *enabled*; it is the main property we want out of mypy.
[[tool.mypy.overrides]]
# TEMPORARY: the unit tests lean heavily on dynamic patterns (e.g.
# attributes on ironic.objects that only exist after register_all(), mock
# attribute injection) and are the largest source of pre-existing errors.
# They are parsed but their errors are suppressed. Walk this back last,
# ideally per test package.
module = ["ironic.tests.*"]
ignore_errors = true
[[tool.mypy.overrides]]
# TEMPORARY: each module below has pre-existing mypy errors (as of the
# introduction of this config, with mypy 1.19 / current upper-constraints).
# They are parsed but their errors are suppressed. Remove entries from this
# list as the modules are fixed; do not add new ones.
module = [
"ironic.api.controllers.v1.port",
"ironic.api.controllers.v1.runbook",
"ironic.api.controllers.v1.utils",
"ironic.api.schemas.v1.allocation",
"ironic.api.schemas.v1.bios",
"ironic.api.schemas.v1.firmware",
"ironic.common.json_rpc.client",
"ironic.common.json_rpc.server",
"ironic.common.mdns",
"ironic.common.service",
"ironic.common.trait_based_networking.plan",
"ironic.conductor.deployments",
"ironic.db.sqlalchemy.alembic.env",
"ironic.db.sqlalchemy.models",
"ironic.drivers.modules.agent",
"ironic.drivers.modules.agent_base",
"ironic.drivers.modules.ipmitool",
"ironic.drivers.modules.redfish.bios",
"ironic.drivers.modules.redfish.firmware",
"ironic.drivers.modules.redfish.management",
"ironic.drivers.modules.redfish.raid",
"ironic.objects.portgroup",
]
ignore_errors = true
[tool.setuptools.packages.find]
include = ["ironic*"]
+15
View File
@@ -50,6 +50,21 @@ description =
deps = {[testenv:pep8]deps}
commands = pre-commit run --all-files --show-diff-on-failure codespell
[testenv:mypy]
description =
Run mypy static type checks
# NOTE(cardoe): mypy is in global-requirements but not in upper-constraints,
# so its version floats; pin it here so a new mypy release cannot break the
# gate. Bump the pin deliberately.
deps =
{[testenv]deps}
mypy>=1.19,<1.20
# Stub packages for untyped dependencies; mypy deliberately does not
# honor the global ignore_missing_imports setting for packages that
# have stubs available in typeshed.
types-PyYAML
commands = mypy {posargs:ironic}
[testenv:cover]
setenv = {[testenv]setenv}
PYTHON=coverage run --source ironic --parallel-mode
+8
View File
@@ -858,6 +858,14 @@
vars:
tox_envlist: mysql-migrations
- job:
name: ironic-tox-mypy
parent: openstack-tox
description: |
Run mypy static type checks.
vars:
tox_envlist: mypy
- job:
name: ironic-tempest-ipa-partition-uefi-pxe-grub2
description: Ironic tempest scenario test utilizing PXE (NOT iPXE),
+2
View File
@@ -10,6 +10,7 @@
- release-notes-jobs-python3
check:
jobs:
- ironic-tox-mypy
- ironic-tox-unit-mysql-migrations
- ironic-tox-unit-with-driver-libs
- ironic-cross-sushy:
@@ -82,6 +83,7 @@
voting: false
gate:
jobs:
- ironic-tox-mypy
- ironic-tox-unit-mysql-migrations
- ironic-tox-unit-with-driver-libs
- ironic-tempest-functional-python3