Skip to content

Lint for published dependencies declared with no lower bound #108

Description

@lesnik512

Split out of #107. That issue is about a CI job that resolves dependencies downward and runs the suite. This one is a static check that needs no resolver, no interpreter matrix and no test run — and it catches a defect the audit found in four repos.

The defect

A dependency declared as a bare name has no lower bound, so the declared range admits every version ever published. It reads as deliberate in a dependency list and is only visible when something resolves it downward.

From modern-python/db-retry#52 and modern-python/semvertag#78:

db-retry:  sqlalchemy[asyncio]  ->  0.1.0   (released 2006)
semvertag: semver               ->  0.0.1

Both fail to build, so those packages do not install at their own declared minimum, on any supported Python.

Current state, all 24 repos

Published dependencies[project] dependencies and [project.optional-dependencies], excluding self-referential extras:

repo bare dependencies
semvertag typer, rich, semver, pydantic-settings, httpx2
db-retry tenacity, sqlalchemy[asyncio], asyncpg
eof-fixer pathspec
that-depends [fastapi] fastapi, [faststream] faststream

11 declarations across 4 repos. The other 20 repos are clean here.

Dev and lint groups: 182 bare entries across all 24 repos — pytest, ruff, ty, eof-fixer and friends, almost none of which carry a bound anywhere.

Why this is worth separating from #107

It finds every instance at once. A resolver stops at the first wall, so the lower-bound audit reported one broken dependency per repo and stopped. The static scan shows db-retry has three unbounded dependencies and semvertag has five, not the one each that the audit surfaced. Fixing what the audit reported would leave most of them in place.

It is cheap. No venv, no matrix, no network. It is a parse of pyproject.toml and a regex. It can run in the existing lint-ci recipe and add no measurable time.

It prevents rather than detects. #107 tells you a floor is wrong after someone wrote it. This refuses the commit that omits one.

Scope decision needed

The published dependencies are clearly in scope: they are the contract users install against, and all four cases above are real defects.

The 182 dev/lint entries are a policy call:

My read: enforce on published dependencies now, and leave dev groups out until someone actually wants the one-step form in #107. Worth an explicit decision either way rather than letting the check's scope drift.

Implementation

No mechanism exists today for a check that spans repos — each repo carries its own copy of _checks.yml, and .github has no workflow that reaches into the others. Two options that fit the org as it is:

  1. A small CLI, the way eof-fixer already works. modern-python already ships a single-purpose lint tool that every repo calls from lint-ci. A second one in that mould would be consistent, versioned, and fixable in one place.
  2. A test per repo, the way tests/test_adr_citations.py and tests/test_invariant_census.py already assert repo-shape invariants. Cheaper to land, but it is 24 copies of the same twenty lines.

(1) is the better fit if this is worth doing at all; (2) is the faster way to find out whether it is.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions