uv.lock can silently resolve protobuf incompatible with generated event_pb2.py (ImportError: runtime_version)
Summary
uv.lock can silently resolve protobuf to a version that is incompatible
with the checked-in generated code in app/events/generated/event_pb2.py,
breaking app, email_handler.py and job_runner.py at import time with:
ImportError: cannot import name 'runtime_version' from 'google.protobuf'This is not a hypothetical: it reproduces on a clean checkout of master.
Root cause
app/events/generated/event_pb2.pyis generated code that requiresgoogle.protobuf.runtime_version, which only exists starting withprotobuf >= 4.25.protobufis not a direct dependency inpyproject.toml— it is only pulled in transitively throughgoogle-api-python-client ~= 1.12.3(an ~2020-era pin) via itsgoogle-api-coredependency chain.google-api-python-client ~= 1.12.3's transitive constraints permit (and, once nudged, actively prefer)protobuf < 4, i.e. the latest 3.x release (3.20.3), which does not have theruntime_versionmodule at all.- Separately,
app/email_utils.pydoesfrom cachetools import cached, TTLCache, butcachetoolsis also not declared as a direct dependency — it only happens to be present transitively (currently viagoogle-auth).
Because protobuf and cachetools both float as undeclared/transitive
dependencies, the exact versions that get resolved depend entirely on
whatever else is going on in the rest of the dependency graph at lock time.
The currently committed uv.lock happens to have protobuf==5.27.1, which
works — but that is incidental, not guaranteed by pyproject.toml.
Reproduction (on a clean checkout of master)
Add any new, unrelated top-level dependency and re-lock, e.g.:
"google-auth-httplib2 ~= 0.0.4",
+ "cachetools ~= 5.3.3",uv lockResult:
Updated cachetools v4.1.1 -> v5.3.3
Updated google-api-core v1.22.2 -> v1.34.1
Updated google-auth v1.22.0 -> v2.47.0
Updated googleapis-common-protos v1.52.0 -> v1.73.0
Updated protobuf v5.27.1 -> v3.20.3protobuf drops from 5.27.1 to 3.20.3. Booting the app (or running
python -c "import email_handler") with this lockfile immediately fails
with the ImportError above.
We hit this for real: adding a dependency for an unrelated feature on a
fork triggered exactly this, taking down a production deployment until we
tracked it down (the failure mode gives no indication that protobuf, of
all things, is the problem).
Suggested fix
- Declare
protobufas a direct dependency with a floor that matches what the generatedevent_pb2.pyactually requires, e.g.protobuf >= 4.25, < 6. - Declare
cachetoolsas a direct dependency (it's directly imported inapp/email_utils.py), e.g.cachetools >= 5.0, < 6. - Bump
google-api-python-clientoff the 2020-era~= 1.12.3pin to a current 2.x release (>= 2.100, < 3) andgoogle-auth-httplib2to>= 0.2.0, < 1.0accordingly — the only call site (app/api/views/auth.py,googleapiclient.discovery.build("oauth2", "v2", ...)) is unaffected by the major version bump.
This resolves cleanly to stable (non-prerelease) versions across the board
(protobuf 5.27.1, google-api-core 2.30.3) and removes the landmine for
good, rather than just re-locking back to a lucky state.
I'll open a PR with this fix, verified locally (dependency resolution +
container build + import smoke test for app, email_handler.py,
job_runner.py, and the Google OAuth code path).
Source: simple-login/app