Hacker News new | ask | show | jobs
by usrbinbash 976 days ago
https://semver.org/

    Given a version number MAJOR.MINOR.PATCH, increment the:
    
    1. MAJOR version when you make incompatible API changes
So what exactly is the issue here?
6 comments

The post's point is that an API change is a very big deal, to be used only when the older version just can't work anymore or the newer version offers something much better. But the Flask team seems to have a much lower bar on what justifies the change. So the author feels unhappy he has to update a bunch of projects for no good reason.
What stops folks from sticking with the last version of the last major point release? Do the flask folks not provide security or other update to older major point releases? Sometimes in the software lifecycle, you need to refactor and move on from older API designs. Even keeping old APIs around adds cruft, testable surface area you have to support, and slows down everything and adds risk with shipping software over time. Android, iOS, GTK, half the javascript and C++ libraries in the world, etc pop in into my head and all will break some amount in a major version update.
> an API change is a very big deal, to be used only when the older version just can't work anymore

That's one opinion.

Another opinion is, that API and major version changes can be used whenever the maintainers of an open source project feel like it. No other reason is necessary. Because it's their project. They write the code. They don't have to justify that decision.

If a large portion of the projects userbase is unhappy about that, the project will lose traction and new project, or a fork, will arise.

The post would have a point if the Flask team treated this like a minor release (2.x -> 2.x+0.1) instead of a major (2.x -> 3.x) and hadn't issued a deprecation warning.
tl;dr The author things that Flask should release a 2.* and have no breaking changes.
The problem is that breaking changes in widely-used packages are disrespectful to your users. See Rich Hickey’s Spec-ulation
I'd say the respect towards the users is displayed by the frequency of breaking changes and the overall time to migrate, through deprecation warnings and at least security maintenance of older versions. Sometimes things need to change.

Speaking of that, flask 2 was released in May 21. I can't really complain about some breaking changes every 2 years or so. That's almost like a linux distribution.

> I'd say the respect towards the users is displayed by the frequency of breaking changes and the overall time to migrate, through deprecation warnings and at least security maintenance of older versions. Sometimes things need to change.

A change that brings new added value to users is a case where the change needs to be made, yes.

But the article's point is that the Flask changes don't seem to be doing that. They're not making breaking changes in order to introduce new features that add value for users. They're making breaking changes just because they feel like it, giving users no added value to compensate for the time and effort required to deal with the change. That's not respecting users.

I disagree with that, to be honest.

At a first glance, you might think "but where is the shiny carrot that makes me upgrade?" But the thing is - we have dealt with major upgrades at work which broke many things, added many features, changed things in undocumented ways, turned the whole dependency inside out.

That's a horrible experience. You end up running after ghosts: Is it the breaking change, is it a change due to a feature, what is even going on!

Looking at it, Flask 3 seems to be a simple batch of several breaking changes on the roadmap for quite some time. This is good - they thought about batching these breaking changes together to have one big bad one, opposed to like 6 of them over the 2 years. And you can upgrade, and you can clearly see if one of the breaking changes causes harm to your application, without having to worry about hundreds of other changes within the same major release.

Batching a bunch of breaking changes together so they all come at once and you only have to deal with them once is fine.

What I am asking is, why did there even need to be a breaking change in the case of the function discussed in the article? No functionality changed; the Python standard library function was identical to the former Flask function. So why couldn't the Python standard library function have been imported into the Flask namespace to replace the former Flask function? Why force every user to do that manually?

Throwing the crust away and refactoring stuff also "doesn't add new features" but it needs to be done to keep codebase good.

> giving users no added value to compensate for the time and effort required to deal with the change

... the users of free software. That didn't bother to find&replace one method in their plugin code.

Refactoring is not a breaking change by definition: refactoring is preserving the same contract with a new implementation.
I put "throwing the cruft away" in the same sentence EXPLICITLY to address that I am not just talking about refactoring.

Learn to read before throwing dictionary definitions at people

Providing method now-provided by standard libs is by definition unnecessary cruft. Nobody wants that in their codebase. They warned people for 2 years.

But apparently basic competence is too hard requirement in software ecosystems.

>But the Flask-Login team is not actively developing the extension anymore.

So they should tie the development to an extension that isn't in further development?

The issue with Flask-Login isn't even a functionality change. Flask just decided to stop making a particular function available in their namespace and now wants you to import it from the Python standard library instead.

A better solution for a case like this would be to import the function from the Python standard library into the Flask namespace, so old code would still work. Then it wouldn't matter that Flask-Login is no longer actively maintained.

Also, as the article notes, Flask-Login is by no means the only Flask-using Python package that was broken by this change. Are all of those other packages no longer actively maintained? I doubt it.

> Flask-Login is by no means the only Flask-using Python package that was broken by this change

Package maintenance also means to keep up with changes in the packages dependencies. If I don't do that, that's my problem, not the dependencies.

If I want to fix a certain version as my requirement, I can do so. Every major package system, including the ones used in Python, allow this. If I don't want that, then I need to keep my package maintained, and that means keeping an eye on what my dependencies do. That's part of package maintenance, simple as that.

There is no onus on the dependencies maintainers to care about whether I do my maintenance or not.

> There is no onus on the dependencies maintainers to care about whether I do my maintenance or not.

There's no "onus" on Flask to do anything they don't want to do. But if Flask forces every package that depends on them to fix a breaking change that they could have avoided with a one-line import statement, I would argue that is not very respectful of all those other package maintainers.

> But if Flask forces every package

The reverse would be that every package that depends on flask forces it to make all future changes dependent on whether or not they break someones code. Which obviously isn't a sustainable model for software development.

> I would argue that is not very respectful of all those other package maintainers.

Define what is "respectful" then?

The flask team announces changes. They deprecate things. They use deprecation warnings. They use major versions correctly. They honor well established good practices in software development, to give package maintainers the opportunity to react to changes early.

Please, do explain: What else is required to meet whatever definition of "respectful" we are talking about here?

There’s also no onus on me to continue using packages that force me to spend valuable time fixing their breaking changes. My rule of thumb for dependencies is that, once I have to fix three or four breaking changes, the cost of switching to a more stable alternative or writing my own becomes more worth it.

There’s also the option of releasing a package called something like flask2_compatibility that monkeypatches flask3 to work with flask2

> A better solution for a case like this would be to import the function from the Python standard library into the Flask namespace, so old code would still work.

Forever?

For as long as the functionality does not change. Which it didn't when the function was removed; the Python standard library function had the same functionality as the former Flask function that was removed.
Adding code into your framework to support abandonware plugins is the exact opposite of better solution
They wouldn't have needed to add any code. They could have just changed their implementation of the function to an import of it from the Python standard library. That would be a net reduction in code size.
Or remove it altogether, warn people for years about deprecation, then remove all of them. Which they did.

More net lines removed too.

But hey, it annoyed Man That Is Bad At Dependency Management and Wrote A Book About It so it must be bad!

This is what I'm scratching my head at too. Why would it be a good thing for any OSS maintainer to slow themselves down for packages that don't want to fix deprecation warnings?
- Because that's what you signed up for as maintainer of a project used by millions.

- It's a significant piece of functionality.

- The change is trivial, not worth the cost of losing login.

In some instances Python deprecates something for six years, Flask six months.

Breaking changes shouldn’t happen with the same name: if you want to break your users, pick a new name too
The version number, and especially the major version, IS part of the name, so they did exactly that.
Then require people to `import flask3` so you can install both major versions together.
No.

That would break every single module that is compliant with flask 3.x and imports it with `import flask`. This simply isn't justified by the "benefit" of some old packages not having to change a single line of code.

Yes, maybe in hindsight it would have been beneficial to put the major version of some packages (flask is far from the only one) directly into the package name, as a workaround for pythons inability to deal with multiple versions of the same module under the same name in one environment. But python works as it does, `import flask` is how god-knows how many projects use it, so that's how the show runs, period.

If some package requires a certain version, it can pin that version in its `setup.py`. If a project requires a certain version it can pin that version in it's `requirements.txt`. If an environment requires a certain version, the admin can create a virtualenv.

Would it not work to specify the version when installing the packages?
Only to an extent. I can't think of a language environment where each package can have its own version of a dependency. Python certainly isn't one.

So if you want any of the Flask 3.0 features you have to take the new Werkzeug, and see Flask-Login break.

Node lets every package have its own versions of dependencies. But, imo, it’s better (less time spent fixing upgrade breakage/incentive to pick stable dependencies) in the long run to depend on latest and always update, fixing breakage as you go and only locking versions in CI so you can deploy a known bundle.
Of "Frequently update dependencies to latest" or "don't have to modify code which depends on others", you only get to pick one.

If you're frequently updating to latest, you're on the bleeding edge; sometimes things will bleed more than others.

If you're stable, you might not have the latest and greatest all the time.

The attitude of expecting to always have the latest and greatest, but never have anything break, all while not paying for the effort, seems absurd to me.

It works pretty well, it’s like how continuous integration seemed absurd to people who spent months integrating changes and now is the standard practice.

Anyways, in my experience, if you routinely use the latest and greatest versions of dependencies, over time you find that you stop using dependencies that make this painful.

Anyways, I’m fine with accidental breakage. Deliberately choosing instability in the form of a major version release seems irresponsible for packages at the base of an entire ecosystem. (Absent some critical security issue that your users would have to address anyways as happened with log4j)

Rust (Cargo) supports this.
No, because your users basically have to upgrade eventually, unless you commit to provide bug fixes (security especially) for every major version.
> breaking changes in widely-used packages are disrespectful to your users

No they are not.

The flask team announces changes, deprecates things and uses major versions correctly. The major version change also doesn't happen frequently.

If a package absolutely requires a certain version, IT CAN SPECIFY AS MUCH in its requirements. If it doesn't, tough luck.

Flask doesn't follow semver. This could just as easily have happened in 3.1.
in the context of python, what exactly is an api change? If my api is

   ```
      def some_function(*args, **kwargs) -> JSON:
         ...
   ```
In essence, I'm pretty sure plenty of the changes in the set of parameters that are actually acceptable quite often changes accidentally (same for what's in the JSON)
I think the author learned for first time of his life that framework plugins need to keep up with framework changes.

2 years (flask 2.0 release) should be plenty for deprecation notice.

Breaking method was only deprecated six months according to the article.
The issue is that releasing breaking changes sucks for your users. A numbering scheme doesn’t really make it suck any less.
Breaking changes are fine (and a lot of times completely necessary) as long as they are expected _for what you're trying to do_.

The numbering scheme is meant to give predictability to breaking changes or backwards compatibility, so if followed correctly, it absolutely should make it suck less.

> A numbering scheme doesn’t really make it suck any less.

Yes it does, because that "numbering scheme" tells my users when and if breaking changes happen.

It also allows them to avoid them. Don't want to keep up with the dependency? Make the version a hard requirement. Done.