[HN Gopher] Deprecations via warnings don't work for Python libr...
___________________________________________________________________
Deprecations via warnings don't work for Python libraries
Author : scolby33
Score : 48 points
Date : 2025-12-08 18:13 UTC (2 days ago)
(HTM) web link (sethmlarson.dev)
(TXT) w3m dump (sethmlarson.dev)
| theamk wrote:
| Deprecations via warnings don't reliably work anywhere, in
| general.
|
| If you are a good developer, you'll have extensive unit test
| coverage and CI. You never see the unit test output (unless they
| fail) - so warnings go unnoticed.
|
| If you are a bad developer, you have no idea what you are doing
| and you ignore all warnings unless program crashes.
| SethMLarson wrote:
| Author here! Agreed that are different levels of "engaged" from
| users, which is okay. The concerning part of this finding is
| that even dependent users that I know to be highly engaged
| didn't respond to the deprecation warnings, so they're not
| working for even the most engaged users.
| eternityforest wrote:
| Why is it that CI tools don't make warnings visible? Why are
| they ignored by default in the first place? Seems like that
| should be a rather high priority.
| metadat wrote:
| Most of the time they don't matter and aren't an immediate
| problem.
|
| The Business doesn't care about warnings, they want working
| software NOW.
| bluGill wrote:
| It isn't that easy. If you have a new warning on upgrade you
| probably want to work on it "next week", but that means you
| need to ignore it for a bit. Or you might still want to
| support a really old version without the new API and so you
| can't fix it now.
| Hizonner wrote:
| > If you have a new warning on upgrade you probably want to
| work on it "next week", but that means you need to ignore
| it for a bit.
|
| So you create a bug report or an issue or a story or
| whatever you happen to call it, and you make sure it gets
| tracked, and you schedule it with the rest of your work.
| That's not the same thing as "ignoring" it.
| bluGill wrote:
| And you always have something more important/interesting
| to do and so never get around to it.
| Hizonner wrote:
| ... which means that when the axe falls, the results are
| 100 percent your fault.
| michaelt wrote:
| _> Why is it that CI tools don 't make warnings visible?_
|
| A developer setting up CI decides to start an ubuntu 24.04
| container and run 'apt-get install npm'
|
| This produces 3,600 lines of logging (5.4 log lines per
| package, 668 packages) and 22 warnings (all warnings about
| man page creation being skipped)
|
| Then they decide "Nobody's going to read all that, and the
| large volume might bury important information. I think I'll
| hide console output for processes that don't fail."
|
| Now your CI doesn't show warnings.
| optionalsquid wrote:
| You can turn warnings into errors with the `-Werror` option. I
| personally use that in CI runs, along with the `-X dev` option
| to enable additional runtime checks. Though that wont solve the
| author's problem, since most Python devs don't use either of
| those options
| Y-bar wrote:
| In PHP I don't think there is a native way to convert
| E_DEPRECATED into E_ERROR, but the most common testing
| framework has a quick way of doing the same.
|
| https://docs.phpunit.de/en/12.5/configuration.html#the-
| failo...
| rimunroe wrote:
| > If you are a good developer, you'll have extensive unit test
| coverage and CI. You never see the unit test output (unless
| they fail) - so warnings go unnoticed.
|
| In my opinion test suites should treat _any_ output other than
| the reporter saying that a test passed as a test failure. In
| JavaScript I usually have part of my test harness record calls
| to the various console methods. At the end of each test it
| checks to see if any calls to those methods were made, and if
| they were it fails the tests and logs the output. Within tests
| if I expect or want some code to produce a message, I wrap the
| invocation of that code in a helper which requires two
| arguments: a function to call and an expected output. If the
| code doesn 't output a matching message, doesn't output
| anything, or outputs something else then the helper throws and
| explains what went wrong. Otherwise it just returns the result
| of the called function: let result =
| silenceWarning(() => user.getV1ProfileId(), /getV1ProfileId has
| been deprecated/); expect(result).toBe('foo');
|
| This is dead simple code in most testing frameworks. It makes
| maintaining and working with the test suite becomes much easier
| as when something starts behaving differently it's immediately
| obvious rather than being hidden in a sea of noise. It makes
| working with dependencies easier because it forces you to
| acknowledge things like deprecation warnings when they get
| introduced and either solve them there or create an upgrade
| plan.
| ploxiln wrote:
| When I update python version, python packages, container image,
| etc for a service, I take a quick look at CI output, in
| addition to the all the other checks I do (like a couple basic
| real-world-usage end-to-end usage tests), to "smoke test"
| whether something not caught by outright CI failure caused some
| subtle problem.
|
| So, I do often see deprecation warnings in CI output, and fix
| them. Am I a bad developer?
|
| I think the mistake here is making some warnings default-
| hidden. The developer who cares about the user running their
| the app in a terminal can add a line of code to suppress them
| for users, and be more aware of this whole topic as a result
| (and have it more evident near the entrypoint of the program,
| for later devs to see also).
|
| I think that making warnings _error_ or _hidden_ removes
| _warnings_ as a useful tool.
|
| But this is an old argument: Who should see Python warnings?
| (2017) https://lwn.net/Articles/740804/
| Hizonner wrote:
| If you are a good developer, you consider warnings to be errors
| until proven otherwise.
| hiq wrote:
| What does a good developer do when working in a codebase with
| hundreds of warnings?
|
| Or are you only considering a certain warnings?
| Hizonner wrote:
| Why does your codebase generate hundreds of warnings, given
| that every time one initially appeared, you should have
| stamped it out (or specifically marked _that one warning_
| to be ignored)? Start with one line of code that doesn 't
| generate a warning. Add a second line of code that doesn't
| generate a warning...
| stackskipton wrote:
| Because most people are working at Failure/Feature
| factories where they might work on something and at last
| minute, they find out something is now warning. If they
| work on fixing it, the PM will screaming about time
| slippage and be like "I want you to work on X, not Y
| which can wait".
|
| 2 Years later, you have hundreds of warning.
| hiq wrote:
| It's rare that I work on a project I myself started. If I
| start working on an existing codebase, the warnings might
| be there already. Then what do I do?
|
| I'm also referring to all the warnings you might get if
| you use an existing library. If the requirements entail
| that I use this library, should I just silence them all?
|
| But I'm guessing you might be talking about more specific
| warnings. Yes I do fix lints specific to my new code
| before I commit it, but a lot of warnings might still be
| logged at runtime, and I may have no control over them.
| michaelt wrote:
| _> Why does your codebase generate hundreds of warnings_
|
| Well, it wasn't my codebase yesterday, because I didn't
| work here.
|
| Today I do. When I build, I get reports of "pkg_resources
| is deprecated as an API" and "Tesla T4 does not support
| bfloat16 compilation natively" and "warning: skip
| creation of /usr/share/man/man1/open.1.gz because
| associated file /usr/share/man/man1/xdg-open.1.gz (of
| link group open) doesn't exist" and "datetime.utcnow() is
| deprecated and scheduled for removal in a future version"
|
| The person onboarding me tells me those warnings are
| because of "dependencies" and that I should ignore them.
| mrweasel wrote:
| There was this one library we depended on, it was sort of in
| limbo during the Python 2 -> 3 migration. During that period is
| was maintained by this one person who'd just delete older
| versions when never ones became available. In one year I think
| we had three or four instances where our CI and unit tests just
| broke randomly one day, because the APIs had changed and the
| old version of the library had been yanked.
|
| In hindsight it actually helped us, because in frustrations we
| ended up setting up our own Python package repo and started to
| pay more attention to our dependencies.
| wang_li wrote:
| That's why you, very early on, release code that slows the API
| down once it has been deprecated. Every place you issue a
| deprecation warning, you also sleep 60. Problem solved.
| superkuh wrote:
| >We ended up adding the APIs back and creating a hurried release
| to fix the issue.
|
| So it was entirely possible to keep the software working with
| these. Why change/remove them in the first place? Is the benefit
| of of the new abstraction greater than the downside of requiring
| everyone using the software to re-write theirs?
| integralid wrote:
| OS software maintainers don't like maintaining legacy ugly APIs
| forever and want to refactor/remove legacy code to keep
| themselves sane and the project maintainable.
| superkuh wrote:
| Yep. Or you can see it as, "This software doesn't really care
| about the users and their use cases. It prioritizes making
| things look pretty and easier on the dev side over
| maintaining functionality." Or in the worse but fairly common
| OSS case, CADT, but that doesn't seem to apply in this
| context.
| LegionMammal978 wrote:
| Every public API change is a cost on the user: for an extreme
| example, if every library I ever used renamed half its APIs
| every year to align with the latest ontology, then there
| would hardly be any point in saving my scripts, since I'd
| have to be constantly rewriting them all.
|
| Of course, the reality is hardly ever as bad as that, but I'd
| say having to deal with trivial API changes is a reasonable
| basis for a user to dislike a given project or try to avoid
| using it. It's up to the maintainers how friendly they want
| to be toward existing user code, and whether they pursue
| mitigating options like bundling migrations into less-common
| bigger updates.
| mxey wrote:
| It's like 5 lines of code
| https://github.com/urllib3/urllib3/pull/3732/files
| memco wrote:
| I think this is a valid question for this specific case, but
| may not always be possible. That said, I think as a user I
| would probably prefer it if under the hood the old function
| called the new so they can deprecate the behavior without
| breaking the API. In that way you can still emit the
| deprecation warning while also only having one actual code path
| to maintain.
| shadowgovt wrote:
| Funny enough, in Python, a sufficiently-dedicated client can
| also do this on their end by just monkey-patching the class
| definition.
| traverseda wrote:
| Wait, does urlib not use semvar? Don't remove APIs on minor
| releases people. A major release doesn't have to be a problem or
| a major redesign, you can do major release 400 for all I care,
| just don't break things on minor releases.
|
| Lots of things not using semvar that I always just assumed did.
| Bratmon wrote:
| +1
|
| The industry has a solution for the exact problem the urllib is
| having (semver). Urllib just actively refuses to use it.
| philipwhiuk wrote:
| Python itself doesn't use semver.
| CaliforniaKarl wrote:
| This, I think, is the crux of the matter.
|
| As an example, I always knew urllib3 as one of the foundational
| packages that Requests uses. And I was curious, what versions
| of urllib3 does Requests pull in?
|
| Well, according to
| https://github.com/psf/requests/blob/main/setup.cfg, it's this:
| urllib3>=1.21.1,<3
|
| That is exactly the kind of dependency specification I would
| expect to see for a package that is using semver: The current
| version of urllib3 is 2.x, so with semver, you set up your
| dependencies to avoid the next major-version number (in this
| case, 3).
|
| So, it seems to me that even the Requests folks assumed urllib3
| was using semver.
| somat wrote:
| I would almost expect the 3 in urllib3 to be the major
| version and if something needed to break it would become
| urllib4. Which, I know, is terribly naive of me. But that is
| how psycopg does it.
| JackSlateur wrote:
| semver is funny because it gives the illusion of working but
| does not work at all
|
| Glory to 0ver: https://0ver.org/
| hartator wrote:
| I think they are misreading the situation.
|
| The devil is in the details. It seems `getHeaders` v. `headers`
| is non-security, non-performance related issue. Why people should
| spend time fixing these?
| CaliforniaKarl wrote:
| If you (either directly or from SerpApi) are supporting the
| urllib3 folks (through a Tidelift subscription), then yes, that
| is a valid point.
|
| Otherwise, I'd say that's a very brave comment you are making.
| shadowgovt wrote:
| "Brave" in what sense? It's a legitimate question why an API
| is having a pattern deprecated and removed for what appears
| to be pattern reasons.
| ipaddr wrote:
| If he gave money valid point if not he must be brave?
| attractivechaos wrote:
| Even if getHeaders() has security/performance concerns, the
| better solution is to make it an alias to the newer
| headers.get() in this case. Keeping the old API is a small
| hassle to a handful of developers but breaking existing code
| puts a much bigger burden on a lot more users.
| nodesocket wrote:
| Ya, why not just alias old api calls to the new if
| implementation details changed?
| Revisional_Sin wrote:
| > Maybe the answer is to do away with advance notice and adopt
| SemVer with many major versions
|
| Yes.
| jakub_g wrote:
| Fixing deprecations is unfortunately the lowest prio of any kind
| of work for majority of the projects. Part of the problem is
| probably lack of pressure to do so it if the timeline is unclear.
| What if this is actually never removed? Why going through the
| pain?
|
| IMO telling "we deprecate now and let's see when we remove it" is
| counterproductive.
|
| A better way: deprecate now and tell "in 12 (or 24?) months this
| WILL be removed".
|
| After 12/24 months, cut a new semver-major release. People notice
| the semver-major through the dependency management tools at some
| point, an maybe they have a look at changelog.
|
| If they don't, at some point they may want to use a new feature,
| and finally be incentivised to update.
|
| If there's no incentive other than "do the right thing", it never
| gets done.
|
| Having said that, I think LLMs are really going to help with
| chores like this, if e.g. deprecations and migration steps are
| well documented.
|
| Alternative option: create a codemod CLI that fixes deprecations
| for the users, doing the right thing automatically. If migration
| is painless and quick, it's more likely people will do it.
| Hizonner wrote:
| > Fixing deprecations is unfortunately the lowest prio of any
| kind of work for majority of the projects.
|
| ... and the right answer to that is to make it _entirely_ their
| problem.
|
| > Part of the problem is probably lack of pressure to do so it
| if the timeline is unclear. What if this is actually never
| removed?
|
| In this case, the warnings said exactly what release would
| remove the API. Didn't help.
|
| > Why going through the pain?
|
| Because you're not a feckless irresponsible idiot? I don't
| think it's an accident that the projects they said didn't react
| were an overcomplicated and ill-designed management layer for
| an overcomplicated and ill-designed container system, a move-
| fast-and-break-things techbro company, and what looks to be a
| consolation project for the not-too-bright.
|
| You probably get an extra measure of that if you're operating
| in the Python ecosystem, which is culturally all about half-
| assed, 80-percent-right-we-hope approaches.
|
| The right answer is to remove it when you say you're going to
| remove it, and let _them_ pick up the pieces.
|
| It also helps if you design your API right to begin with, of
| course. But this is Python we're talking about again.
| CaliforniaKarl wrote:
| > After 12/24 months, cut a new semver-major release. People
| notice the semver-major through the dependency management tools
| at some point, an maybe they have a look at changelog.
|
| The urllib3 package doesn't use SemVer.
| eddythompson80 wrote:
| Deprecations in all forms are always a shitshow. There isn't a
| particular pattern that "just works". Anybody that tells you
| about one, best case scenario, it just worked for them because of
| their consumer/user not because of the method itself.
|
| The best I have seen is a heavy handed in-editor strike through
| with warnings (assuming the code is actively being worked on) and
| even then it's at best a 50/50 thing.
|
| 50% of the developers would _feel_ that using an API with a
| strike through in the editor is wrong. And the other 50% will
| just say "I dunno, I copied it from there. What's wrong with
| it??"
| shadowgovt wrote:
| The secret trick I've used on rare occasion, but when necessary,
| is the "ten second rule."
|
| Users don't notice a deprecation warning. But they _might_ notice
| adding a "time.sleep(10)" immediately at the top of the
| function. And that gives them one _last_ grace period to change
| out their software before it breaks-breaks.
| odie5533 wrote:
| This will just waste CI compute and not solve anything.
| shadowgovt wrote:
| It's worked in the past. But it does require someone at your
| org to care that CI times are spiking, which is not always a
| thing you can rely upon.
|
| In addition: if CI is the only place the issue shows up, and
| never in a user interaction... Why does that software exist
| in the first place? In that context, the slowdown may be
| serving as a useful signal to the project to drop the entire
| dependency.
|
| _ETA_ : To be clear, I don't do this as a substitute for a
| regular deprecation cycle (clear documentation, clear
| language-supported warnings / annotations, clear timeline to
| deprecate); I do it _in addition_ before the final yank that
| actually breaks end-users.
| pavel_lishin wrote:
| Are you saying you wouldn't notice if your CI suddenly
| started taking twice as long, ten times as long, a hundred
| times as long to run?
| minitech wrote:
| This is so much worse than just making the breaking change.
| shadowgovt wrote:
| It depends on how we define "worse."
|
| A breaking change causes a full-stop to a service.
|
| An intentional slowdown lets the service continue to operate
| at degraded performance.
|
| I concur that it's less clear for debugging purposes
| (although any reasonable debugging infrastructure should
| allow you to break and see what function you're in when the
| program hangs; definitely not as clear as the program
| crashing because the called function is gone, however).
| minitech wrote:
| A breaking change in a dependency doesn't cause a full-stop
| to a service at all. The old version continues to work.
| Making subtly harmful changes so that new broken versions
| sneak in is just a bad idea and totally unnecessary.
| shadowgovt wrote:
| > A breaking change in a dependency doesn't cause a full-
| stop to a service at all
|
| From the article:
|
| "We still received feedback from users that this removal
| was unexpected and was breaking dependent libraries."
|
| I think we may be assuming different floors on service
| maintainer competency; with so many users pulling in
| dependencies across an arbitrarily-wide version window
| with no testing, such changes _do_ break services.
| minitech wrote:
| It's not necessary to cater to the absolute least
| competent end user to begin with, but inserting slowdown
| bugs _does not even achieve that_. (Note that the bit
| about the breaking of dependent libraries you're quoting
| is still not actually a service being affected.)
| hiq wrote:
| Wild (and I guess most of the time bad) idea: on top of the
| warnings, introduce a `sleep` in the deprecated functions. At
| every version, increase the sleep.
|
| Has this ever been considered?
|
| The problem with warnings is that they're not really observable:
| few people actually read these logs, most of the time. Making the
| deprecation observable means annoying the library users. The
| question is then: what's the smallest annoyance we can come up
| with, so that they still have a look?
| fpoling wrote:
| Yes, people do notice sleep. But it has to be on the scale of
| minutes or it will be ignored especially if it happens during a
| CI run.
| Hackbraten wrote:
| A deprecation warning is not actionable for typical end users.
| Why don't more warnings include calls to action?
|
| Instead of a warning that says, "The get_widget method in libfoo
| is deprecated and will be removed by November 30", the warning
| could say:
|
| "This app uses the deprecated get_widget method. Please report
| this bug to the app developer. Ask them to fix this issue so you
| can continue using this app after November 30."
| jeffrallen wrote:
| I prefer Go's solution to this problem. Just don't deprecate
| stuff. And to make that possible, slow down and design stuff you
| will be willing to support forever.
| plantain wrote:
| # Deprecated APIs resp.getheader("Content-Length")
|
| # Recommended APIs resp.headers.get("Content-Length")
|
| Why inflict this change on _tens of millions_ of users? It 's
| such a nonsense tiny busywork change. Let sleeping dogs lie.
| gsnedders wrote:
| This is exactly the sort of breaking change that I really
| struggle to see the value of -- maintaining the deprecated
| method seems incredibly unlikely to be a notable maintenance
| burden when it is literally just:
| @deprecated("Use response.headers.get(name) instead")
| def getheader(self, name): return
| self.headers.get(name)
|
| Like sure -- deprecate it, which might have _some_ downstream
| cost, rather than having two non-deprecated ways to do the same
| thing, just to make it clear which one people _should_ be
| using; but removing it has a much more significant cost on
| every downstream user, and the cost of maintenance of the old
| API seems like it should be almost nothing.
|
| (I also don't hate the thought of having a `DeprecationWarning`
| subclass like `IndefiniteDeprecationWarning` to make it clear
| that there's no plan to remove the deprecated function, which
| can thus be ignored/be non-fatal in CI etc.)
| akie wrote:
| Start adding a 500ms delay to every invocation of a deprecated
| function. Incentives work!
| tgsovlerkhgsel wrote:
| _Deprecations_ don 't work. Don't deprecate stuff without a
| really really good reason. The new API being cleaner is not a
| good reason. There are very few good reasons.
|
| If you deprecate something in a popular library, you're forcing
| millions of people to do work. Waste time that could be used for
| something better, possibly at a time of _your_ choice, not
| theirs. It was emitting warnings for 3 years... so you think
| everyone should have to rewrite their software every 3 years?
|
| Especially for something like this. Only document it in a
| footnote, mark it as deprecated, etc - but don't remove the
| alias.
|
| Don't break stuff, unless, to quote a famous work, you think your
| users are scum. Do you think your users are scum? Why do you hate
| your users?
| bigstrat2003 wrote:
| Honestly, I think that the author already found (and rejected the
| solution):
|
| > I could ask for more Python developers to run with warnings
| enabled, but solutions in the form of "if only we could all just"
| are a folly.
|
| I get where he's coming from. But the facts are: the language
| provides a tool to warn users, the library is using that tool,
| and users are choosing to turn that tool off. That's fine, but
| then they don't get to complain when stuff breaks without
| warning. It is _the user 's responsibility_ at that point, not
| the library maintainers'.
___________________________________________________________________
(page generated 2025-12-10 23:01 UTC)