Dash is planning to lock Flask & Werkzeug versions

Since day 1, Dash has used Flask as its web server. We’re incredibly grateful to have this excellent package to build on, and grateful for all the hard work of the folks maintaining it! And in the spirit of openness and our place as just one library of many in the Python ecosystem, we’ve always required only a minimum version for Flask (currently Flask>=1.0.4), so that if you integrate Dash with a larger Flask app you can use whatever version of Flask you want, whatever version your host app already uses.

But the great majority of our users don’t care what version of Flask they get, they just want Dash to work. And time and again this has caused problems: a new version of Flask (or its matched WSGI utility Werkzeug) changes something we were using, either in Dash itself or in one of the many add-on libraries, either in the open-source world or in our enterprise offerings, and apps that were previously working suddenly fail. Ironically, the last time Flask put out a major release, v2.0, which according to SemVer is expected to have breaking changes, as I recall Dash needed no changes; but nearly every minor release (or feature release) since then has caused something in Dash to break, and Flask/Werkzeug 2.3 released this week is no exception to that. This time both Dash a few versions back (eg v2.6.1) and the latest versions some of our enterprise packages are broken by the new Flask/Werkzeug.

So in order to make all of those standalone Dash apps more robust and maintainable, we’re planning to start locking Flask and Werkzeug via install_requires to versions that we know work with Dash and all the other packages maintained by Plotly.

After we release that (expected in Dash 2.10.0):

  • If you use Dash standalone and have NOT pinned Flask/Werkzeug in your own app, you don’t need to do anything, there will just be one less thing that might break your app in the future.
  • If you use Dash standalone and your DO pin Flask/Werkzeug in your app (we’ve had to recommend this many times to work around these problems in the past), just remove those lines and let Dash choose these versions. If you keep pinning those and the versions differ from what Dash requires, Dash will refuse to update to 2.10.0 or beyond.
  • If you are adding Dash to your Flask app, this means you’ll need to use exactly the version required by Dash, and when you update Dash, if Dash updates Flask it will affect your app as well.

Comments are welcome! We recognize that for users adding Dash to an existing Flask app this is not ideal, but we’ve been burned too many times by Flask updates, something needs to change.

15 Likes

I haven’t used Plotly in a long time, but that was one of my irritants–FLASK. So if I want to try Plotly again I’m happy to hear it’s one less thing to worry about.

We use very controlled environments that we don’t change very often. I can imagine this causing issues by dash (used by a few apps) driving the version of flask (used more widely).

I do understand why you are doing it though.

1 Like

Have you considered generating a constraints file similar to what apache-airflow does? Installation from PyPI — Airflow Documentation

This way for each version of Dash you can provide a set of known working dependencies and transitive dependencies that is recommended for users that use Dash as an application. But for users who use Dash as a library they have more flexibility and can include or not include these constraints based on their own needs, but know if they don’t it falls outside what has been tested for Dash.

2 Likes

Looks like an excellent solution to the problem of Flask updates breaking dash.

1 Like

Edited the post: @mingosanch pointed out that even though the latest version of Dash is OK with Flask v2.3, a couple of versions back, Dash v2.6.1, was working last week with the latest Flask but breaks with Flask v2.3

I hadn’t seen exactly that pattern, but their note about it certainly resonates with Dash and this discussion:

Airflow installation can be tricky sometimes because Airflow is both a library and an application. Libraries usually keep their dependencies open and applications usually pin them, but we should do neither and both at the same time.

We did consider a related pattern: adding a new extras_require like:

pip install dash[lockflask]

Where without [lockflask] you’d have the Flask/Werkzeug versions floating, but with [lockflask] it would be pinned. That would certainly be a less intrusive way to do it, and before this week it’s the one I was advocating. The problem with that is it goes against our general design philosophy, which is something like “The common things should be easy, the uncommon things should be possible.” It makes the thing everyone wants to do (just install and use the package!) more complicated and confusing in the name of greater flexibility for the less common use case of adding Dash to an existing Flask app, but we still can’t guarantee that Dash will work inside that Flask app. Whereas if we lock Flask versions, the more common case will just work, and so will the Dash-in-a-Flask-app case, it just requires the Flask app to conform to our version of Flask.

1 Like

Lock forever or lock until it makes sense to lock to a new version?

Either way, I’ve been grateful to benefit from this tool in so many ways I fully support you all making your lives easier! I would be curious if there is a “use at your own risk” option for unsupported versions by installing the package from source and making a code edit somewhere? Maybe to satisfy the “uncommon things should be possible” ethos? I would by no means suggest this be a priority though.

All that said, seems this is for the greater good. Dash is the star of the show for my work - I only integrate with Flask to handle the authentication - I suspect i’ll be just fine with a locked version.

1 Like

We would advance along with Flask, but naturally we’d be a bit behind as it would wait for our next minor release. And if it caused headaches elsewhere in the Dash ecosystem I imagine we might delay a little more to give ourselves time to clean that up first. So in as far as Flask changes that break Dash might also break other Flask apps using Dash, this could make those apps more stable as well. Or maybe that’s just me rationalizing… regardless, we wouldn’t be locking to the same version forever.

Yes of course, that’s always possible! Just change exactly the same lines as in Philippe’s PR :slight_smile:

1 Like

My thinking is that instead of an exact version number, it could be a range.

This would allow flexibility for people who have a Flask version that arent looking to upgrade to allow for their Dash package to be updated.

Philippe’s PR is locking it into 2.2.2, which we know is working. But we know that there are versions that work together from 1.04 to 2.2.2. >=1.0.4, <2.2.3 would allow for these all to be available as viable options. Then, if Dash has to change its code to work with Flask 2.3.* and no longer supports the previous versions, you could update the requirements and potentially split Dash versions. Supporting an older version for >=1.0.4, <2.2.3 and >2.2.2, <=2.2.3, allowing the range to move up until there is another breaking split.

This would satisfy the requirement of pip install dash installing a working version of the app, without allowing for unknown working/breaking versions. But also allow for people who use Flask to not have to upgrade their versions in order to make use of the latest version of Dash.

3 Likes

That’s a possibility. I wonder though, in the end it may be harder as a user than a completely locked version. On the plus side it should reduce the problem of holding folks with pinned Flask on an old Dash version, and would give more flexibility to the Dash-in-a-Flask-app use case. But I’ve seen problems of mismatched Flask and Werkzeug versions - take at look at the Flask 2.2.4 changelog, it exists only because 2.2.3 is incompatible with Werkzeug 2.3 - and this situation can happen in unexpected ways. And for folks who pin Dash but don’t pay attention to the Flask version it makes environments less consistent - an old environment that you upgrade may keep the old Flask whereas a new env would get the newest.

It’s also more for us to test - are we really still compatible back to 1.0.4? I don’t think we’ve tested that in ages, and changes we’ve made to support newer Flask may have broken Dash with older Flask.

All that to say it’s worth considering, but not a clear-cut improvement over exact version locking.

1 Like

To coincide with this part of it, you could suggest upgrading dash via pip install —upgrade dash plotly flask werkzeug especially since you already have some requirements to upgrade plotly alongside dash.

Sounds like a recipe to get users who want to combine Dash with their own software and library into certain dependency hell. The problem with pinning versions often is not so much that the downstream app developer would need a different version of the pinned library, but that this in turn causes other library versions to get limited in a way which makes it almost impossible to use the mix of libraries the downstream app needs.

1 Like

Alex and forum,
I appreciate this type of communication a lot, since it really helps me understand what is going on and the rationale behind it.

I understand the pain point. Yet one concern remains, related to cyber security. Once the versions of Flask and Werkzeug are frozen, how are we going to keep up removing vulnerabilities to be discovered on the frozen, becoming older versions? This will be critical for any service exposed to public consumption.

I propose not to completely freeze the versions, but to space their upgrades. Also, explore collaboration with Flask and Werkzeug teams, explosing the issue and looking for them to improve their versioning practices, which seems to be at the root of the problem.

I hope this helps.

2 Likes

I believe the main benefit and differentiator of Dash is standardizing and simplifying decisions for small data science teams that are bootstrapping apps to improve the visibility of their work. Locking the Flask version further aligns with this benefit.

3 Likes

I’m in support of @carloslang point. I hope version requires or even ranges could be last resort. I see it kind of like PPE in the heirarchy of controls, are there other options that could be used instead (or together) to eliminate the hazard?

Maybe one can’t replace flask with quart or fast-api but David and the Flask team may have ideas or would be willing to help? Perhaps a process with RCs or incorporating tests for dash within the flask codebase?

I’m really glad to see such enthusiastic development of flask and quart at the moment, it’s heartening although unfortunately causing headaches for dash.

Absolutely - it wasn’t clear in the original post, but that’s what I meant in my comment above by “We would advance along with Flask, but naturally we’d be a bit behind as it would wait for our next minor release.”

Interesting analogy! From that chart I see it more as an engineering control from the standpoint of users, and an administrative control from the standpoint of us maintainers… but your point is well taken, including about replacing or allowing users to replace Flask with other frameworks. This has come up before, and it may be that if we think about what that would require of our architecture it will point to a solution that will work for bring-your-own-Flask-version as well, while keeping the primary installation pathway simple and clean.

I actually don’t really like Flask. My dislike of Flask is why I started looking at things like Dash, which abstract that away for me. From my pov, you can even switch to something else entirely and I wouldn’t care and in fact would understand.

Update: we researched a number of options for this, including creating a separate package dash-core that only specifies a minimum version of Flask while the dash package becomes just a wrapper that installs dash-core and pins Flask and Werkzeug. The problem with this is that many popular component packages - both Plotly-authored and third-party components - list dash as a dependency, so any app using one of these will inherit the version locking until they all update to depend on dash-core instead. This would get us into lots of confusing situations where dependence on dash and a newer Flask either won’t resolve or will push you back to dash@2.9.

So what we’ve decided to do instead is just add an upper bound - like @jinnyzor suggested above.

Here’s the note I wrote in the PR that will do this, with a bit more detail about the rationale and plan going forward:

We’re well aware of the arguments against upper version bounds - articulated most clearly perhaps in Should You Use Upper Bound Version Constraints? - - let me just point out one line in the intro of that piece:

After reading this, hopefully you will always consider every cap you add, you will know the (few) places where pinning an upper limit is reasonable

I think we have ample evidence at this point that this is such a place. From our experience with the last several Flask minor releases, this particular upper bound expresses a true statement about Flask & Dash: We expect the Dash ecosystem to be incompatible with the next minor release of Flask. We know that this is true for Flask 2.3 already, and we are working to fix the incompatibilities in our related libraries, after which we will bump these upper limits to <2.4. Note that we need to limit both Flask and Werkzeug, because even though they’re normally released synchronously with matching version numbers, Flask depends on Werkzeug>={matching version}

After we make this change, we will institute a process upon every new Flask minor release where we thoroughly test it across the Dash ecosystem, make any required changes, and then bump the upper version constraint. In general we will bump to <{next minor} rather than <={known good patch} as we’re doing here, just because the later patches Flask@2.2.4 and Flask@2.2.5 that currently exist are primarily about forward compatibility with Werkzeug@2.3.x which we are not supporting in the upcoming Dash release.

5 Likes

Dash v2.10.0, released yesterday, limits Flask and Werkzeug to v2.2.3. Thanks everyone for your input.

One additional situation to be aware of going forward: If you specifically ask for the latest Flask (any newly-released version of Flask that we haven’t yet approved) alongside Dash with no version, you’ll get pushed back to dash==2.9.3, the last version without an upper bound on Flask. To avoid this and still pin a Flask version, we recommend setting a minimum Dash version at least 2.10, so that instead of getting the older Dash you’ll be alerted to the conflict:

> pip install "dash>=2.10" "Flask>=2.3"
Collecting dash>=2.10
  Downloading dash-2.10.0-py3-none-any.whl (10.3 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.3/10.3 MB 17.3 MB/s eta 0:00:00
Collecting Flask>=2.3
  Using cached Flask-2.3.2-py3-none-any.whl (96 kB)
INFO: pip is looking at multiple versions of dash to determine which version is compatible with other requirements. This could take a while.
ERROR: Cannot install Flask>=2.3 and dash==2.10.0 because these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested Flask>=2.3
    dash 2.10.0 depends on Flask<=2.2.3 and >=1.0.4

To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip attempt to solve the dependency conflict
1 Like