gRPC/protobuf 个生成的 Python 个文件使用了不正确的导入语句

gRPC/protobuf generated Python files use incorrect import statements

我在 python 项目回购中包含一个 grpc 回购作为 src/grpc/protobuf 的子模块,具有以下示例结构:

protobuf/
  |
  |-data_structs/
  |   |-example_structA.proto
  |   |-example_structB.proto
  |   
  |-messages/
  |   |-example_messageA.proto
  |   |-example_messageB.proto
  |
  |-services/
      |-example_service.proto

使用简化的示例实现:

//example_structA.proto

syntax = "proto3"

message example_structA {
    int id = 1;
}
//example_structB.proto

syntax = "proto3"

import "data_structs/example_structA.proto";

message example_structB {
     int id = 1;
     repeated example_structA items = 2;
}
//example_messageA.proto

syntax = "proto3"

import "data_structs/example_structB.proto"

message example_messageA {
    string info = 1;
    example_structB data = 2;
}
//example_messageB.proto

syntax = "proto3"

message example_messageB {
    string status = 1;
}
//example_service.proto

syntax = "proto3"

import "messages/example_messageA.proto";
import "messages/example_messageB.proto";

service example_service {
    rpc SendMessage (example_messageA) returns (example_messageB) {}
}

我正在使用以下命令生成 python 代码,运行 来自 python 项目根目录:

find src/grpc/protobuf -name "*.proto" | xargs python -m grpc_tools.protoc =I=./src/grpc/protobuf --python_out=./src/grpc/generated --grpc_python_out=./src/grpc/generated

这会在 src/grpc/generated 目录中正确生成 .py 文件,并将它们放入模仿 proto repo 结构的子目录中。例如example_structB 的文件位于 src/grpc/generated/data_structs/example_structB_pb2.pysrc/grpc/generated/data_structs/example_structB_pb2_grpc.py 消息位于 src/grpc/generated/messages 目录,等等

这些生成的文件虽然生成了错误的导入。

example_structB_pb2.pyfrom data_structs import example_structA_pb2 这应该是 from . import example_structA_pb2 或者只是 import example_structA_pb2

example_messageA_pb2.pyfrom data_structs import example_structB_pb2这应该是from ..data_structs import example_structB_pb2

依此类推。导入消息的服务应该是 from ..messages import 而不是 from messages import

我在 java 或 c++ 中使用此 directory/proto 结构和生成没有任何问题。对于 python 生成的代码,有什么办法可以解决这个问题吗?

我通过将 src/grpc/generated 导出到我的 PYTHONPATH

解决了这个问题

export PYTHONPATH=${PYTHONPATH}:/path/to/project/root/src/grpc/generated

这不是错误,它正在按预期工作,pyi_generator.cc:168 添加从 --python_out 的路径作为 from 的参数并且它不检查它是否可访问通过根(如果你使用 python_out=./ 使入口点与输出路径相同,它应该在没有 export 的情况下工作) 并且它也不允许在路径 (importer.cc:341) 中使用 ..,因此您的解决方案是有效的,但我更愿意从 python 内部添加路径,如下所示:

# this line is somewhere before the import of the proto-stuff
sys.path.append(os.path.join(os.path.dirname(__file__), 'src', 'grpc', 'generated'))

基本示例(编辑 1)

如果你有这样的树,源文件夹包含确切的 sub-tree 就好像它在 ./ 下一样,最顶层的文件夹将在入口目录中,这意味着它将使用正确的导入进行编译,因为它的结构相同。

root
│  <your main>.py
│  proto_compile.py
└─ proto_src
   └─ proto_py
        │  base.proto
        └─ structs
            ├─ message
            │    type_blob.proto
            │    type_text.proto
            └─ utils
                 blob.proto

In python,导入是“in sys.path”查找,入口点在执行开始时添加到该列表,. 独立于该列表,即使该文件不在已知目录中,它也是相对于该文件的。然后,如果您有一个目录不能遵循“从根分支”导入样式,您只需将该路径添加到 sys.path(但如果您从项目内部添加路径,这可能会导致循环导入问题)。

这些是要测试的文件:

# proto_compile.py

import subprocess
import os

def get_proto_files(path):
    proto_files = []
    for root, dirs, files in os.walk(path):
        for name in files:
            if name.rsplit('.')[-1] == 'proto':
                proto_files.append(
                    # path is relative !
                    os.path.join(root, name)
                )
    return proto_files

def run_protoc(exe_path, src, dst):
    popen = subprocess.Popen(
        [
            exe_path,
            f'--proto_path={src}',
            f'--python_out={dst}'
        ] + get_proto_files(src),
        # could olso use os.getcwd()
        cwd=os.path.dirname(__file__),
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT
    )
    while popen.returncode is None:
        stdout, stderr = popen.communicate()
        if stdout is not None:
            print(stdout.decode())
        if stderr is not None:
            print(stderr.decode())

if __name__ == '__main__':
    run_protoc(
        './protobuff/bin/protoc.exe',
        './proto_src',
        './'
    )
// base.proto

syntax = "proto3";

import "proto_py/structs/message/type_blob.proto";
import "proto_py/structs/message/type_text.proto";

message user_message {
    int32 id = 1;
    oneof content {
        content_text  text  = 2;
        content_image image = 3;
    }
}
// type_text.proto

syntax = "proto3";

message content_text {
    int32 id = 1;
}
// type_blob.proto

syntax = "proto3";

import "proto_py/structs/utils/blob.proto";

message content_image {
    int32 id = 1;
    blob_check check = 2;
}
// blob.proto

syntax = "proto3";

message blob_check {
    string sha256 = 1;
}