Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: 2

updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
open-pull-requests-limit: 5
groups:
python-minor-and-patch:
update-types:
- "minor"
- "patch"
# ruff minors can enable new default rules, so it is bumped on its own
exclude-patterns:
- "ruff"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
open-pull-requests-limit: 5
groups:
github-actions:
patterns:
- "*"
55 changes: 55 additions & 0 deletions tests/test_dependency_pins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import re
import tomllib
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

REQUIREMENT = re.compile(r"^([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:==\s*(\S+))?$")


def parse_requirements(lines):
# Maps lower-cased name -> pinned version (None when unpinned). Blank
# lines, comments and -r includes are skipped.
parsed = {}
for line in lines:
line = line.split("#", 1)[0].strip()
if not line or line.startswith("-"):
continue
match = REQUIREMENT.match(line)
if match is None:
raise ValueError(f"unsupported requirement line: {line!r}")
name, version = match.groups()
parsed[name.lower()] = version
return parsed


def read_requirements(name):
return parse_requirements((ROOT / name).read_text().splitlines())


def read_pyproject():
with (ROOT / "pyproject.toml").open("rb") as handle:
return tomllib.load(handle)["project"]


class DependencyPinsTest(unittest.TestCase):
# Dependabot bumps requirements.txt and pyproject.toml independently, so a
# PR that touches only one of them fails here: update the other file in
# the same PR.

def test_runtime_pins_match_pyproject(self):
requirements = read_requirements("requirements.txt")
declared = parse_requirements(read_pyproject()["dependencies"])

self.assertEqual(requirements, declared)

def test_dev_requirements_listed_in_pyproject_extra(self):
dev = read_requirements("requirements-dev.txt")
extra = parse_requirements(read_pyproject()["optional-dependencies"]["dev"])

self.assertEqual(set(dev), set(extra))


if __name__ == "__main__":
unittest.main()
Loading