2025, Sep 22 05:01

Компиляция .proto в Python: правильный --proto_path и импорты

Разбираем, почему protoc для Python падает на импортах с File not found, и как настроить --proto_path и include-пути. Примеры для POSIX и Windows. Без магии IDE

Компиляция .proto файлов в Python иногда спотыкается на импортах, которые в C# и Java работают без проблем. Обычно причина не в самой схеме, а в том, как protoc получает корни импортов и пути к файлам. Если файл без импортов компилируется, но стоит добавить import "common/…" — и всё ломается с ошибкой File not found, скорее всего, вы указываете protoc неверный корневой каталог.

Пример структуры и команд

Возьмём минимальную конфигурацию, отражающую ситуацию с общей папкой пакета:

.
└── 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 {}

Генерируйте Python‑стабы из корня репозитория, выставляя include‑путь на корень всего дерева .proto, а не на папку пакета. Вот POSIX‑пример с переименованными переменными для ясности:

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

В Windows логика та же. Укажите корень include на c:/tmp/proto и передайте путь к .proto, который хотите скомпилировать:

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

Что происходит на самом деле

protoc строго разрешает импорты. Флаг --proto_path задаёт корневой каталог, относительно которого разрешаются все инструкции import. Когда в схеме написано import "common/environment_params.proto", protoc ожидает найти каталог common непосредственно под путём, переданным в --proto_path. Если вы указали --proto_path=c:/tmp/proto/common, но в импортах по‑прежнему есть префикс "common/", protoc ищет c:/tmp/proto/common/common/… и падает с File not found.

Есть и второй, связанный нюанс. В этих примерах файлы лежат в proto/common и объявляют package common;. Эта связка важна. Путь импорта common/foo.proto согласован с именем пакета common и с путём в файловой системе под корнем include. Многие IDE скрывают эти детали при сборках для разных языков; «голая» CLI‑утилита protoc — нет. Она сурова, но предсказуема, если правильно указать корень.

Как исправить

Всегда задавайте --proto_path как корень дерева ваших .proto, а не внутренний каталог пакета. Если файлы лежат в c:/tmp/proto/common, а импорты записаны как import "common/…", установите --proto_path=c:/tmp/proto и вызывайте protoc с полным путём к компилируемому файлу, включая часть каталога пакета. Например:

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

Если в .proto остаётся package common; и импорты вида import "common/google_account.proto";, protoc корректно найдёт зависимости и сгенерирует Python‑модули. Раньше файлы без импортов «работали» лишь потому, что protoc не приходилось разрешать пути с префиксом пакета; как только появились импорты, несоответствие между include‑корнем и путями в import сразу проявилось.

Почему это важно

Кросс-языковые сборки зависят от согласованных объявлений пакетов и стабильных include‑корней. Когда настройки CLI отражают структуру на диске и имена пакетов, используемые в импортax, генерация для Python, C# и Java получается воспроизводимой. Полагаться на «магии» IDE рискованно: неверные настройки всплывут, как только перейдёте к чистому вызову protoc в CI или на другой машине.

Итоги

Организуйте дерево proto с понятным корнем, объявляйте имена пакетов, которые совпадают со структурой каталогов под этим корнем, и передавайте protoc и корректный --proto_path, и корректный путь к целевому .proto. Если импорты пишутся с префиксом common/, include‑путь должен указывать на родительский каталог common, а не на сам common. При таком раскладе protoc чисто разрешает импорты, и сборка Python проходит так же успешно, как и ваши сборки для C# и Java.

Материал основан на вопросе на StackOverflow от saka и ответе от DazWilkin.