fix: pilot findings (pip-audit scope, SPDX license, E501 in tests) #1

Merged
christianmanivong merged 2 commits from fix/pilot-findings into main 2026-10-22 19:10:48 +00:00
8 changed files with 65 additions and 17 deletions

No files matched your search

+16 -9
View File
@@ -137,18 +137,25 @@ jobs:
with:
python-version: "3.12"
- name: Install
# Two venvs: the tools, and the package alone. Auditing the tools' venv
# reported the runner's own pip instead of anything the package brings.
run: |
python -m venv .venv
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
.venv/bin/pip install -q "$STANDARD" bandit pip-audit
if [ -n "${{ inputs.pre-install }}" ]; then .venv/bin/pip install -q ${{ inputs.pre-install }}; fi
.venv/bin/pip install -q .
python -m venv .tools
.tools/bin/pip install -q --upgrade pip
.tools/bin/pip install -q "$STANDARD" bandit pip-audit
python -m venv .audit
.audit/bin/pip install -q --upgrade pip
if [ -n "${{ inputs.pre-install }}" ]; then .audit/bin/pip install -q ${{ inputs.pre-install }}; fi
.audit/bin/pip install -q .
- name: bandit
run: bandit -q -r "$(napalm-standard package)"
run: .tools/bin/bandit -q -r "$(.tools/bin/napalm-standard package)"
- name: pip-audit
# The installed set: what a user gets today from the package's ranges.
# A package not on PyPI yet (the driver itself, a git pre-install) is skipped with a note.
run: pip-audit --skip-editable --progress-spinner off
# What a user gets today from the package's ranges. Packages installed from
# a URL (the package itself, a git pre-install) are not on PyPI's index yet.
run: |
.audit/bin/pip freeze --all | grep -vE ' @ |^-e ' > audit-requirements.txt
cat audit-requirements.txt
.tools/bin/pip-audit -r audit-requirements.txt --no-deps --disable-pip --progress-spinner off
build:
runs-on: ubuntu-latest
+11
View File
@@ -5,6 +5,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/), versions fol
## [Unreleased]
### Changed
- `check` reports a license given as a TOML table or as a `License ::` classifier:
setuptools refuses both from 2027-02-18 (PEP 639). The template builds with
setuptools>=77.
- E501 is off under `tests/`: recorded device output keeps its lines (found in the
device-types pilot).
### Fixed
- `security` audited the runner's own pip instead of the package: pip-audit now
checks a venv holding only the package and its dependencies.
## [0.1.0] - 2026-10-22
### Added
+5 -2
View File
@@ -101,7 +101,9 @@ A pull request without tests for what it adds or changes is not merged.
## 5. Clean code
- **Format and lint:** `ruff format` and `ruff check`, line length 100.
- **Format and lint:** `ruff format` and `ruff check` with `E, F, I, UP, B`, line length
100. Only under `tests/` may a line be longer (E501 off there): recorded device output
keeps its lines.
- **Types:** every public function is annotated; `mypy --strict` (§6 for the old code).
- **Small functions:** under 30 lines recommended, 50 at most; cyclomatic
complexity 10 at most. One function does one thing.
@@ -165,7 +167,8 @@ release tags:
someone else gets a descriptive one instead. The `napalm.drivers` entry point
names do not change with the package name.
- **`pyproject.toml`** declares `name`, `version`, `description`, `readme`,
`license = "Apache-2.0"`, `requires-python = ">=3.10"`, the dependencies, a
`license = "Apache-2.0"` (an SPDX string, PEP 639; no table, no `License ::`
classifier, `setuptools>=77`), `requires-python = ">=3.10"`, the dependencies, a
`dev` extra, `[project.urls]` with `Repository` (and `Issues`) on
`https://git.netork.io/NAPALM/…`, and the `napalm.drivers` entry points.
- **Dependencies are ranges with true floors.** A driver is a library: it never pins
+5
View File
@@ -77,6 +77,11 @@ def _metadata(repo: Repo) -> Iterator[Finding]:
for field in REQUIRED_FIELDS:
if not repo.project.get(field):
yield Finding("metadata", f"[project] has no {field}")
# PEP 639: setuptools refuses the table form and license classifiers from 2027-02-18.
if isinstance(repo.project.get("license"), dict):
yield Finding("metadata", 'license must be an SPDX string: license = "Apache-2.0"')
if any(c.startswith("License ::") for c in repo.project.get("classifiers", [])):
yield Finding("metadata", "drop the License :: classifier; the SPDX license says it")
def _python(repo: Repo) -> Iterator[Finding]:
+1 -1
View File
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=68"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
+5 -1
View File
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=68"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
@@ -43,6 +43,10 @@ target-version = "py310"
[tool.ruff.lint]
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
+1 -1
View File
@@ -10,7 +10,7 @@ import pytest
PYPROJECT = """\
[build-system]
requires = ["setuptools>=68"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
+21 -3
View File
@@ -51,10 +51,28 @@ def test_a_missing_project_field_is_reported(make_repo: RepoFactory, field: str)
assert field in findings[0].message
def test_a_license_table_is_accepted(make_repo: RepoFactory) -> None:
pyproject = PYPROJECT.replace('license = "Apache-2.0"', 'license = { text = "Apache-2.0" }')
@pytest.mark.parametrize(
"license_line",
['license = { text = "Apache-2.0" }', 'license = { file = "LICENSE" }'],
)
def test_a_license_table_is_reported(make_repo: RepoFactory, license_line: str) -> None:
"""setuptools refuses the table form from 2027-02-18 (PEP 639)."""
pyproject = PYPROJECT.replace('license = "Apache-2.0"', license_line)
assert check_repo(make_repo({"pyproject.toml": pyproject})) == []
findings = check_repo(make_repo({"pyproject.toml": pyproject}))
assert rules(findings) == {"metadata"}
assert "SPDX" in findings[0].message
def test_a_license_classifier_is_reported(make_repo: RepoFactory) -> None:
pyproject = PYPROJECT.replace(
'requires-python = ">=3.10"\n',
'requires-python = ">=3.10"\n'
'classifiers = ["License :: OSI Approved :: Apache Software License"]\n',
)
assert rules(check_repo(make_repo({"pyproject.toml": pyproject}))) == {"metadata"}
@pytest.mark.parametrize("spec", [">=3.8", ">=3.9", ""])