Managing versions on python projects
# Chasing a `TypeError` Through Python's Wheel Ecosystem
I ran my video-notes pipeline against a new lecture recording and got this, two stages in:
```
TypeError: open() got an unexpected keyword argument 'metadata_errors'
```
The traceback pointed into `faster-whisper`, which called into `PyAV`'s `av.open()`. Nothing in my own code — the failure was enti
rely inside two third-party packages talking past each other.
## The actual bug: a silent API break
`faster-whisper` calls:
```python
av.open(input_file, mode="r", metadata_errors="ignore")
```
My environment had `av==19.0.1` installed — the newest release at the time — and at some point between the version `faster-whisper
==1.2.1` was built against and 19.0.1, PyAV dropped the `metadata_errors` argument from `open()`. Both packages were "compatible"
by the version constraints in `faster-whisper`'s metadata (`av>=11`), but not compatible at the actual function-signature level. T
hat's the trap with loose version ranges: pip is happy, the import works, and the crash only shows up once you hit the specific co
de path that uses the missing argument.
The fix in isolation is simple — pin `av` to a ver-whisper` 1.2.1 was released, instead of "whatever'
s newest."
## The twist: Python 3.14 had no escape hatch
Pinning `av` to an older version should have been cause my venv was running Python 3.14 — only a few
months old at the time.
PyAV ships prebuilt wheels per Python version. The newest PyAV release (19.x) ships a `cp312-abi3` wheel, which works on 3.12 and
up via the stable ABI — that's why it installed cleanly on 3.14 in the first place. But older PyAV versions (14.x, 15.x, 16.x) wer
e built before Python 3.14 existed, so there's no reter. Trying to install one falls back to a from-s
ource build, which immediately demands the Microsoft C++ Build Tools:
```
error: Microsoft Visual C++ 14.0 or greater is required.
```
So the matrix looked like:
| av version | has the bug? | has a 3.14 wheel? |
|---|---|---|
| 19.x | yes | yes |
| 14.x–18.x | no | no |
There was no version of `av` that was simultaneous* on Python 3.14. The newest language version and t
he dependency I needed had a gap neither side had closed yet.
## The real fix: pick a Python version the ecosystem has caught up to
The fix wasn't a package pin at all — it was a Pytilt the environment on Python 3.13, where `av==16.0
.0` has a prebuilt wheel and predates the `metadata_errors` removal. Problem gone.
The general lesson: **for projects with native-extension dependencies (anything wrapping ffmpeg, CUDA, ONNX, etc.), don't default
to the newest Python.** Packages like PyAV, `ctranslate2`, `onnxruntime`, and `numpy` typically take 6–12 months after a new CPyth
on release to ship prebuilt wheels for it. Being o buys you nothing for a project like this and quiet
ly removes your ability to pin around bugs in your dependencies.
## Making sure it doesn't happen again: moving to uv
The deeper issue was reproducibility. The project's setup instructions were "create a venv, `pip install faster-whisper`" — no pin
ned versions, no lockfile. Every fresh install would silently resolve to "whatever's newest," including whatever version of PyAV h
appens to be newest *that week*. The bug wasn't a process was guaranteed to reintroduce it eventually
.
I migrated the project from a bare `venv` + unpinntps://github.com/astral-sh/uv):
```toml
# pyproject.toml
[project]
name = "video-notes-pipeline"
version = "0.1.0"
requires-python = "==3.13.*"
dependencies = [
"faster-whisper==1.2.1",
"av==16.0.0",
]
```
`uv lock && uv sync` now pins every dependency — direct and transitive — in a `uv.lock` file, and pins the Python version itself.
Anyone (including future me) who runs `uv sync` gets the exact same environment, on the exact same interpreter, every time. No mor
e "it works on my machine because I happened to inthe PyAV release."
## Takeaways
- A `TypeError: unexpected keyword argument` coming from inside a third-party dependency is a version-skew signature, not a logic
bug — check `pip show` on both packages before debugging your own code.
- Loose version constraints (`av>=11`) only guarantee the version *number* satisfies a range; they say nothing about whether the a
ctual function signatures still line up.
- Before adopting a brand-new Python version for a project with native/ML dependencies, check whether those dependencies ship whee
ls for it yet (`pip download <pkg> --no-deps -d /tgning up for from-source builds and a C++ toolchain
just to downgrade a package later.
- A lockfile (`uv.lock`, `poetry.lock`, etc.) isn't stops "latest compatible" from silently becoming
"broken" six months later.
Comments
Post a Comment