2025, Sep 22 01:00

How to fix Protobuf Python import errors: set protoc --proto_path to the proto root for common/ packages

Troubleshooting Protobuf Python imports: why protoc needs --proto_path set to your proto root. Fix 'File not found' when compiling .proto with common/ packages.

Compiling .proto files to Python sometimes stumbles on imports that work fine in C# and Java. The usual culprit isn’t the schema itself but how protoc receives your import roots and file paths. If a file without imports compiles, but the moment you add import "common/…" everything breaks with File not found, you’re likely pointing protoc at the wrong root.

Example layout and commands

Consider a minimal setup that mirrors the situation with a shared package folder:

.
└── proto
    └── common
        ├── client_accounts.proto
        └── google_account.proto

client_accounts.proto:

syntax = "proto3";

package common;

import "google/protobuf/timestamp.proto";
import "common/google_account.proto";

message ClientAccount {
    google.protobuf.Timestamp last_login = 1;
    common.GoogleAccount google_account = 2;
}

google_account.proto:

syntax = "proto3";

package common;

message GoogleAccount {}

Generate Python stubs from the repo root, setting the include path to the root of all protos, not the package folder itself. Here’s a POSIX-style invocation with renamed variables for clarity:

SRC_ROOT="${PWD}/proto"
OUT_DIR="${PWD}/out_py"

protoc \
--proto_path=${SRC_ROOT} \
--python_out=${OUT_DIR} \
--pyi_out=${OUT_DIR} \
${SRC_ROOT}/common/client_accounts.proto

On Windows, the same idea applies. Point the include root to c:/tmp/proto and pass the file path down to the .proto you want to compile:

protoc --proto_path=c:/tmp/proto --python_out=c:/tmp/generated_python_files --pyi_out=c:/tmp/generated_python_files c:/tmp/proto/common/client_accounts.proto

What’s really going on

protoc’s import resolution is strict. The flag --proto_path designates the root directory from which all import statements are resolved. When your schema says import "common/environment_params.proto", protoc expects to find a directory named common directly under the path you passed to --proto_path. If you set --proto_path to c:/tmp/proto/common but your imports still include the "common/" prefix, protoc looks for c:/tmp/proto/common/common/… and fails with File not found.

There’s a second, related detail. In these examples the files live under proto/common and declare package common;. That pairing matters. The import path common/foo.proto is aligned with the package name common and the filesystem path beneath the include root. Many IDEs smooth over these details during multi-language builds; the raw protoc CLI does not. It’s unforgiving, but consistent once you wire the root correctly.

The fix

Always make --proto_path the root of your proto tree, not an inner package directory. If your files live under c:/tmp/proto/common and imports are written as import "common/…", set --proto_path=c:/tmp/proto and invoke protoc with the full path to the file you want to compile, including the package directory portion. For example:

protoc --proto_path=c:/tmp/proto --python_out=c:/tmp/generated_python_files --pyi_out=c:/tmp/generated_python_files c:/tmp/proto/common/client_accounts.proto

If you keep package common; in the .proto files and imports like import "common/google_account.proto";, protoc will correctly locate the dependencies and generate the Python modules. Files without imports appeared to work before because protoc didn’t need to resolve any package-qualified paths; the moment imports were introduced, the mismatch between include root and import path surfaced.

Why this matters

Cross-language builds depend on consistent package declarations and stable include roots. When the CLI setup mirrors the on-disk structure and the package names used in imports, you get reproducible generation across Python, C#, and Java. Relying on IDE magic can hide misconfigurations until you move to a plain protoc call in CI or on another machine.

Takeaways

Organize your proto tree with a clear root, declare package names that match your directory structure beneath that root, and pass protoc both the correct --proto_path and the correct path to the target .proto file. If imports are written with a common/ prefix, the include path should be the parent of common, not common itself. With that in place, protoc resolves imports cleanly and the Python build succeeds, just like your C# and Java builds.

The article is based on a question from StackOverflow by saka and an answer by DazWilkin.