[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)