Mypy: dir is excluded but then errors are thrown for files within the dir

Hello all.

I am having trouble excluding a directory for mypy checks. The exclusion is defined in pyproject.toml, and it works when running on my dev workstation. However, it fails on codeberg’s CI (full output of mypy -v available), despite all versions used being exactly the same and the logs showing that the configuration is correctly picked. Here are the relevant lines from mypy -v:

LOG: Config File: /woodpecker/src/codeberg.org/slidge/slidge/pyproject.toml
[…]
LOG: Exclude: [‘tests’, ‘slidge.slixfix.*’]
[…]
LOG: Processing SCC singleton (slidge.slixfix.xep_0313.stanza) as inherently stale with stale deps (builtins datetime slixmpp.jid slixmpp.plugins slixmpp.plugins.xep_0082 slixmpp.stanza slixmpp.xmlstream typing)
slidge/slixfix/xep_0313/stanza.py:88: error: “ElementBase” has no attribute “get_fields” [attr-defined]
slidge/slixfix/xep_0313/stanza.py:313: error: Incompatible types in assignment (expression has type “datetime | str”, target has type “str”) [assignment]
slidge/slixfix/xep_0313/stanza.py:359: error: Incompatible types in assignment (expression has type “datetime | str”, target has type “str”) [assignment]

When running it locally, I cannot find any relevant difference in the logs (available here), except that no errors are detected in the excluded files, as wanted.

LOG: Config File: /home/nicoco/src/slidge/pyproject.toml
[…]
LOG: Exclude: [‘tests’, ‘slidge.slixfix.*’]
[…]
LOG: Processing SCC singleton (slidge.slixfix.xep_0313.stanza) as inherently stale with stale deps (builtins datetime slixmpp.jid slixmpp.plugins slixmpp.plugins.xep_0082 slixmpp.stanza slixmpp.xmlstream typing)
[no errors and moves on to the next file]

What is happening here?

This might be because mypy by default follows imports and type checks those modules, see the corresponding section in the mypy docs: Following Imports

You can switch to follow-imports = silent to ignore errors in modules that you did not explicitly check.

That worked, thanks!

However, I would really like to understand why this is needed in codeberg’s woodpecker CI and not when running mypy on my dev workstation?

That really depends on how you are running mypy on your dev workstation. If you are invoking mypy the same way and you don’t have a local-only mypy config there should really be no difference.

If you are relying on your editor’s mypy integration, it might be doing additional things behind the scenes, like filtering the violations to the file you’re currently looking at.

It’s also worth noting that globally enabling follow-imports = silent via the command line can be a bit problematic, you may be better off adding a module specific section to your config file for slidge.slixfix.*.

My workstation is running debian bookworm, and I launch uv run mypy from a terminal in the project’s root. With -v I can see that the only config being picked up is from my pyproject.toml.

In CI, I use a python3.11-bookworm container image and launch uv run mypy the same way. I really having trouble finding out what’s different between the two. I tried to diff the outputs of mypy -v locally and in CI, and the only difference I could find (besides /home/nicoco/src/slidge becoming /woodpecker/src/codeberg.org/slidge/slidge) was that errors were thrown (or not).

Now I am convinced that there is no magic involved and I am somehow missing something, but I cannot point it out.

Here are the relevant sections of my pyproject.toml in case what I am missing is obvious:

[tool.mypy]
check_untyped_defs = true
files = ["slidge", "superduper"]
# I tried with .*slidge.slixfix.*" and "slidge/slixfix"
# Both options produce the same effect: OK locally, KO in CI
exclude = ["tests", "slidge.slixfix.*"]

[[tool.mypy.overrides]]
module = [
    "thumbhash",
    "configargparse",
    "qrcode",
]
ignore_missing_imports = true

The different folder structure could be the culprit, it’s possible your files setting is incorrect locally or on the CI. exclude definitely does seem incorrect, since it’s supposed to be file paths not modules.

The other thing it could be is that you need to install your package into the virtual environment that’s executing mypy or setup your mypy_path to include those folders.

The folder structure is the same, it just does not “live” in the same prefix, which should not be an issue? The venv is set up by uv in $PROJECT_ROOT/.venv. Anyway, thanks for your help, I changed my pyproject to

[tool.mypy]
check_untyped_defs = true
files = ["slidge", "superduper"]
exclude = ["tests"]  # not excluded here

[[tool.mypy.overrides]]   # new section
module = "slidge.slixfix.*"
ignore_errors = true

and it seems to work fine. I wish I understood what was wrong with the previous syntax but one’s got to let go at some point. :wink: