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.py
,src/grpc/generated/data_structs/example_structB_pb2_grpc.py
消息位于 src/grpc/generated/messages
目录,等等
这些生成的文件虽然生成了错误的导入。
example_structB_pb2.py
有 from data_structs import example_structA_pb2
这应该是 from . import example_structA_pb2
或者只是 import example_structA_pb2
example_messageA_pb2.py
有from 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;
}
我在 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.py
,src/grpc/generated/data_structs/example_structB_pb2_grpc.py
消息位于 src/grpc/generated/messages
目录,等等
这些生成的文件虽然生成了错误的导入。
example_structB_pb2.py
有 from data_structs import example_structA_pb2
这应该是 from . import example_structA_pb2
或者只是 import example_structA_pb2
example_messageA_pb2.py
有from 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;
}