Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature-request] Require first-party imports to target highest exporting non-ancestor module #1107

Open
smackesey opened this issue Dec 6, 2022 · 3 comments
Labels
needs-decision Awaiting a decision from a maintainer rule Implementing or modifying a lint rule

Comments

@smackesey
Copy link

smackesey commented Dec 6, 2022

A symbol can be defined in one module and re-exported from several others. When importing a symbol from an external typed library, the convention is to import from as close to the root as possible. This is how popular auto-import resolution tools work (see my very similar post in this pylance discussion:

If the same name is exported from multiple modules, the auto-import logic in pyright and pylance prefer the shortest module path. Only the shortest path is listed in the completion list, and the longer paths are de-duped.

In large projects, sometimes you have big submodules that you want to provide an "internal public" interface for-- e.g. if you have a large submodule project.foo, you might want project-internal code to import only from project.foo itself, rather than project.foo.* submodules.

I'd like to suggest that ruff offer a rule that enforces this by requiring internal imports resolve to the shortest module that exposes a symbol. So:

### project/foo/__init__.py
# redundant alias is necessary to expose `BAR` as public for `project.foo`
from .bar import BAR as BAR

### project/foo/bar.py
BAR = "BAR"

### project/baz.py
from project.foo.bar import BAR   # ERROR: should import from project.foo instead
@smackesey smackesey changed the title [feature-request] Require first-party imports to target highest exporting sibling-or-lower exporting module [feature-request] Require first-party imports to target highest exporting non-ancestor module Dec 6, 2022
@charliermarsh
Copy link
Member

Yeah this makes sense. It would also force us to support cross-module checks (since we'd need to build a mapping from module to exported symbols), which would be a good thing.

@charliermarsh charliermarsh added the rule Implementing or modifying a lint rule label Dec 9, 2022
@charliermarsh charliermarsh added the needs-decision Awaiting a decision from a maintainer label Jul 10, 2023
@chbndrhnns
Copy link

I sometimes see modules prefixed with an underscore being treated as private. Here is an example how this could work:

# ./client.py
from .myp._private import Member
from .myp._private import OtherMember

assert Member

# ./myp/__init__.py
__all__ = ["Member"]

from ._private import Member

# ./myp/_private.py
class Member: ...

class OtherMember: ...

Ruff could turn this into:

# ./client.py
from .myp import Member
from .myp._private import OtherMember

assert Member

# ./myp/__init__.py
__all__ = ["Member"]

from ._private import Member

# ./myp/_private.py
class Member: ...

class OtherMember: ...

@smackesey
Copy link
Author

Just wondering whether this is now easily doable with all the changes in the last year to ruff. Would be a nice QOL improvement for us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-decision Awaiting a decision from a maintainer rule Implementing or modifying a lint rule
Projects
None yet
Development

No branches or pull requests

3 participants