bazel-contrib / bazel-contrib/rules_python

[gazelle] Support relative python imports

Đang mở
#2,203 5 bình luận 3 reaction 0 người được giao Xem trên GitHub
gazelle help wanted
Ngôn ngữ chính
Starlark
Star
688
Fork
721
Merge trung bình
15 giờ 7 phút
Pull request đã merge (30 ngày)
76

Mô tả

# 🚀 feature request

### Relevant Rules

+ gazelle

### Description

Sometimes a python code base uses relative imports:

```python
from .baz import Baz
from .same_package_other_module import cat_in_the_hat
from ..one_level_up import thing1
from ...two_levels_up import thing2
from .. import moduleA
```

While I don't necessarily agree with this style, it's something that Python supports (see [PEP 328](https://peps.python.org/pep-0328/#guido-s-decision)) and thus I believe that Gazelle should support it too.

Right now, Gazelle simply ignores these imports. Here's an example:

```
example/
__init__.py
a.py
b.py
BUILD.bazel
```

```python
# example/a.py
from . import b
del b
```

```python
# example/b.py
pass
```

```starlark
# example/BUILD.bazel
# as generated by Gazelle with "file" generation mode
load("@rules_python//python:defs.bzl", "py_library")

py_library(
name = "a",
srcs = ["a.py"],
visibility = ["//:__subpackages__"],
)

py_library(
name = "b",
srcs = ["b.py"],
visibility = ["//:__subpackages__"],
)
```

If we change a.py to use an absolute import:

```python
# example/a.py
from example import b
del b
```

Gazelle will add the dep:

```starlark
# example/BUILD.bazel
# as generated by Gazelle with "file" generation mode
load("@rules_python//python:defs.bzl", "py_library")

py_library(
name = "a",
srcs = ["a.py"],
visibility = ["//:__subpackages__"],
deps = [":b"],
)

...
```

### Describe the solution you'd like

I'm somewhat familiar with the Gazelle code base. I believe it will be pretty easy (for the most part - see **Issues** below) below) to generate the full target path for the current file being processed, and then adjust the target based on how many dots `.` are in the relative import.

Because of the **Issues**, I'd imagine that this would be an experimental, opt-in feature for a while. It would be guarded by a directive:

```
# gazelle:experimental_allow_relative_imports true
```

Here are some examples of the logic, assuming a slightly more complex dir structure:

```
toplevel/
__init__.py
MODULE.bazel
BUILD.bazel
foo/
__init__.py
BUILD.bazel/
bar.py
baz.py
example/
__init__.py
a.py
b.py
BUILD.bazel
```

```
# relative import of another module "b.py" in same package "example"
# from . import b
read "from . import b" in file "example/a.py"
what is our current target? "//example:a"
We have 1 dot, so trim off 1 component ":a". target_stem = "//example"
Add the import "b". dep target name = "//example:b"
```

```
# relative import of a different packages's module
# from ..foo import bar
read "from ..foo import bar" in file "example/a.py"
current target? "//example:a"
We have 2 dots, so trim off 2 components "example:a". target_stem = "//"
Add the name from the dots and from the import. dep_target_name = "//foo:bar
```

```
# relative import of the parent packages's module "baz"
# from .. import baz
read "from .. import baz" in file "example/a.py"
current target? "//example:a"
We have 2 dots, so trim off 2 components "example:a". target_stem = "//"
There's no name after the dots, so the thing we're importing is a module (probably).
dep_target_name = "//:baz"
```

The above examples assume file-level generation. More thought will be needed to support package-level generation.

### Issues

It's difficult to know if `from ..foo import bar` should be `//:foo` or `//foo:bar`. Is `bar` a class/function? Or is `bar` another module? In the example above, it's a python module, but that's not always the case.

In the `from .. import baz` case, it's possible that `baz` is an identifier defined in the parent package's `__init__.py`, and thus the dep target would be `//:__init__`.

### Describe alternatives you've considered

I tried convincing the developers to use absolute imports, but they just weren't having it :rofl:

So I also tried using the "package" generation mode, but the issue was that unit test files also used relative imports and would not include the dep.

The current workaround is to add `# gazelle:include_dep //example:b` annotations, but those are prone to diverging from the actual code and can be tedious to write for large projects.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Bắt đầu bằng cách theo dõi cách Gazelle hiện đang xử lý các import Python tuyệt đối và so sánh các chế độ sinh ở cấp tệp và cấp gói của nó. Xác định cách mỗi dạng import tương đối ánh xạ tới một dependency, thêm hỗ trợ opt-in phía sau directive được đề xuất experimental_allow_relative_imports, và bao quát các ví dụ cũng như các trường hợp không rõ ràng bằng các test.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
build-system, tooling
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.