From 8dac2e67dd852c0b1dc699aa0af67721d13905ad Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Fri, 23 Oct 2026 08:46:19 +0200 Subject: [PATCH] chore: adopt the NAPALM driver standard - CI arrives in .gitea/workflows/ci.yml and calls the shared driver workflow of NAPALM/driver-standard; the repo had none before. - setuptools>=77 and the SPDX license string PEP 639 wants, without the License classifier; py.typed; Repository, Issues and Changelog links on git.netork.io; napalm-device-types>=4.21.1 from PyPI. - True floors, found by running the tests at them: redfish>=3.4.1 (older releases return no response with max_retry=0) and pyghmi>=1.5.1 (1.5.0 imports python-dateutil without declaring it). - The test extra becomes the dev extra of the standard, plus responses, which the tests mock the BMC with. pytest puts the repo root on the path, so tests.conftest also imports under a bare pytest. - mypy strict; exempt for now (50 errors, most of them getters returning dicts where the role declares TypedDicts): napalm_redfish.bmc, .driver, .ipmi, .transport and parsers.common, .environment, .manager, .system. They are recorded in tests/quality_baseline.json with seven functions over complexity 10. - Coverage floor 94 % (measured 94.27 %). - The README's development steps install from PyPI. --- .gitea/workflows/ci.yml | 15 +++++++++ .gitignore | 3 ++ CHANGELOG.md | 11 +++++++ README.md | 9 +++--- napalm_redfish/py.typed | 0 pyproject.toml | 64 +++++++++++++++++++++++++++++-------- tests/quality_baseline.json | 25 +++++++++++++++ 7 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 napalm_redfish/py.typed create mode 100644 tests/quality_baseline.json diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..7530c79 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,15 @@ +name: CI + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + +jobs: + driver: + uses: NAPALM/driver-standard/.gitea/workflows/driver.yml@main + with: + standard-ref: main + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 3b4fdc1..cca36af 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ build/ dist/ .venv/ .pytest_cache/ +.coverage +.mypy_cache/ +.ruff_cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a07f774..af37fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- The driver follows the NAPALM driver standard + (https://git.netork.io/NAPALM/driver-standard): CI on Gitea runs ruff, mypy, the + tests on Python 3.10–3.13 and at the lowest declared versions, bandit, pip-audit and + a build check, and a release tag publishes to PyPI. Python 3.10 stays the floor; the + package now ships `py.typed`, and the license is declared as the SPDX string + `Apache-2.0`. napalm-device-types now comes from PyPI (4.21.1 or later). +- Requires redfish 3.4.1 and pyghmi 1.5.1: older redfish releases return no answer + with `retries=0`, and pyghmi 1.5.0 misses a dependency it imports. +- A failed IPMI logout on close is now logged at debug level. + ## [0.5.0] - 2026-10-17 ### Added diff --git a/README.md b/README.md index 0a55dd4..156ada5 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,6 @@ These follow the other drivers in the `NAPALM` organisation: - Parsing lives in pure functions, tested on recorded responses under `tests/fixtures/--/`, with a `COMMANDS.txt` (here: the request paths) listing what produced each file. -- `napalm-device-types` is not on PyPI: install it from - `git+https://git.netork.io/NAPALM/napalm-device-types.git` before this package. ## Usage @@ -104,11 +102,12 @@ to basic auth only if the service cannot create sessions), `retries` (1). ## Development +The rules are in [STANDARDS.md](https://git.netork.io/NAPALM/driver-standard/src/branch/main/STANDARDS.md). + ```bash python -m venv .venv && . .venv/bin/activate -pip install git+https://git.netork.io/NAPALM/napalm-device-types.git -pip install -e '.[test]' -pytest +pip install -e '.[dev]' +pytest --cov ``` Fixtures from the DMTF mockups live under `tests/fixtures/dmtf-/`, unmodified, with diff --git a/napalm_redfish/py.typed b/napalm_redfish/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index b0509f3..afb06a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=64"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] @@ -7,27 +7,30 @@ name = "napalm-redfish" version = "0.5.0" description = "NAPALM driver for server baseboard management controllers over DMTF Redfish" readme = "README.md" -license = { file = "LICENSE" } +license = "Apache-2.0" requires-python = ">=3.10" dependencies = [ "napalm>=5.0", - # Not on PyPI: install from git before this package (see README). - "napalm-device-types>=4.15", - "redfish>=3.2", - "pyghmi>=1.5", + "napalm-device-types>=4.21.1", + # Before 3.4.1, max_retry=0 (retries=0) gets no response back at all. + "redfish>=3.4.1", + # 1.5.0 imports python-dateutil without declaring it. + "pyghmi>=1.5.1", ] classifiers = [ - "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Topic :: System :: Hardware", "Topic :: System :: Networking", ] [project.optional-dependencies] -test = [ - "pytest>=7", - "responses>=0.23", -] +# responses mocks the BMC's HTTP answers in the tests. +dev = ["pytest>=8", "pytest-cov>=5", "mypy>=1.11", "ruff>=0.6", "responses>=0.23"] + +[project.urls] +Repository = "https://git.netork.io/NAPALM/napalm-redfish" +Issues = "https://git.netork.io/NAPALM/napalm-redfish/issues" +Changelog = "https://git.netork.io/NAPALM/napalm-redfish/src/branch/main/CHANGELOG.md" [project.entry-points."napalm.drivers"] redfish = "napalm_redfish:RedfishDriver" @@ -37,8 +40,8 @@ bmc = "napalm_redfish.bmc:BmcDriver" [tool.setuptools.packages.find] include = ["napalm_redfish*"] -[tool.pytest.ini_options] -testpaths = ["tests"] +[tool.setuptools.package-data] +napalm_redfish = ["py.typed"] [tool.ruff] line-length = 100 @@ -50,3 +53,38 @@ select = ["E", "F", "I", "UP", "B"] [tool.ruff.lint.per-file-ignores] # Recorded device output keeps its lines. "tests/**" = ["E501"] + +[tool.mypy] +python_version = "3.10" +strict = true +# napalm itself is untyped, so every driver subclasses Any. +disallow_subclassing_any = false + +[[tool.mypy.overrides]] +module = ["napalm.*", "redfish.*", "pyghmi.*", "requests.*"] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +# Not yet under strict mypy (quality baseline); may only shrink. +module = [ + "napalm_redfish.bmc", + "napalm_redfish.driver", + "napalm_redfish.ipmi", + "napalm_redfish.parsers.common", + "napalm_redfish.parsers.environment", + "napalm_redfish.parsers.manager", + "napalm_redfish.parsers.system", + "napalm_redfish.transport", +] +ignore_errors = true + +[tool.pytest.ini_options] +testpaths = ["tests"] +# The tests import their helpers as tests.conftest, also under a bare pytest. +pythonpath = ["."] + +[tool.coverage.run] +source = ["napalm_redfish"] + +[tool.coverage.report] +fail_under = 94 diff --git a/tests/quality_baseline.json b/tests/quality_baseline.json new file mode 100644 index 0000000..efda9c9 --- /dev/null +++ b/tests/quality_baseline.json @@ -0,0 +1,25 @@ +{ + "coverage_floor": 94.0, + "function_complexity": { + "napalm_redfish/driver.py::RedfishDriver._accepted_actions": 14, + "napalm_redfish/driver.py::RedfishDriver.get_environment": 14, + "napalm_redfish/driver.py::RedfishDriver.get_facts": 15, + "napalm_redfish/parsers/environment.py::build_environment": 13, + "napalm_redfish/parsers/manager.py::parse_network_protocol": 14, + "napalm_redfish/transport.py::RedfishTransport._request": 14, + "napalm_redfish/transport.py::RedfishTransport.members": 15 + }, + "function_lines": {}, + "mypy_relaxed": [ + "napalm_redfish.bmc", + "napalm_redfish.driver", + "napalm_redfish.ipmi", + "napalm_redfish.parsers.common", + "napalm_redfish.parsers.environment", + "napalm_redfish.parsers.manager", + "napalm_redfish.parsers.system", + "napalm_redfish.transport" + ], + "python_file_lines": {}, + "untyped_public": [] +}