From 457c1ca7edace84397f2c9480486df9ac29ff3ea Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Fri, 23 Oct 2026 08:40:41 +0200 Subject: [PATCH] chore: adopt the NAPALM driver standard - CI moves to .gitea/workflows/ci.yml and calls the shared driver workflow of NAPALM/driver-standard; .github/ is gone. napalm-device-types comes from git until 4.22.0 (AptProxyMixin) is on PyPI; the floor stays at 4.22.0. - requires-python >=3.10 (napalm-device-types needs it), py.typed, SPDX license string, a dev extra with floors (pytest-mock and responses are dropped: no test uses them), links to git.netork.io (README included). pytest no longer adds --cov by itself; CI passes it. - mypy strict for the package; exempt for now, with 204 errors between them: napalm_proxmox.config_mixin, .driver, .interfaces_mixin, .lldp_mixin, .routing_mixin, .sdn_mixin, .system_mixin, .utils, .vm_contract_mixin, .vm_mixin and .vm_provision_mixin. They are recorded in the quality baseline with the long and complex functions and the two unannotated public methods. - Coverage floor 68 % (measured 68.94 %). --- .gitea/workflows/ci.yml | 17 ++++++++++ .github/workflows/ci.yml | 48 --------------------------- CHANGELOG.md | 10 ++++++ README.md | 5 +-- napalm_proxmox/py.typed | 0 pyproject.toml | 62 +++++++++++++++++++++++++---------- tests/quality_baseline.json | 65 +++++++++++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 .gitea/workflows/ci.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 napalm_proxmox/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..34f9b57 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [main, master] + tags: ["v*"] + pull_request: + +jobs: + driver: + uses: NAPALM/driver-standard/.gitea/workflows/driver.yml@main + with: + standard-ref: main + # Until napalm-device-types 4.22.0 (AptProxyMixin, #53) is on PyPI. + pre-install: "git+https://git.netork.io/NAPALM/napalm-device-types.git" + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a7bc4d4..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: CI - -on: - push: - branches: ["**"] - pull_request: - branches: ["**"] - -jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12"] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - - name: Install package with dev extras - run: | - python -m pip install --upgrade pip - # napalm-device-types lives in git.netork.io/NAPALM, not on PyPI: without this - # pip looks there, finds an unrelated 0.1.0 and the job dies before any test. - python -m pip install "napalm-device-types @ git+https://git.netork.io/NAPALM/napalm-device-types.git" - python -m pip install -e ".[dev]" - - - name: Run unit tests - run: | - python -m pytest -q --tb=short - - - name: Build wheel and sdist - run: | - python -m pip install build - python -m build - - - name: Upload dist artifacts - # v4 refuses to run on Gitea ("not currently supported on GHES"). - uses: actions/upload-artifact@v3 - with: - name: dist-${{ matrix.python-version }} - path: dist/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dc9fd7..9afc381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ 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. The package now ships `py.typed`. + **Python 3.9 is no longer supported**; napalm-device-types never ran on it. +- Exceptions the driver deliberately ignores (closing the node's SSH session, the + `is_alive()` probe) are now logged at debug level. +- The project links point at git.netork.io. + ### Added - apt's proxy (napalm-device-types' `AptProxyMixin`, NetOrk/netork#1397): `get_package_proxy()`, `get_package_proxy_report()`, `check_package_proxy(url, expect)` and the fix `configure_package_proxy(url)`, as on any Debian host. The node is logged in as root, so the file is written and other proxy lines are commented out over the exec path. Requires napalm-device-types 4.22.0; version 0.7.0. - `get_service_packages()` (napalm-device-types' `ServicePackagesMixin`, NetOrk/netork#1236): which installed packages each systemd unit of the node is made of — the one shipping its unit file and the one shipping its `ExecStart` binary. Read-only, over the exec path. Requires napalm-device-types 4.20.0; version 0.6.0. diff --git a/README.md b/README.md index df2a326..c99de0a 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ It supports all three networking domains of Proxmox VE: ## Requirements -- Python ≥ 3.9 +- Python ≥ 3.10 - NAPALM ≥ 5.0.0 +- napalm-device-types ≥ 4.22.0 - proxmoxer ≥ 2.0.0 - netaddr ≥ 0.9.0 - requests ≥ 2.31.0 @@ -27,7 +28,7 @@ pip install napalm-proxmox Or directly from source: ```bash -git clone https://github.com/example/napalm-proxmox.git +git clone https://git.netork.io/NAPALM/napalm-proxmox.git cd napalm-proxmox pip install -e . ``` diff --git a/napalm_proxmox/py.typed b/napalm_proxmox/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index c1ecdca..719afe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61", "wheel"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] @@ -7,22 +7,20 @@ name = "napalm-proxmox" version = "0.7.0" description = "NAPALM driver for Proxmox VE (classic Linux networking, SDN, OVS)" readme = "README.md" -license = { text = "Apache-2.0" } +license = "Apache-2.0" authors = [{ name = "Christian Manivong" }] keywords = ["napalm", "proxmox", "network", "sdn", "ovs", "automation"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Intended Audience :: System Administrators", - "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: System :: Networking", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "napalm>=5.0.0", "napalm_device_types>=4.22.0", @@ -33,22 +31,23 @@ dependencies = [ ] [project.optional-dependencies] -dev = [ - "pytest>=7.4", - "pytest-cov>=4.1", - "pytest-mock>=3.12", - "responses>=0.24", - "ruff>=0.1", - "mypy>=1.7", -] +dev = ["pytest>=8", "pytest-cov>=5", "mypy>=1.11", "ruff>=0.6"] [project.entry-points."napalm.drivers"] proxmox = "napalm_proxmox:ProxmoxDriver" +[project.urls] +Repository = "https://git.netork.io/NAPALM/napalm-proxmox" +Issues = "https://git.netork.io/NAPALM/napalm-proxmox/issues" +Changelog = "https://git.netork.io/NAPALM/napalm-proxmox/src/branch/main/CHANGELOG.md" + [tool.setuptools.packages.find] where = ["."] include = ["napalm_proxmox*"] +[tool.setuptools.package-data] +napalm_proxmox = ["py.typed"] + [tool.ruff] line-length = 100 target-version = "py310" @@ -60,11 +59,38 @@ select = ["E", "F", "I", "UP", "B"] # 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.*", "proxmoxer.*", "paramiko.*", "netaddr.*", "yaml.*"] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +# Not yet under strict mypy (quality baseline); may only shrink. +module = [ + "napalm_proxmox.config_mixin", + "napalm_proxmox.driver", + "napalm_proxmox.interfaces_mixin", + "napalm_proxmox.lldp_mixin", + "napalm_proxmox.routing_mixin", + "napalm_proxmox.sdn_mixin", + "napalm_proxmox.system_mixin", + "napalm_proxmox.utils", + "napalm_proxmox.vm_contract_mixin", + "napalm_proxmox.vm_mixin", + "napalm_proxmox.vm_provision_mixin", +] +ignore_errors = true + [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--cov=napalm_proxmox --cov-report=term-missing" -[tool.mypy] -python_version = "3.9" -warn_return_any = true -warn_unused_configs = true +[tool.coverage.run] +source = ["napalm_proxmox"] + +[tool.coverage.report] +fail_under = 68 diff --git a/tests/quality_baseline.json b/tests/quality_baseline.json new file mode 100644 index 0000000..988e73a --- /dev/null +++ b/tests/quality_baseline.json @@ -0,0 +1,65 @@ +{ + "coverage_floor": 68.0, + "function_complexity": { + "napalm_proxmox/driver.py::ProxmoxDriver._resolve_node": 13, + "napalm_proxmox/driver.py::ProxmoxDriver.get_facts": 16, + "napalm_proxmox/interfaces_mixin.py::ProxmoxInterfaceMixin.get_interfaces_counters": 14, + "napalm_proxmox/interfaces_mixin.py::ProxmoxInterfaceMixin.get_mac_address_table": 22, + "napalm_proxmox/routing_mixin.py::ProxmoxRoutingMixin.get_route_to": 13, + "napalm_proxmox/routing_mixin.py::ProxmoxRoutingMixin.ping": 12, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin._find_switch_uplink": 13, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin._get_ovs_bridge_for_port": 13, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin._get_vm_vlan_tags": 11, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin._is_cluster_master": 13, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.get_network_instances": 13, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.get_vlans": 16, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.set_vlan": 13, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_disk_smart": 22, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_packages": 16, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_users": 15, + "napalm_proxmox/utils.py::addresses_from_node_network": 15, + "napalm_proxmox/vm_contract_mixin.py::parse_vm_config": 16, + "napalm_proxmox/vm_mixin.py::ProxmoxVMMixin._get_vm_disk_and_boot": 16, + "napalm_proxmox/vm_mixin.py::ProxmoxVMMixin.get_vm_interfaces": 37, + "napalm_proxmox/vm_mixin.py::ProxmoxVMMixin.get_vms": 21, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin.create_vm_from_cloud_init": 26, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin.get_vm_status": 16 + }, + "function_lines": { + "napalm_proxmox/driver.py::ProxmoxDriver.get_facts": 82, + "napalm_proxmox/interfaces_mixin.py::ProxmoxInterfaceMixin.get_mac_address_table": 71, + "napalm_proxmox/lldp_mixin.py::ProxmoxLLDPMixin.get_lldp_neighbors_detail": 52, + "napalm_proxmox/routing_mixin.py::ProxmoxRoutingMixin.ping": 78, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin._get_vm_vlan_tags": 54, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.get_vlans": 66, + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.set_vlan": 70, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_disk_smart": 74, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_packages": 66, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_users": 51, + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.set_hostname": 124, + "napalm_proxmox/vm_mixin.py::ProxmoxVMMixin.get_vm_interfaces": 179, + "napalm_proxmox/vm_mixin.py::ProxmoxVMMixin.get_vms": 114, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin._download_cloud_image": 61, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin.create_vm_from_cloud_init": 232, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin.destroy_vm": 65, + "napalm_proxmox/vm_provision_mixin.py::ProxmoxVMProvisionMixin.get_vm_status": 92 + }, + "mypy_relaxed": [ + "napalm_proxmox.config_mixin", + "napalm_proxmox.driver", + "napalm_proxmox.interfaces_mixin", + "napalm_proxmox.lldp_mixin", + "napalm_proxmox.routing_mixin", + "napalm_proxmox.sdn_mixin", + "napalm_proxmox.system_mixin", + "napalm_proxmox.utils", + "napalm_proxmox.vm_contract_mixin", + "napalm_proxmox.vm_mixin", + "napalm_proxmox.vm_provision_mixin" + ], + "python_file_lines": {}, + "untyped_public": [ + "napalm_proxmox/sdn_mixin.py::ProxmoxSDNMixin.set_vlan", + "napalm_proxmox/system_mixin.py::ProxmoxSystemMixin.get_snmp_config" + ] +}