Python - setuptools Skips a Package Named scripts
A console entry point of mine failed with an error that made no sense, because the package it imports was sitting right there in the project:
ModuleNotFoundError: No module named 'scripts'
Three files reproduce it: an empty scripts/__init__.py, a scripts/seed.py defining main(), and a pyproject.toml.
[project]
name = "repro"
version = "0.1.0"
[build-system]
requires = ["setuptools>=82"]
build-backend = "setuptools.build_meta"
[project.scripts]
repro-seed = "scripts.seed:main"
Installing reports success, and the command it just installed doesn’t run:
$ pip install -e .
Successfully installed repro-0.1.0
$ repro-seed
ModuleNotFoundError: No module named 'scripts'
setuptools’ automatic discovery filtered the package out. Since 61.0.0 setuptools infers your project layout with no configuration, and its flat-layout finder (for packages at the project root rather than under src/) keeps a list of directory names it won’t treat as packages. FlatLayoutPackageFinder._EXCLUDE holds scripts along with tests, docs, tools, examples, benchmarks, tasks, build, and dist, which the docs call reserved names, filtered out because they match conventions for code you don’t distribute.
The exclusion isn’t specific to editable installs or to pip. uv pip install -e . behaves the same, and a plain pip install . builds a wheel holding nothing but dist-info metadata.
import scripts still works in a Python shell started from the project directory, because Python puts the current directory on sys.path there, and it imports fine under pytest too. repro-seed is a script that pip installed into the environment’s bin/, and Python puts the script’s own directory on sys.path instead of the current one, so the command fails even when you run it from the project root.
Declaring the package explicitly overrides discovery:
[tool.setuptools.packages.find]
include = ["scripts*"]
Once include is set, setuptools finds only the names it matches, reserved or not, so list any other top-level packages there too. The * picks up subpackages like scripts.db.
Renaming the directory works too, along with the entry point (ops.seed:main). I went with ops, since a name setuptools reserves is a decent hint not to ship a package called scripts.