Does protobuf support relative imports for python?
I have been unsuccessful in creating a protobuf build script that supports this. When generating python-classes from my .proto-files, I am only able to import the python-modules if I launch python from the same folder as where the generated .py-files were created.
I've constructed the following MVP. Ideally, I would like a structure where the generated python code it placed in a separate folder (e.g. ./generated
) that I can then move into other projects. I've posted the approaches that I have gotten to work, but I am hoping someone more experienced will be able to point me towards a better solution.
General info:
- Python 3.6.8
- protobuf 3.11.3
Folder structure:
.
|--- schemas
|---- main_class.proto
|---- sub
|----sub_class.proto
|--- generated
Attempt 1: Relative imports
main_class.proto:
syntax = "proto3";
import public "sub/sub_class.proto";
message MainClass {
repeated SubClass subclass = 1;
}
sub_class.proto:
syntax = "proto3";
message LogMessage {
enum Status {
STATUS_ERROR = 0;
STATUS_OK = 1;
}
Status status = 1;
string timestamp = 2;
}
message SubClass {
string name = 1;
repeated LogMessage log = 2;
}
Protoc command:
From the root folder:
protoc -I=schemas --python_out=generated main_class.proto sub/sub_class.proto
This puts the python-files in the ./generated
folder.
What works and what doesn't
Using the approach above, I am able to launch python in the folder ./generated
and import using
import main_class_pb2 as MC_proto
.
However, when I launch python from the .
root folder (or any other folder for that matter), importing using
import generated.main_class_pb2 as MC_proto
yields the error ModuleNotFoundError: No module named 'sub'
. Based on this post, I manually modified the generated main_class_pb2.py
-file as follows
# Original
# from sub import sub_class_pb2 as sub_dot_sub__class__pb2
# from sub.sub_class_pb2 import *
# Fix
from .sub import sub_class_pb2 as sub_dot_sub__class__pb2
from .sub.sub_class_pb2 import *
By adding the .
in at the beginning of the import statement, I am now able to import the module from the root folder using import generated.main_class_pb2 as MC_proto
. However, it is very impractical having to edit the generated files manually every time, so I don't like this approach.
Attempt 2: Absolute imports
My second approach was to try absolute imports. If I knew where my project root folder would be, I could then move the .proto-files to where I wanted the python-classes to be and generate them there. For this example, I used the same folder structure as before, but without the ./generated
-folder. I also had to change the root folder for the protoc command, which required me to modify the import statement in the main_class.proto
file, as follows:
main_class.proto:
syntax = "proto3";
// Old
//import public "sub/sub_class.proto";
// New
import public "schemas/sub/sub_class.proto";
message MainClass {
repeated SubClass subclass = 1;
}
Protoc command
protoc -I=. --python_out=. schemas/main_class.proto schemas/sub/sub_class.proto
What works and what doesn't
Assuming my root folder is also my project's root folder, this approach now lets me launch python in the root folder and import the module using
import schemas.main_class_pb2
However, this means that my .proto-files must be located in the same folders as my python-files in that project, which seems pretty messy. It also means that you must generate the python-files from the same root folder as the project, which is not always possible. The .proto-files might be used to create a common interface for two totally different applications, and having to maintain two slightly different protobuf-projects seems to defeat the purpose of using protobuf.
Example python code
I am providing some sample python code that can be used to test that the import works and that the classes work as intended. This example is from attempt 1, and assumes that python is launched from the ./generated
folder
import main_class_pb2 as MC_proto
sub1, sub2 = (MC_proto.SubClass(name='sub1'),
MC_proto.SubClass(name='sub2'))
sub1.log.append(MC_proto.LogMessage(status=1, timestamp='2020-01-01'))
sub1.log.append(MC_proto.LogMessage(status=0, timestamp='2020-01-01'))
sub2.log.append(MC_proto.LogMessage(status=1, timestamp='2020-01-02'))
main = MC_proto.MainClass(subclass=[sub1, sub2])
main
Out[]:
subclass {
name: "sub1"
log {
status: STATUS_OK
timestamp: "2020-01-01"
}
log {
timestamp: "2020-01-01"
}
}
subclass {
name: "sub2"
log {
status: STATUS_OK
timestamp: "2020-01-02"
}
}