Skip to content

pip install -e ".[dev]" produces an environment where pytest cannot start #110

Description

@jeremymanning

Part of #108 · Phase 0 · Blocks all measurement.

Problem

The documented setup produces an environment in which pytest cannot start.

pyproject.toml:189 hard-requires xdist:

addopts = "-v --tb=short --strict-markers -n 4 --dist loadfile"

but pytest-xdist is in the [test] extra, not [dev]. So after the documented pip install -e ".[dev]":

__main__.py: error: unrecognized arguments: -n --dist loadfile
  inifile: /Users/jmanning/clustrix/pyproject.toml

Separately, on this machine no interpreter can import paramiko — a hard runtime dependency — across anaconda base, homebrew, system, and mambaforge. There is no project venv. import clustrix fails everywhere, which is why every coverage figure in issues #61/#86/#98-#106 is unverifiable.

Undeclared test dependencies cause 6 collection errors on a clean [dev] install:

  • numpy -> tests/test_decorator_real.py, tests/comprehensive/test_{edge_cases,failure_recovery,performance_benchmarks,serialization}_real.py
  • pandas -> the same comprehensive/ files
  • sklearn -> 2 further failures

And one error is unfixable by installation:

  • tests/real_world/test_container_registry_comprehensive.py:571 — SyntaxError: f-string expression part cannot include a backslash. This blocks collection of the whole run on Python <=3.11 even under -m "not real_world".

Also in scope

requires-python = ">=3.8" (pyproject.toml:14, setup.py:32). Python 3.8 and 3.9 are both past end-of-life. The f-string bug above is precisely a <=3.11 issue.

Acceptance criteria

  • pip install -e ".[dev]" on a clean machine yields an environment where pytest starts and collects with zero errors.
  • [dev] includes everything addopts and the tests require: pytest-xdist, numpy, pandas, scikit-learn (or the tests needing them are moved behind an extra and marked).
  • tests/real_world/test_container_registry_comprehensive.py:571 syntax error fixed.
  • requires-python raised to a supported floor (>=3.10 recommended); CI matrix updated to match.
  • A documented, reproducible dev-env bootstrap (make dev or equivalent) that a new contributor can run start to finish.
  • CI job that installs only [dev] on a clean runner and asserts collection succeeds — so this cannot regress.

Verification

python -m venv /tmp/fresh && /tmp/fresh/bin/pip install -e ".[dev]"
/tmp/fresh/bin/pytest tests/ --collect-only -q 2>&1 | tail -5   # 0 errors

Activity

  1. added
    P0-criticalBlocks everything; safety or correctness landmine
    testingTest suite, CI, coverage
    on Aug 17, 2026
  2. added 2 commits that reference this issue on Aug 17, 2026
  3. jeremymanning commented on Aug 17, 2026

    @jeremymanning
    MemberAuthor

    New evidence from PR #129: unbounded dependency pins make CI fail with no code change.

    While landing #109, CI failed on Lint with black with "19 files would be reformatted" — none of which the PR touched.

    Cause: pyproject.toml:86,135 declared "black>=21.0" with no upper bound, so pip install -e ".[dev,...]" resolved black 26.5.1, whose new stable style reformats 19 existing files. .pre-commit-config.yaml separately pins rev: 25.1.0. Local pre-commit and CI were therefore running different formatters, and CI could turn red spontaneously whenever black published a release.

    Reproduced exactly by installing 26.5.1 locally: 19 files would be reformatted, 276 files would be left unchanged.

    Fixed in #129 by pinning black==25.1.0; python_version >= "3.9" and running lint once on ubuntu/3.11. The tree passes cleanly under 25.1.0, so no files needed reformatting.

    Still outstanding for this issue — the same unbounded pattern applies to the other linters, and they carry identical drift risk:

    pyproject.toml:87,136   "flake8>=3.8"
    pyproject.toml:88,137   "mypy>=0.812"
    
    • Pin flake8 and mypy to versions the tree currently passes under
    • Consider pinning or constraining the runtime deps too — 6 of the 127 failures in Fix the 127 test failures and 8 collection errors that CI never sees #114 are TypeError: __init__() missing 1 required keyword-only argument: response, which is googleapiclient changing under an unpinned requirement. Same root cause, different package.

    This is worth treating as part of this issue's "reproducible dev-env bootstrap" AC: an environment is not reproducible if pip install -e ".[dev]" resolves differently today than yesterday.

  4. jeremymanning commented on Aug 17, 2026

    @jeremymanning
    MemberAuthor

    Dependabot alert #1 — caused by the black pin in #129

    black==25.1.0 (pinned in PR #129 to stop CI drift) falls inside a high-severity advisory range:

    black >= 24.3.0, < 26.3.1
    Arbitrary file writes from unsanitized user input in cache file name
    

    Full disclosure: the previous unbounded black>=21.0 would have resolved to 26.5.1, which is patched. So the pin fixed a CI-determinism problem and introduced a (low-practical-risk) advisory. Both facts belong on the record.

    Practical risk

    The vector is a cache filename derived from untrusted input. black here runs over the project's own source, in CI and pre-commit, with no attacker-controlled paths. Real exposure is close to nil — but the alert stays red until resolved, and "red alerts we have decided to ignore" is exactly the habit this repo is trying to break.

    The awkward part

    The patched line (>=26.3.1) requires Python >=3.10, and the CI matrix still includes 3.8 and 3.9. So a straight bump is impossible without one of:

    (a) Raise the floor. Drop 3.8/3.9 and set requires-python = ">=3.10" — already an AC of this issue, and both are long EOL (3.8 Oct 2024, 3.9 Oct 2025). Then pin black==26.5.1 and reformat the 19 files it restyles in one dedicated commit.

    (b) Environment markers — black==26.5.1; python_version >= "3.10" plus an older pin below. Then two formatters disagree, which is the original bug in a new costume. Not recommended.

    (c) Stop installing black per-matrix-entry. #129 already gated lint steps to ubuntu/3.11 only; the logical next step is moving linting into its own job with its own pinned toolchain, so formatter version stops being coupled to the Python support matrix. Cleanest fix, and it makes (a) independent rather than a prerequisite.

    Recommendation

    Do (c), then (a). Together they clear this alert, remove the 3.8/3.9 EOL exposure, and decouple lint tooling from the runtime support matrix — the underlying design problem, not the black version.

    The same unbounded-pin risk still applies to flake8>=3.8 and mypy>=0.812 (pyproject.toml:87-88,136-137) and to unpinned runtime deps — 6 of the 127 failures in #114 are googleapiclient changing under an unpinned requirement.

  5. jeremymanning commented on Aug 17, 2026

    @jeremymanning
    MemberAuthor

    Correction: the addopts premise in this issue does not describe master

    Found while fixing #130. Two claims here need amending, and one acceptance-criteria item is now done.

    1. The quoted addopts was never live on master

    This issue opens with:

    pyproject.toml:189 hard-requires xdist:

    addopts = "-v --tb=short --strict-markers -n 4 --dist loadfile"
    

    That line is from the closed epic branch, not master. On master at a9393b7, pyproject.toml's addopts was -v --tb=short — no -n, no --dist.

    More importantly, it did not matter what it said. pytest.ini sat in the repo root with the section header [tool:pytest], which is valid only in setup.cfg. pytest still selected pytest.ini as its config file, and having selected one it stops searching — so pyproject.toml's [tool.pytest.ini_options] was never read. On master, addopts from either file was inert:

    $ pytest tests/unit/ --co 2>&1 | grep configfile
    configfile: pytest.ini
    $ pytest --markers | grep -c '^@pytest.mark.real_world'
    0
    

    So pip install -e ".[dev]" did not fail with unrecognized arguments: -n --dist loadfile on master — that reproduces only where pytest.ini is absent. Any statement about "the addopts" needs to name which config was live. Full analysis in #130.

    The underlying dependency point still stands and is now recorded in pyproject.toml: pytest-xdist is in [test], not [dev], so -n must not be added to addopts without moving it first. There is a comment above addopts saying so.

    2. scikit-learn does not cause collection errors

    sklearn -> 2 further failures

    Not at collection time. Every sklearn import under tests/ is inside a function body (test_notebook_magic_real.py:450, test_decorator_real.py:463-467, reference_workflows/kubernetes_workflows.py:68-70), so it is never executed during collection. With numpy and pandas installed and nothing else, collection is clean:

    $ pytest tests/ -m "not real_world" --co -q -o addopts= | tail -1
    1275/1665 tests collected (390 deselected)      # 0 errors
    

    sklearn may still be needed to run those tests; it is not needed to collect them.

    3. Two acceptance-criteria items are done (in #130's PR)

    • tests/real_world/test_container_registry_comprehensive.py:571 syntax error fixed. The backslash was inside an f-string expression, a SyntaxError on every Python before 3.12 while requires-python says >=3.8. The split() is now hoisted out. An AST parse over all of tests/ and clustrix/ confirms it was the only such error in the tree.
    • numpy and pandas added to the [dev] extra. These were the real cause of the collection errors. On master a clean [dev] install gave 6 collection errors; it now gives 0.

    Still open here: pytest-xdist placement, the requires-python floor, the bootstrap command, and the CI job that installs only [dev] and asserts collection succeeds.

    The verification command in this issue's "Verification" section now passes:

    $ python -m venv /tmp/fresh && /tmp/fresh/bin/pip install -e ".[dev]"
    $ /tmp/fresh/bin/pytest tests/ --collect-only -q 2>&1 | tail -1
    1670 tests collected                            # 0 errors
    
  6. added 4 commits that reference this issue on Aug 17, 2026
  7. jeremymanning commented on Aug 20, 2026

    @jeremymanning
    MemberAuthor

    Closed — the environment starts pytest

    $ pytest --collect-only -q
    1677 tests collected, 0 errors
    

    Each named cause is gone: addopts carries no -n, so xdist is not required to start; requires-python = ">=3.10" (pyproject.toml:19, setup.py:31) with a CI matrix of 3.10/3.11/3.12; [dev] declares numpy, pandas, scikit-learn, ipywidgets, ipython and pytest-timeout; the file carrying the f-string SyntaxError no longer exists. fast_ci.yml:47 installs only .[dev] and runs the suite green.

    The one item not done is a make dev bootstrap. There is no Makefile, and CONTRIBUTING.md documents the pip command instead — a documentation nicety rather than the defect this issue describes.

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

    P0-criticalBlocks everything; safety or correctness landminebugtestingTest suite, CI, coverage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions