TL;DR: Add filterwarnings = ["error"] to your charm’s [tool.pytest.ini_options]. Pytest then promotes every Python warning to a test failure, so removed APIs, leaked file handles, and other soon-to-break behaviour show up in CI instead of in production.
In a future Charmcraft release, both machine and kubernetes profiles will ship with this setting by default. I’d like you to strongly consider enabling this for your existing charms, too.
Why bother
Deprecation warnings exist for a reason: they tell you a function, argument, or behaviour is going away in a future release. The default warning filter prints them to stderr; pytest provides a nice summary at the end by default, but in my experience this tends to be ignored when running locally, and not seen at all in CI.
Promoting warnings to errors flips the situation. You learn about the problem when the warning is added (cheap to fix), not when the API is removed (urgent, blocking, often during a release crunch).
It’s not only deprecation warnings. -Werror also catches:
ResourceWarning: file handles, subprocesses, sockets, sqlite connections, temporary directories left open. These are real bugs, even if the short-lived charm process tends to minimise the impact.UserWarning: libraries shouting that you’re using them wrong.PendingDeprecationWarning: the early heads-up beforeDeprecationWarning.
We considered recommending the narrower -W error::DeprecationWarning and decided against it. The Charm Tech baseline run found that the majority of the real bugs the warnings flagged were ResourceWarnings, not deprecations. A deprecation-only setting would have shipped a recommendation that didn’t catch the bugs the baseline existed to find.
The setting
In your charm’s pyproject.toml:
[tool.pytest.ini_options]
filterwarnings = [
"error",
]
That’s it. No PYTHONWARNINGS environment variable, no -W flag in tox.ini. The pytest setting is harder to accidentally clobber and travels with the test config.
If you want the same behaviour for code paths that aren’t under pytest (for example, a script run via tox -e something-else), -W error in the relevant testenv works fine; it just isn’t the place to start.
When you turn it on, expect this
The first run will most likely fail. The Charm Tech downstream baseline (254 charms running on hyrum, the cross-repo test runner formerly known as super-tox) showed the pass rate drop from 61% to 11% the first time -Werror was applied, even with ops patched to current main.
After that, the residual failures typically fall into a handful of clusters. Most charms hit one or two; you filter each by adding a more specific entry below the "error" line:
[tool.pytest.ini_options]
filterwarnings = [
# Pytest prepends each entry to warnings.filters, so later entries take
# precedence at runtime. Put "error" first, then more specific ignores below.
"error",
"ignore::DeprecationWarning:paramiko.*",
]
Known residual clusters and recommended filters
| Cluster | Where it comes from | Suggested filter |
|---|---|---|
| Harness deprecation | ops when using Harness tests (move to Scenario!) | "ignore:Harness is deprecated:PendingDeprecationWarning" |
paramiko TripleDES |
pulled in by pytest-operator (move to pytest-jubilant!) |
"ignore::DeprecationWarning:paramiko.*" |
websockets legacy API |
older juju / python-libjuju versions (move to Jubilant!) |
"ignore::DeprecationWarning:websockets.*" |
pydantic V1 @validator |
traefik_k8s.v2.ingress charm lib |
"ignore::DeprecationWarning:traefik_k8s.*" |
| protobuf metaclass on 3.14 | googleapis-common-protos |
"ignore::DeprecationWarning:google.*" (3.14 only) |
These are the ones we hit ourselves; your dependency mix may surface others. The general shape is the same: identify the module, add a narrowly-scoped ignore below "error". Always consider whether you can fix the situation rather than ignore the warning.
If a residual warning is in your own code, fix the warning rather than filter it: that’s the whole point. In tests, you should use pytest.warns to make sure you’re warning when expected.
What if my tests are too far gone to fix in one PR?
A few realistic strategies:
- Use the narrow form for now.
filterwarnings = ["error::DeprecationWarning", "error::PendingDeprecationWarning"]gets you the deprecation-catching benefit immediately, and you can broaden to"error"once theResourceWarnings are cleaned up. - Filter loudly, ignore quietly. Add
"ignore::ResourceWarning"below"error", file an issue against your own charm to come back to it, and move on. The deprecations still surface. - Module-scope your filters. If only one test file is noisy, use
@pytest.mark.filterwarnings("...")on that module/test rather than loosening the project-wide filter.
What we changed in Charm Tech
We had to do a bunch of clean-up work in our own code too:
canonical/operator—tox -e unitruns with-Werror. PRs #2506, #2507, #2509, #2541, #2542, #2548, #2558, #2560, #2561, #2581.canonical/charmlibs— fix proposed in #492 (open).canonical/charmcraft— theunitprofile in bothmachineandkubernetesinit templates will ship withfilterwarnings = ["error"](#2718, open).canonical/jubilant— fix landed in #342canonical/hyrum— fix landed in #36canonical/charm-ubuntu— fix landed in #82canonical/charmhub-listing-review— fix landed in #126canonical/pytest-jubilant- fix landed in #81
If you’d like a hand turning this on for your charm, reply here or ping us in the Charm Development Matrix room. We are explicitly trying to do this on a handful of charms across teams this cycle and are happy to take the first cut.