pythonsetuptoolsmypy

mypy —explicit-package-based vs setuptools


I’ve a project structured as follows:

.
├── hello
│   ├── __init__.py
│   └── animal.py
├── tests
│   ├── __init__.py
│   └── test_animal.py
├── README
└── pyproject.toml

This is just a personal Python library, and doesn’t need to be published or distributed. The usage consists of running pytest and mypy from the root directory.

Among other things, the pyproject.toml contains the following sections:

[project.optional-dependencies]
test = [
    "pytest",
]
lint = [
    "ruff",
    "mypy",
]

[tool.mypy]
exclude = [
    'venv',
]
ignore_errors = false
warn_return_any = true
disallow_untyped_defs = true

I install the dependencies locally as follows:

% $(brew --prefix python)/bin/python3 -m venv ./venv

% ./venv/bin/python -m pip install --upgrade pip '.[test]' '.[lint]'

But my GitHub CI fails with the following error:

hello/__init__.py: error: Duplicate module named "hello" (also at "./build/lib/hello/__init__.py")
hello/__init__.py: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules for more info
hello/__init__.py: note: Common resolutions include: a) using `--exclude` to avoid checking one of them, b) adding `__init__.py` somewhere, c) using `--explicit-package-bases` or adjusting MYPYPATH
Found 1 error in 1 file (errors prevented further checking)
Error: Process completed with exit code 2.

As suggested, running mypy with --explicit-package-bases fixes this problem, but so does addition of the following section in pyproject.toml.

[tool.setuptools]
py-modules = []

I’ve reviewed the mypy, and setuptools documentations, but am not sure which of the two is better suited for my purpose, or why are they even necessary. As mentioned earlier, I’m not trying to publish or distribute this as a Python package.

Which one of the two configurations the recommended way to go, and why?


Solution

  • I found a pip ticket for this exact problem where pytest was confused by the presence of a build directory. One of the suggestions in the ticket was to ignore the build directory. Apparently, in-place builds were introduced in pip 20.1, and is now the default.

    The following configuration in pyproject.toml solves the problem. However, it's a surprise that mypy does't exclude the directory automatically. I've created a ticket on their GitHub.

    [tool.mypy]
    exclude = [
        "venv",
        "build",
    ]