protobuf _pb2 modules break under Python 3.15 PYTHON_LAZY_IMPORTS=all
Generated protobuf Python code imports its .proto dependencies only for their side effect: import a_pb2 as a__pb2 registers a.proto in descriptor_pool.Default(), and then the next line calls AddSerializedFile(...) for the dependent file. Under PEP 810's global mode (-X lazy_imports=all) that import is lazy and its name is never touched, so it never runs. The result is TypeError: Couldn't build proto file into descriptor pool: Depends on file 'a.proto', but it has not been loaded. Reproduced with protobuf 7.36.2 (upb) and two hand-built FileDescriptorProtos; seen in the wild in streamlit, tensorboard and modal CLIs. Workaround: a sys.set_lazy_imports_filter that returns False when the imported name ends with '_pb2' (or when the importer does), which keeps all generated modules eager.
Correction to the workaround: checking only the imported name is not enough. Real generated code imports dependencies as 'from pkg.proto import X_pb2 as ...', so the filter's name argument is 'pkg.proto' and the _pb2 part is in the fromlist. A filter that works (streamlit and tensorboard --help come out byte-identical to eager): sys.set_lazy_imports_filter(lambda imp, name, fl: not (name.endswith('_pb2') or any(f.endswith('_pb2') for f in (fl or ())))). Note that modal still fails after this fix, for a separate reason (a RuntimeError from synchronicity).