Hi all. I noticed there haven't been releases (as in https://github.com/pallets/quart/releases ) for the past 2 years. It would be really helpful to know whether there are important updates, CVEs, breaking changes, etc for recent releases.
I created some for the past releases below to help smooth this along if you agree there should be releases listed on the repo. I also attached this below as Markdown in case that is helpful. quart-release-notes.md
Quart release notes, 0.21.0 through 0.23.1
Reconstructed from the git history and CHANGES.md, with dates taken from PyPI.
0.21.0: Python 3.14 support and a session key rotation fix
Released 2026-07-23
Seven months after 0.20.0, and mostly a catch-up with Flask. The supported Python range moves to 3.11 through 3.14, and a Flask security fix for secret key rotation comes across with it.
Security
- Backported the Flask fix for signing key selection order when key rotation is enabled through
SECRET_KEY_FALLBACKS (GHSA-4grg-w6v8-c28g). itsdangerous expects the oldest key first and the active signing key last, and Quart had the list the other way round, so sessions were being signed with a fallback key rather than the current one. If you use SECRET_KEY_FALLBACKS, this is the release you want.
Python versions
- Added support for Python 3.14, including a CI run against the free-threaded build.
- Dropped Python 3.9 and 3.10. The floor is now 3.11, and the
importlib-metadata and typing-extensions conditional dependencies are gone with them.
Added
SESSION_COOKIE_PARTITIONED config value, matching Flask. Setting it marks the session cookie as partitioned (CHIPS), which is what lets a third-party cookie work on the top-level site that set it instead of being blocked outright.
--debug / --no-debug flags for quart run, matching the Flask CLI.
Quart.default_config is now built from Flask.default_config rather than being maintained as a separate literal, so Flask config keys arrive automatically instead of drifting apart.
Fixed
app.run() now detects URI schemes that do not need a port and configures the server accordingly, rather than forcing a port onto them.
max_form_parts is no longer applied to non-multipart forms. URL-encoded forms are cheap to parse, so capping the part count there only rejected valid requests.
- Exception propagation was corrected. (Reverted in 0.22.0, once it turned out to conflate propagation with traceback rendering. See below.)
Typing and docs
- Removed the invalid uses of
AnyStr.
- Added detailed type hints for
Request.files, and documented the type.
template_folder now accepts os.PathLike, for parity with Flask.
- Assorted documentation fixes:
add_websocket example code, server-sent events, the event loop page, the async generator section, and the blog example's pathlib usage.
0.22.0: Logging, root_path, and traceback corrections
Released 2026-08-19
Four weeks later, three bug fixes. One of them backs out a change from 0.21.0.
Fixed
app.run() now logs through Quart's logger instead of Hypercorn's. Customising app.logger previously had no effect on development server output, which was surprising.
- Fixed request paths when
root_path is set and the application is not mounted at the root. The old code did path.split(root_path, 1)[1], which split on the first occurrence of the root path anywhere in the URL, so a path that happened to repeat the mount string was rewritten incorrectly. It now checks the prefix and uses str.removeprefix.
- Reverted the exception propagation change from 0.21.0. Whether a traceback is rendered is a decision for the ASGI layer and should not be driven by
PROPAGATE_EXCEPTIONS; a traceback is now shown when debug or testing is set.
Performance
- Small improvements to the global proxies and to
Response.
Docs
- Documented the ordering in which the serving functions are called.
- Added a missing default argument to the docs.
0.23.0: New reloader, merged contexts, and real disconnect handling
Released 2026-08-29. Yanked from PyPI, use 0.23.1 instead.
The biggest of these four releases, and the one that breaks things. The development reloader is rewritten, the request and websocket contexts collapse into a single AppContext as they did in Flask, and the ASGI layer is reworked around task groups and queue shutdown.
It was yanked hours later over a stray debug print (see 0.23.1). Everything below still applies. Install 0.23.1 to get it.
Breaking changes
- The contexts are merged.
RequestContext and WebsocketContext are gone, along with the request_ctx and websocket_ctx proxies in quart.globals. There is now one AppContext carrying optional request, websocket and session data, with has_request and has_websocket properties. The public top-level API is unchanged: request, websocket, session, g, current_app, has_request_context(), has_websocket_context() and the copy_current_*_context decorators all work as before. Code that reached into quart.ctx or quart.globals directly will need updating.
g is no longer copied when a context is copied. This follows an explicit Flask decision. Work started with copy_current_request_context now gets a fresh g, so anything stashed there before the copy will not be visible inside the background task.
- Python 3.11 and 3.12 are no longer supported. The floor is now 3.13, which is what makes
asyncio.Queue.shutdown available for the disconnect handling below.
- Client disconnects raise
ClientDisconnectedError instead of CancelledError. Reads and sends against a disconnected client raise the new exception, which is exported from quart.wrappers. Handlers may also run for longer after a disconnect than they used to, since the teardown path changed.
Reloader
- The development reloader now runs the app in a subprocess and reloads it continually, the way Werkzeug's does. A syntax error is recoverable: fix the file and the server comes back, instead of the whole process dying.
- Signal handling no longer applies to the reloading parent process, so Ctrl+C works even while the app is failing to import.
- The reloader now uses
sys.orig_argv rather than reconstructing the command line, which should retire a long tail of reloader bugs.
- A return code of 3 tells the reloader to stop and shut down. (Werkzeug uses 3 for the opposite meaning, so this is worth noting if you script around it.)
ASGI layer
- HTTP and websocket connections are now driven by an
asyncio.TaskGroup instead of asyncio.wait plus manual cancellation.
- Request bodies flow through an
asyncio.Queue with shutdown() rather than being accumulated into a bytearray, so a disconnect closes the queue and surfaces as ClientDisconnectedError. Body.append is replaced by await Body.put() and await Body.get().
- Websockets gained a
Buffer class, overridable through Websocket.buffer_class, in place of the queue that lived on the ASGI connection object.
- Supports ASGI WWW 2.4, where sending after disconnection raises
OSError; that is converted to ClientDisconnectedError.
Fixed
- All teardown functions now run even if one of them raises. Errors are collected and re-raised together in an
ExceptionGroup instead of the first one aborting the rest, so teardown functions are free to fail. (The _CollectErrors helper is taken from Flask's main branch, ahead of a Flask release.)
- Blueprint
teardown_websocket functions were never registered on the app. They are now.
0.23.1: Removes a debug print that echoed request bodies
Released 2026-08-29
0.23.0 shipped with a print(data) left in Body.__await__, in src/quart/wrappers/request.py. Every call that awaited a request body printed the accumulated bytes to stdout, once per chunk. On a busy server that is a lot of noise, and on any server it means request bodies land in the process output and from there into logs: form posts, JSON payloads, credentials and all. 0.23.0 was yanked for it.
- Removed the
print from Body.__await__, plus two others in an example and a test.
- Enabled ruff's
T20 (flake8-print) rules so a stray print fails lint rather than reaching a release.
There are no other changes; 0.23.1 is 0.23.0 with the print removed.
Notes for the maintainer
Two small things turned up while reading through the history.
docs/discussion/python_versions.rst currently says releases from 0.22.0 onward require Python 3.13. The floor actually moved in 0.23.0. 0.22.0 still declares requires-python = ">=3.11". The line was edited as part of the backpressure commit, which was written before the release it landed in was decided.
ClientDisconnectedError is exported from quart.wrappers but not from the top-level quart package. Anyone catching it has to reach for the submodule, which is a slightly awkward import for what is now the ordinary way a disconnect surfaces to application code.
Hi all. I noticed there haven't been releases (as in https://github.com/pallets/quart/releases ) for the past 2 years. It would be really helpful to know whether there are important updates, CVEs, breaking changes, etc for recent releases.
I created some for the past releases below to help smooth this along if you agree there should be releases listed on the repo. I also attached this below as Markdown in case that is helpful. quart-release-notes.md
Quart release notes, 0.21.0 through 0.23.1
Reconstructed from the git history and
CHANGES.md, with dates taken from PyPI.0.21.0: Python 3.14 support and a session key rotation fix
Released 2026-07-23
Seven months after 0.20.0, and mostly a catch-up with Flask. The supported Python range moves to 3.11 through 3.14, and a Flask security fix for secret key rotation comes across with it.
Security
SECRET_KEY_FALLBACKS(GHSA-4grg-w6v8-c28g).itsdangerousexpects the oldest key first and the active signing key last, and Quart had the list the other way round, so sessions were being signed with a fallback key rather than the current one. If you useSECRET_KEY_FALLBACKS, this is the release you want.Python versions
importlib-metadataandtyping-extensionsconditional dependencies are gone with them.Added
SESSION_COOKIE_PARTITIONEDconfig value, matching Flask. Setting it marks the session cookie as partitioned (CHIPS), which is what lets a third-party cookie work on the top-level site that set it instead of being blocked outright.--debug/--no-debugflags forquart run, matching the Flask CLI.Quart.default_configis now built fromFlask.default_configrather than being maintained as a separate literal, so Flask config keys arrive automatically instead of drifting apart.Fixed
app.run()now detects URI schemes that do not need a port and configures the server accordingly, rather than forcing a port onto them.max_form_partsis no longer applied to non-multipart forms. URL-encoded forms are cheap to parse, so capping the part count there only rejected valid requests.Typing and docs
AnyStr.Request.files, and documented the type.template_foldernow acceptsos.PathLike, for parity with Flask.add_websocketexample code, server-sent events, the event loop page, the async generator section, and the blog example's pathlib usage.0.22.0: Logging, root_path, and traceback corrections
Released 2026-08-19
Four weeks later, three bug fixes. One of them backs out a change from 0.21.0.
Fixed
app.run()now logs through Quart's logger instead of Hypercorn's. Customisingapp.loggerpreviously had no effect on development server output, which was surprising.root_pathis set and the application is not mounted at the root. The old code didpath.split(root_path, 1)[1], which split on the first occurrence of the root path anywhere in the URL, so a path that happened to repeat the mount string was rewritten incorrectly. It now checks the prefix and usesstr.removeprefix.PROPAGATE_EXCEPTIONS; a traceback is now shown when debug or testing is set.Performance
Response.Docs
0.23.0: New reloader, merged contexts, and real disconnect handling
Released 2026-08-29. Yanked from PyPI, use 0.23.1 instead.
The biggest of these four releases, and the one that breaks things. The development reloader is rewritten, the request and websocket contexts collapse into a single
AppContextas they did in Flask, and the ASGI layer is reworked around task groups and queue shutdown.It was yanked hours later over a stray debug
print(see 0.23.1). Everything below still applies. Install 0.23.1 to get it.Breaking changes
RequestContextandWebsocketContextare gone, along with therequest_ctxandwebsocket_ctxproxies inquart.globals. There is now oneAppContextcarrying optional request, websocket and session data, withhas_requestandhas_websocketproperties. The public top-level API is unchanged:request,websocket,session,g,current_app,has_request_context(),has_websocket_context()and thecopy_current_*_contextdecorators all work as before. Code that reached intoquart.ctxorquart.globalsdirectly will need updating.gis no longer copied when a context is copied. This follows an explicit Flask decision. Work started withcopy_current_request_contextnow gets a freshg, so anything stashed there before the copy will not be visible inside the background task.asyncio.Queue.shutdownavailable for the disconnect handling below.ClientDisconnectedErrorinstead ofCancelledError. Reads and sends against a disconnected client raise the new exception, which is exported fromquart.wrappers. Handlers may also run for longer after a disconnect than they used to, since the teardown path changed.Reloader
sys.orig_argvrather than reconstructing the command line, which should retire a long tail of reloader bugs.ASGI layer
asyncio.TaskGroupinstead ofasyncio.waitplus manual cancellation.asyncio.Queuewithshutdown()rather than being accumulated into abytearray, so a disconnect closes the queue and surfaces asClientDisconnectedError.Body.appendis replaced byawait Body.put()andawait Body.get().Bufferclass, overridable throughWebsocket.buffer_class, in place of the queue that lived on the ASGI connection object.OSError; that is converted toClientDisconnectedError.Fixed
ExceptionGroupinstead of the first one aborting the rest, so teardown functions are free to fail. (The_CollectErrorshelper is taken from Flask's main branch, ahead of a Flask release.)teardown_websocketfunctions were never registered on the app. They are now.0.23.1: Removes a debug print that echoed request bodies
Released 2026-08-29
0.23.0 shipped with a
print(data)left inBody.__await__, insrc/quart/wrappers/request.py. Every call that awaited a request body printed the accumulated bytes to stdout, once per chunk. On a busy server that is a lot of noise, and on any server it means request bodies land in the process output and from there into logs: form posts, JSON payloads, credentials and all. 0.23.0 was yanked for it.printfromBody.__await__, plus two others in an example and a test.T20(flake8-print) rules so a strayprintfails lint rather than reaching a release.There are no other changes; 0.23.1 is 0.23.0 with the print removed.
Notes for the maintainer
Two small things turned up while reading through the history.
docs/discussion/python_versions.rstcurrently says releases from 0.22.0 onward require Python 3.13. The floor actually moved in 0.23.0. 0.22.0 still declaresrequires-python = ">=3.11". The line was edited as part of the backpressure commit, which was written before the release it landed in was decided.ClientDisconnectedErroris exported fromquart.wrappersbut not from the top-levelquartpackage. Anyone catching it has to reach for the submodule, which is a slightly awkward import for what is now the ordinary way a disconnect surfaces to application code.