Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ END_UNRELEASED_TEMPLATE
dep is not added to the {obj}`py_test` target.
* (gazelle) New directive `gazelle:python_generate_proto`; when `true`,
Gazelle generates `py_proto_library` rules for `proto_library`. `false` by default.
* (gazelle) Added directive `# gazelle:python_resolve_sibling_imports` (default
`enabled` for backwards-compatibility). When enabled, gazelle will allow absolute
imports to be resolved to sibling modules (Python 2's behavior without
`absolute_import`).

{#v0-0-0-removed}
### Removed
Expand Down
2 changes: 2 additions & 0 deletions gazelle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ Python-specific directives are as follows:
| Controls whether to generate a separate `pyi_deps` attribute for type-checking dependencies or merge them into the regular `deps` attribute. When `false` (default), type-checking dependencies are merged into `deps` for backward compatibility. When `true`, generates separate `pyi_deps`. Imports in blocks with the format `if typing.TYPE_CHECKING:`/`if TYPE_CHECKING:` and type-only stub packages (eg. boto3-stubs) are recognized as type-checking dependencies. |
| [`# gazelle:python_generate_proto`](#directive-python_generate_proto) | `false` |
| Controls whether to generate a `py_proto_library` for each `proto_library` in the package. By default we load this rule from the `@protobuf` repository; use `gazelle:map_kind` if you need to load this from somewhere else. |
| `# gazelle:python_resolve_sibling_imports` | `true` |
| Allows absolute imports to be resolved to sibling modules (Python 2's behavior without `absolute_import`). |

#### Directive: `python_root`:

Expand Down
7 changes: 7 additions & 0 deletions gazelle/python/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (py *Configurer) KnownDirectives() []string {
pythonconfig.GeneratePyiDeps,
pythonconfig.ExperimentalAllowRelativeImports,
pythonconfig.GenerateProto,
pythonconfig.PythonResolveSiblingImports,
}
}

Expand Down Expand Up @@ -244,6 +245,12 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
log.Fatal(err)
}
config.SetGenerateProto(v)
case pythonconfig.PythonResolveSiblingImports:
v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
if err != nil {
log.Fatal(err)
}
config.SetResolveSiblingImports(v)
}
}

Expand Down
16 changes: 8 additions & 8 deletions gazelle/python/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
fqTarget.String(), actualPyBinaryKind, err)
continue
}
pyBinary := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyBinary := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addVisibility(visibility).
addSrc(filename).
addModuleDependencies(mainModules[filename]).
Expand Down Expand Up @@ -301,7 +301,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addVisibility(visibility).
addSrcs(srcs).
addModuleDependencies(allDeps).
Expand Down Expand Up @@ -354,7 +354,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
setMain(pyBinaryEntrypointFilename).
addVisibility(visibility).
addSrc(pyBinaryEntrypointFilename).
Expand Down Expand Up @@ -387,7 +387,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors.Add(err)
}

conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames).
conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addSrc(conftestFilename).
addModuleDependencies(deps).
addResolvedDependencies(annotations.includeDeps).
Expand Down Expand Up @@ -419,7 +419,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
fqTarget.String(), actualPyTestKind, err, pythonconfig.TestNamingConvention)
collisionErrors.Add(err)
}
return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames).
return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames, cfg.ResolveSiblingImports()).
addSrcs(srcs).
addModuleDependencies(deps).
addResolvedDependencies(annotations.includeDeps).
Expand Down Expand Up @@ -476,7 +476,7 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes

for _, pyTestTarget := range pyTestTargets {
if conftest != nil {
conftestModule := Module{Name: strings.TrimSuffix(conftestFilename, ".py")}
conftestModule := Module{Name: importSpecFromSrc(pythonProjectRoot, args.Rel, conftestFilename).Imp}
if pyTestTarget.annotations.includePytestConftest == nil {
// unset; default behavior
pyTestTarget.addModuleDependency(conftestModule)
Expand Down Expand Up @@ -594,7 +594,7 @@ func generateProtoLibraries(args language.GenerateArgs, pythonProjectRoot string
// Generate a py_proto_library for each proto_library.
for _, protoRuleName := range protoRuleNames {
pyProtoLibraryName := strings.TrimSuffix(protoRuleName, "_proto") + "_py_pb2"
pyProtoLibrary := newTargetBuilder(pyProtoLibraryKind, pyProtoLibraryName, pythonProjectRoot, args.Rel, &emptySiblings).
pyProtoLibrary := newTargetBuilder(pyProtoLibraryKind, pyProtoLibraryName, pythonProjectRoot, args.Rel, &emptySiblings, false).
addVisibility(visibility).
addResolvedDependency(":" + protoRuleName).
generateImportsAttribute().build()
Expand All @@ -611,7 +611,7 @@ func generateProtoLibraries(args language.GenerateArgs, pythonProjectRoot string
continue
}

emptyRule := newTargetBuilder(pyProtoLibraryKind, ruleName, pythonProjectRoot, args.Rel, &emptySiblings).build()
emptyRule := newTargetBuilder(pyProtoLibraryKind, ruleName, pythonProjectRoot, args.Rel, &emptySiblings, false).build()
res.Empty = append(res.Empty, emptyRule)
}

Expand Down
53 changes: 27 additions & 26 deletions gazelle/python/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,36 @@ import (

// targetBuilder builds targets to be generated by Gazelle.
type targetBuilder struct {
kind string
name string
pythonProjectRoot string
bzlPackage string
srcs *treeset.Set
siblingSrcs *treeset.Set
deps *treeset.Set
resolvedDeps *treeset.Set
visibility *treeset.Set
main *string
imports []string
testonly bool
annotations *annotations
kind string
name string
pythonProjectRoot string
bzlPackage string
srcs *treeset.Set
siblingSrcs *treeset.Set
deps *treeset.Set
resolvedDeps *treeset.Set
visibility *treeset.Set
main *string
imports []string
testonly bool
annotations *annotations
resolveSiblingImports bool
}

// newTargetBuilder constructs a new targetBuilder.
func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set) *targetBuilder {
func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set, resolveSiblingImports bool) *targetBuilder {
return &targetBuilder{
kind: kind,
name: name,
pythonProjectRoot: pythonProjectRoot,
bzlPackage: bzlPackage,
srcs: treeset.NewWith(godsutils.StringComparator),
siblingSrcs: siblingSrcs,
deps: treeset.NewWith(moduleComparator),
resolvedDeps: treeset.NewWith(godsutils.StringComparator),
visibility: treeset.NewWith(godsutils.StringComparator),
annotations: new(annotations),
kind: kind,
name: name,
pythonProjectRoot: pythonProjectRoot,
bzlPackage: bzlPackage,
srcs: treeset.NewWith(godsutils.StringComparator),
siblingSrcs: siblingSrcs,
deps: treeset.NewWith(moduleComparator),
resolvedDeps: treeset.NewWith(godsutils.StringComparator),
visibility: treeset.NewWith(godsutils.StringComparator),
annotations: new(annotations),
resolveSiblingImports: resolveSiblingImports,
}
}

Expand All @@ -77,7 +79,7 @@ func (t *targetBuilder) addModuleDependency(dep Module) *targetBuilder {
if dep.From != "" {
fileName = dep.From + ".py"
}
if t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) {
if t.resolveSiblingImports && t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) {
// importing another module from the same package, converting to absolute imports to make
// dependency resolution easier
dep.Name = importSpecFromSrc(t.pythonProjectRoot, t.bzlPackage, fileName).Imp
Expand Down Expand Up @@ -138,7 +140,6 @@ func (t *targetBuilder) setAnnotations(val annotations) *targetBuilder {
return t
}


// generateImportsAttribute generates the imports attribute.
// These are a list of import directories to be added to the PYTHONPATH. In our
// case, the value we add is on Bazel sub-packages to be able to perform imports
Expand Down
1 change: 1 addition & 0 deletions gazelle/python/testdata/annotation_include_dep/BUILD.in
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# gazelle:python_generation_mode file
# gazelle:python_resolve_sibling_imports true
1 change: 1 addition & 0 deletions gazelle/python/testdata/annotation_include_dep/BUILD.out
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# gazelle:python_generation_mode file
# gazelle:python_resolve_sibling_imports true

py_library(
name = "__init__",
Expand Down
1 change: 1 addition & 0 deletions gazelle/python/testdata/naming_convention/BUILD.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# gazelle:python_library_naming_convention my_$package_name$_library
# gazelle:python_binary_naming_convention my_$package_name$_binary
# gazelle:python_test_naming_convention my_$package_name$_test
# gazelle:python_resolve_sibling_imports true
1 change: 1 addition & 0 deletions gazelle/python/testdata/naming_convention/BUILD.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
# gazelle:python_library_naming_convention my_$package_name$_library
# gazelle:python_binary_naming_convention my_$package_name$_binary
# gazelle:python_test_naming_convention my_$package_name$_test
# gazelle:python_resolve_sibling_imports true

py_library(
name = "my_naming_convention_library",
Expand Down
12 changes: 11 additions & 1 deletion gazelle/python/testdata/sibling_imports/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Sibling imports

This test case asserts that imports from sibling modules are resolved correctly. It covers 3 different types of imports in `pkg/unit_test.py`
This test case asserts that imports from sibling modules are resolved correctly
when the `python_resolve_sibling_imports` directive is enabled (default
behavior). It covers 3 different types of imports in `pkg/unit_test.py`:

- `import a` - resolves to the sibling `a.py` in the same package
- `import test_util` - resolves to the sibling `test_util.py` in the same
package
- `from b import run` - resolves to the sibling `b.py` in the same package

When sibling imports are enabled, we allow them to be satisfied by sibling
modules (ie. modules in the same package).
1 change: 1 addition & 0 deletions gazelle/python/testdata/sibling_imports/pkg/BUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_resolve_sibling_imports true
3 changes: 2 additions & 1 deletion gazelle/python/testdata/sibling_imports/pkg/BUILD.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports true

py_library(
name = "pkg",
srcs = [
Expand All @@ -23,4 +25,3 @@ py_test(
":test_util",
],
)

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_resolve_sibling_imports false
17 changes: 17 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports false

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

py_test(
name = "test_util",
srcs = ["test_util.py"],
)
17 changes: 17 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sibling imports disabled

This test case asserts that imports from sibling modules are NOT resolved as
absolute imports when the `python_resolve_sibling_imports` directive is
disabled. It covers 3 different types of imports in `pkg/unit_test.py`:

- `import a` - resolves to the root-level `a.py` instead of the sibling
`pkg/a.py`
- `import test_util` - resolves to the root-level `test_util.py` instead of
the sibling `pkg/test_util.py`
- `from b import run` - resolves to the root-level `b.py` instead of the
sibling `pkg/b.py`

When sibling imports are disabled with
`# gazelle:python_resolve_sibling_imports false`, the imports remain as-is
and follow standard Python resolution rules where absolute imports can't refer
to sibling modules.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
1 change: 1 addition & 0 deletions gazelle/python/testdata/sibling_imports_disabled/a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Root level a.py file for testing disabled sibling imports
3 changes: 3 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Root level b.py file for testing disabled sibling imports
def run():
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gazelle:python_resolve_sibling_imports false
27 changes: 27 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/pkg/BUILD.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports false

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

py_test(
name = "test_util",
srcs = ["test_util.py"],
)

py_test(
name = "unit_test",
srcs = ["unit_test.py"],
deps = [
"//:sibling_imports_disabled",
"//:test_util",
],
)
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions gazelle/python/testdata/sibling_imports_disabled/pkg/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def run():
pass
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import a
import test_util
from b import run
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Root level test_util.py file for testing disabled sibling imports
2 changes: 2 additions & 0 deletions gazelle/python/testdata/simple_test_with_conftest/BUILD.in
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
load("@rules_python//python:defs.bzl", "py_library")

# gazelle:python_resolve_sibling_imports true
2 changes: 2 additions & 0 deletions gazelle/python/testdata/simple_test_with_conftest/BUILD.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports true

py_library(
name = "simple_test_with_conftest",
srcs = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load("@rules_python//python:defs.bzl", "py_library")

# gazelle:python_resolve_sibling_imports false
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
load("@rules_python//python:defs.bzl", "py_library", "py_test")

# gazelle:python_resolve_sibling_imports false

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

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

py_test(
name = "simple_test_with_conftest_sibling_imports_disabled_test",
srcs = ["__test__.py"],
main = "__test__.py",
deps = [
":conftest",
":simple_test_with_conftest_sibling_imports_disabled",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Simple test with conftest.py (sibling imports disable)

This test case asserts that a simple `py_test` is generated as expected when a
`conftest.py` is present with sibling imports disabled.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This is a Bazel workspace for the Gazelle test data.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from foo import foo

_ = foo
Loading