Skip to content

Folder with module name detected as source leads to false I001 error #10519

Description

@carschno

This issue affects the expected sorting of imports in Python and hence (incorrectly) triggers a I001 error.

When there is a folder that has the same name as a module, it is (possibly incorrectly) identified as SourceMatch. Ruff then categorizes the module as Known(FirstParty) and adapts the expected sorting accordingly.

This happens commonly, but not exclusively, when using the wandb library because it creates a wandb folder, as discovered in ChartBoost/ruff-action#20.

To reproduce the issue, create a Python file with these imports (I called it test.py):

import csv
import logging
import random
import sys
from typing import Any, Optional, TextIO

import torch
import torch.nn as nn
from torch import optim
from torcheval.metrics import (
    Metric,
    MulticlassAccuracy,
    MulticlassF1Score,
    MulticlassPrecision,
    MulticlassRecall,
)
from tqdm import tqdm

import wandb

In the initial scenario, there is a wandb directory:

% ls -d wandb/
wandb/

Running Ruff to check the import sorting:

% poetry run ruff check -v --select=I001 test.py                                            
[2024-03-22][08:57:28][ruff::resolve][DEBUG] Using configuration file (via parent) at: /Users/carstenschnober/LAHTeR/workspace/document-segmentation/pyproject.toml
[2024-03-22][08:57:28][ruff::commands::check][DEBUG] Identified files to lint in: 2.013375ms
[2024-03-22][08:57:28][ruff::diagnostics][DEBUG] Checking: /Users/carstenschnober/LAHTeR/workspace/document-segmentation/test.py
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch.nn' as Known(ThirdParty) (NoMatch)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'csv' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'random' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'sys' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'logging' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch' as Known(ThirdParty) (NoMatch)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'wandb' as Known(FirstParty) (SourceMatch("/Users/carstenschnober/LAHTeR/workspace/document-segmentation"))
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch' as Known(ThirdParty) (NoMatch)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torcheval.metrics' as Known(ThirdParty) (NoMatch)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'typing' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][08:57:28][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'tqdm' as Known(ThirdParty) (NoMatch)
[2024-03-22][08:57:28][ruff::commands::check][DEBUG] Checked 1 files in: 765.709µs
All checks passed!

The checks pass, wandb has been categorized as Known(FirstParty)

Now remove the wandb directory:

% mv wandb wandb.bak
% ls -d wandb/
ls: wandb/: No such file or directory

Running the same Ruff check triggers a I001 error on the same file, categorizing wandb as Known(ThirdParty); the module categorization is cached, so I remove the .ruff_cache directory first to reproduce the error:

% rm -r .ruff_cache                             
% poetry run ruff check -v --select=I001 test.py
[2024-03-22][09:06:42][ruff::resolve][DEBUG] Using configuration file (via parent) at: /Users/carstenschnober/LAHTeR/workspace/document-segmentation/pyproject.toml
[2024-03-22][09:06:42][ruff::commands::check][DEBUG] Identified files to lint in: 1.959083ms
[2024-03-22][09:06:42][ruff::diagnostics][DEBUG] Checking: /Users/carstenschnober/LAHTeR/workspace/document-segmentation/test.py
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch.nn' as Known(ThirdParty) (NoMatch)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'csv' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'random' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'sys' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'logging' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch' as Known(ThirdParty) (NoMatch)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'wandb' as Known(FirstParty) (SourceMatch("/Users/carstenschnober/LAHTeR/workspace/document-segmentation"))
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torch' as Known(ThirdParty) (NoMatch)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'torcheval.metrics' as Known(ThirdParty) (NoMatch)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'typing' as Known(StandardLibrary) (KnownStandardLibrary)
[2024-03-22][09:06:42][ruff_linter::rules::isort::categorize][DEBUG] Categorized 'tqdm' as Known(ThirdParty) (NoMatch)
[2024-03-22][09:06:42][ruff::commands::check][DEBUG] Checked 1 files in: 1.004834ms
test.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 1 error.
[*] 1 fixable with the `--fix` option.

This is now the expected sorting that is generated when calling ruff --fix call above:

import csv
import logging
import random
import sys
from typing import Any, Optional, TextIO

import torch
import torch.nn as nn
import wandb
from torch import optim
from torcheval.metrics import (
    Metric,
    MulticlassAccuracy,
    MulticlassF1Score,
    MulticlassPrecision,
    MulticlassRecall,
)
from tqdm import tqdm

This configuration option fixes the issue properly (see ChartBoost/ruff-action#20 (comment)):

[tool.ruff.lint.isort]
known-third-party = ["wandb"]

However, it is difficult for users to identify the issue and fix the configuration accordingly. I think a better solution would be to have a more robust source directory detection.
A heuristics like checking for __init__.py or generally the presence of *.py files as a condition might be a solid starting point for Python.

Activity

  1. changed the title [-]Folder with module name detected as source[/-] [+]Folder with module name detected as source leads to false `I001` error[/+] on Mar 24, 2024
  2. charliermarsh commented on Mar 24, 2024

    @charliermarsh
    Member

    We could consider requiring an __init__.py but it would be a breaking change, I think.

  3. carschno commented on Mar 25, 2024

    @carschno
    Author

    We could consider requiring an __init__.py but it would be a breaking change, I think.

    I see how a folder that is now correctly categorized as a source folder might not have an __init__.py in some cases, so this requirement could perhaps be something for the next major release indeed.

    If a source folder is required to contain any *.py file, however, I cannot see how it would break current behaviour -- except for cases in which the sorting has now been incorrectly adapted to identified source folders.
    So perhaps requiring any .py file could be an intermediate fix, whereas the next major release requires an __init__.py?

    If somebody could point me to the relevant source code, I could try and work on a PR.

  4. bkad commented on Apr 4, 2024

    @bkad

    Running the same Ruff check triggers a I001 error on the same file, categorizing wandb as Known(ThirdParty); the module categorization is cached, so I remove the .ruff_cache directory first to reproduce the error

    Shouldn't the removal of the wandb directory be enough to invalidate the module categorization cache? The fact that you need to manually remove the cache to get the correct results also seems wrong.

  5. charliermarsh commented on Apr 4, 2024

    @charliermarsh
    Member

    Diagnostics are cached on a per-file basis, where the "file" is the file in which the diagnostic is present. Changing other files on the filesystem doesn't invalidate the cache.

    Changing the settings will also invalidate the cache. So if you add wandb as a known-third-party module (which is the suggested change), it will also reflect that change on re-run without clearing the cache mnanually.

  6. bkad commented on Apr 4, 2024

    @bkad

    Say you were using a third party package in your requirements, but decided later to vendor it to make changes, turning it into a first party package. Then unless caches were manually cleaned you would get incorrect results from the isort check. Even worse, they could be inconsistent with CI checks, which can cause a lot of confusion. We ran into an issue like this today, and had to turn off caching as a result.

  7. carschno commented on Apr 4, 2024

    @carschno
    Author

    Say you were using a third party package in your requirements, but decided later to vendor it to make changes, turning it into a first party package. Then unless caches were manually cleaned you would get incorrect results from the isort check. Even worse, they could be inconsistent with CI checks, which can cause a lot of confusion. We ran into an issue like this today, and had to turn off caching as a result.

    Honestly, I think this should be a separate issue about caching. It is only related in the sense that caching must somehow also handle the detection of third party folders. But if you propose improvements in the caching logic, I think it would make sense to do this separately from this issue (detection of source folders).

  8. MichaReiser commented on Apr 4, 2024

    @MichaReiser
    Member

    We're looking into changing how we cache data as part of our multifile analysis work. It will allow us to invalidate caches based on dependencies.

  9. angerhang commented on Jan 28, 2025

    @angerhang

    I would echo the same concern, it is really difficult for the end-users to pick up the error.

    I had the same issue when using wandb. I ensured my local and remote CI tests have consistent environments but the differences still persist until I realised that a local folder name wandb is created.

    Better error message is needed if we can't have a quick fix easily.

  10. MichaReiser commented on Feb 26, 2025

    @MichaReiser
    Member

    We should prioritize resolving this issue. There is a known workaround, but it comes up regularly, and reproducing it is a ton of work for users (and us).

  11. MichaReiser commented on Apr 23, 2025

    @MichaReiser
    Member

    @dylwil3 do you have any ideas on how we could improve the wandb case described here? What I understand is that your most recent change won't help with this because it will still consider wandb as a possible namespace package (which is correct because Python would do the same)

  12. carschno commented on Apr 24, 2025

    @carschno
    Author

    A slightly naive workaround would be to provide a default list of known third parties, including wandb for this specific issue. I see two downsides of such a list:

    1. Adding maintenance effort
    2. Not necessarily expected behaviour for new users

    However, I don't see a generalizable solution, so perhaps such a specific list would be a starting point until it grows to an unmaintainable scale.

  13. 8 remaining items

  14. carschno commented on Aug 29, 2025

    @carschno
    Author

    A short-term workaround could be to add wandb to the known_third_party defaults, as this appears to be the cause of the confusing behavior for most users. Wdyt @dylwil3 ?

    That was my suggestion above. While the downside (maintenance) remains, a more generic solution does not seem available.

  15. jbcoe commented on Aug 29, 2025

    @jbcoe

    I wonder if skipping folders that are ignored by git (optionally) might be a nice generic fix for avoiding temporary files?

  16. dylwil3 commented on Sep 9, 2025

    @dylwil3
    Collaborator

    A short-term workaround could be to add wandb to the known_third_party defaults, as this appears to be the cause of the confusing behavior for most users. Wdyt @dylwil3 ?

    Yeah I think we should do this - I'll make a PR!

  17. vadimkantorov commented on Nov 28, 2025

    @vadimkantorov

    IMO it can easily happen with some other python packages, and it will be equally confusing to debug. I suggest adding an option to somehow adjust how a package is treated local or not. And maybe somehow make isort variant which will not be dependent on local-ness of the module (and existence of any name-matching dirs) at all.

  18. charliermarsh commented on Nov 28, 2025

    @charliermarsh
    Member

    If you want to "not depend on local-ness", you can just remove the first-party and local sections -- this already exists:

    [lint.isort]
    section-order = [
        "future",
        "standard-library",
        "third-party",
    ]

    Then all first-party and third-party imports will be grouped together.

  19. MichaReiser commented on Nov 28, 2025

    @MichaReiser
    Member

    You can use no-sections if you don't want isort to distinguish between first and third party packages

  20. vadimkantorov commented on Nov 28, 2025

    @vadimkantorov

    This should help, thanks! Would be nice to maybe make a better default specifically for local dirs: e.g. when package exists both as third-party and as a local dir - do not propose import order fixes, and do not propose moving it out of the third-party section.

    It should be relatively less common these days to intentionally actually have a local dir package to shadow a global package (given that these days it's so simple to have venvs, so it's easy to not put unneded global packages into the env in the first place). So maybe if a package exists as both third-party and local - actually consider it third-party.

    And also, somehow python itself doesn't get confused and still loads the global wandb, and not breaks saying that the local folder wandb doesn't contain anything loadable. But maybe it requires more sophisticated resolution logic.

  21. dylwil3 commented on Nov 29, 2025

    @dylwil3
    Collaborator

    when package exists both as third-party and as a local dir - do not propose import order fixes, and do not propose moving it out of the third-party section.

    Ruff does not really have a way of knowing what third-party means beyond "not first party", essentially. That is: Ruff does not know what packages you have declared in your pyproject.toml, nor does it look for a virtual environment or anything like that.

    One of the big ironies of import sorting is that it feels like it should belong to the formatter, but when you look at the information it would need to understand in order to match user's expectations for first/third-party, it really needs to belong to something closer to a type checker or a package manager.

    If only some organization built a formatter, package manager, and a type checker 😄

    See #21520 for a possible way forward on this.

  22. vadimkantorov commented on Nov 29, 2025

    @vadimkantorov

    Maybe making a heuristic, that a directory without __init__.py or without any python files considered not-first-party? How does Python itself decide that it should skip the local wandb dir?

    I think the main problem here is coming up with a default setting which does not depend on existence of local folders and on CI / no-CI env... And it's really a painful thing to debug :(

  23. dylwil3 commented on Nov 29, 2025

    @dylwil3
    Collaborator

    Maybe making a heuristic, that a directory without init.py or without any python files considered not-first-party? How does Python itself decide that it should skip the local wandb dir?

    A directory without an __init__.py can still be a namespace package. Checking recursively for Python files would be more likely to be a correct heuristic but could be more expensive to do.

    What Python actually does will necessarily involve finding the virtual environment: As described in the PEP, Python will look first at your local directory structure and see the directory foo with no Python files in it, say. It will record that it found this as a "potential" namespace package. But then it will search your virtual environment. If it finds an honest Python package foo there, then that is the resolved module. If it doesn't, then it will resolve to the local directory foo as an implicit namespace package.

    So we couldn't mimic that exact behavior unless we found your virtual environment. But having declared dependencies as in the uv metadata idea should give the same behavior (unless someone has an environment that doesn't match the declared one).

  24. danra commented on Jun 6, 2026

    @danra

    Bump, also just ran into this with wandb, was stumped for quite a bit

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions