willitbreak 0.1.0 — does this upgrade break YOUR code?
Will this dependency upgrade break code you actually wrote?
Dependabot opens eleven pull requests. The changelog for one lists forty breaking changes. Thirty-nine are about parts of the library you have never imported, and the fortieth is on line 218 of a file you forgot existed.
pip install willitbreak
willitbreak urllib3 --to 2.2.1284 API changes in that release. Two of them touch this code. Those two come with file and line numbers; the rest are a number you can ignore.
their API diff your source what you get
────────────────── ───────────────── ─────────────────────
284 things changed × 217 call sites = 2 problems, with line numbers
Why this isn't just a changelog
Every other tool in this space is built for the people publishing a library, so it can tell you what changed but not whether you used any of it. griffe — the most capable of them — puts it plainly in its own documentation: users must review the reported breakages and assess their own codebase for impacts.
That assessment is the work. This does it.
What it takes seriously
Never guessing
A checker that cries wolf gets uninstalled after the second false alarm, and then catches nothing at all. Resolution stops the moment it would have to assume:
client = pkg.Client()
client.get(timeout=5) # resolved: pkg.Client.get
thing = make_it()
thing.get(timeout=5) # not reported — origin unprovable
c = pkg.Client()
c = something_else()
c.get(timeout=5) # not reported — the name was reboundThe same applies to arguments. f(**options) might be passing the removed parameter, or might not, so it is never claimed as a break.
The condition attached to every change
A removed keyword only matters to code that passes it. A parameter that lost its default only matters to code that omits it — pass it positionally and you are fine. A parameter that became keyword-only only breaks callers who passed it positionally. Each condition is checked against the actual call site.
Where libraries really live
Almost every package implements in pkg/_client.py and exposes in pkg/__init__.py. A tool that only looked at where a class was defined would report pkg._client.Client and never match the pkg.Client everyone writes, so re-export chains are followed to a fixed point.
Not importing anything
Both versions are downloaded and read as source. Importing a package to inspect it means executing it — and the version you are asking about is by definition not the one installed. It may not even import on your interpreter.
In CI
- run: pip install willitbreak
- run: willitbreak urllib3 --to ${{ matrix.candidate }} src/Exit 2 means this upgrade breaks something you wrote. Exit 1 is reserved for the tool itself failing, so a pipeline can act differently on each. Exit 0 means the changes do not reach your code.
Isn't this what mypy does?
Partly, and the overlap is worth being honest about. If a package ships type information, you install the new version, and your code is annotated, mypy will flag a removed attribute. The differences that matter:
- You have to install the upgrade first — which is the thing you were trying to evaluate, and on a conflict it may not install at all.
- It reports everything, not what changed. A pre-existing error and one introduced by this upgrade look identical.
- It needs types. Untyped packages and
**kwargs-heavy APIs are exactly where signature changes hide.
This answers a narrower question — what does this specific version bump do to me — without touching your environment.
88 tests. No dependencies. Python 3.9+, tested on Linux, Windows and macOS across 3.9–3.13.
Full documentation: https://github.com/CAOShurong/willitbreak
Full Changelog: https://github.com/CAOShurong/willitbreak/commits/v0.1.0
