pythonsubprocessprotocol-buffersprotoc

Running protoc in Python subprocess


I have my .proto. files defined in a folder workspace_directory/sub_directory/proto_files.

When I run:

protoc --python_out=workspace_directory -I workspace_directory/sub_directory/proto_files workspace_directory/sub_directory/proto_files/*

the output python code is generated in workspace_directory/proto_files, which is what I want.

My main goal is to run that command as part of a build script in Python, so I attempted to use subprocess.run() to achieve that. In Python I run:

subprocess.run(shlex.split("protoc --python_out=workspace_directory -I workspace_directory/sub_directory/proto_files workspace_directory/sub_directory/proto_files/*"))

I get

Could not make proto path relative: workspace_directory/sub_directory/proto_files/*: No such file or directory

Some other things to note:

It feels like I'm missing something when using the subprocess module, but can't seem to see it.


Solution

  • protoc --python_out=workspace_directory -I workspace_directory/sub_directory/proto_files workspace_directory/sub_directory/proto_files/*

    When you execute this command normally in bash or other shell, the shell detects the * wildcard character and converts it to a list of matching filenames.

    Neither subprocess.run() nor shlex.split() do any wildcard handling, so protoc receives the text as-is and tries to open a file literally called *, which it cannot find. The error message is a bit confusing, but contains the reason: No such file or directory.

    Calling the command through the shell is a reasonable solution as long as you can trust all the text strings you include in the command. Alternatively you can use the Python glob module to expand the wildcard to a list of paths and include it in the command. That can be safer if the filenames may contain special characters.


    (You may also be interested in knowing that protoc is available as a Python module in grpcio-tools and can be called using import grpc_tools.protoc; grpc_tools.protoc.main(['protoc', '-I...', 'myproto.proto']). This avoids having to go through a subprocess, and can be easier to install using the Python pip package manager.)