Changelog for Falcon 4.1.0
==========================

.. falcon-release: 2025-08-06

Summary
-------

This release contains enhancements to media handling, serving static files, and
a fix for the WebSockets-sink interaction, alongside performance optimizations
and full support for CPython 3.14.

During this release cycle, we have migrated to publishing to PyPI with a
`Trusted Publisher <https://docs.pypi.org/trusted-publishers/>`__ (thanks to
`@webknjaz <https://github.com/webknjaz>`__ for helping to iron out the
workflow details).

For those relying on other package distribution channels than PyPI, we have
prepared a brand new :doc:`/community/packaging` for Falcon.
Please check it out and let us know what you think!
Additionally, we have formalized our security maintenance policy as well as the
status of stable releases: :doc:`/community/releases`.

This release also incorporates many pull requests submitted by our
community. We would like to extend our heartfelt thanks to all 17 contributors
who made this release possible!


Changes to Supported Platforms
------------------------------

- CPython 3.14 is now fully supported.
  (`#2413 <https://github.com/falconry/falcon/issues/2413>`__)
- Although the Falcon 4.x series is only guaranteed to support Python 3.10+,
  this release still supports 3.8 & 3.9 at runtime using the pure Python wheel.

  Falcon 4.2 is expected to drop the end-of-life Python 3.8 completely
  (but runtime support will continue for 3.9 on a best effort basis).


New & Improved
--------------

- :class:`~falcon.routing.StaticRoute` now renders ``Etag`` headers. It also
  checks ``If-None-Match`` in requests and returns HTTP 304 response if
  appropriate. (`#2243 <https://github.com/falconry/falcon/issues/2243>`__)
- :class:`~falcon.routing.StaticRoute` now sets the ``Last-Modified`` header when
  serving static files. The improved implementation also checks the value of the
  ``If-Modified-Since`` header, and renders an HTTP 304 response when the
  requested file has not been modified. (`#2244 <https://github.com/falconry/falcon/issues/2244>`__)
- Similar to :func:`~falcon.testing.create_environ`,
  the :func:`~falcon.testing.create_scope` testing helper now preserves the raw URI path,
  and propagates it to the created ASGI connection scope as the ``raw_path`` byte string
  (according to the `ASGI specification
  <https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope>`__). (`#2262 <https://github.com/falconry/falcon/issues/2262>`__)
- Two new :ref:`media_type constants <media_type_constants>`,
  ``falcon.MEDIA_CSV`` and ``falcon.MEDIA_PARQUET``, were added in order to
  provide better support for Python data analysis applications out of the box. (`#2335 <https://github.com/falconry/falcon/issues/2335>`__)
- Support for allowing :ref:`cross-origin <cors>`
  `private network access <https://wicg.github.io/private-network-access/>`__ was
  added to the built-in :class:`~falcon.middleware.CORSMiddleware`.
  The new feature is off by default, and can be enabled by passing the keyword
  argument ``allow_private_network=True`` to
  :class:`~falcon.middleware.CORSMiddleware` during initialization. (`#2381 <https://github.com/falconry/falcon/issues/2381>`__)
- The :func:`falcon.secure_filename()` utility function can now ensure that the
  length of the sanitized filename does not exceed the requested limit (passed
  via the ``max_length`` argument). In addition, a new option,
  ``max_secure_filename_length``, was added to
  :class:`~falcon.media.multipart.MultipartParseOptions` in order to
  automatically populate this argument when referencing a body part's
  :attr:`~falcon.media.multipart.BodyPart.secure_filename`. (`#2420 <https://github.com/falconry/falcon/issues/2420>`__)
- The :meth:`~falcon.Response.unset_cookie` method now accepts a ``same_site``
  parameter (with underscore) for consistency with :meth:`~falcon.Response.set_cookie`.
  The previous ``samesite`` parameter (without underscore) is now deprecated
  (referencing it will emit a deprecation warning). (`#2453 <https://github.com/falconry/falcon/issues/2453>`__)
- A new method, ``__rich__``, has been added to :class:`falcon.testing.Result`
  for facilitating a rich-text representation when used together with the popular
  `rich <https://rich.readthedocs.io/>`__ library.

  Provided you have installed both ``falcon`` and ``rich`` into your environment,
  you should be able to see a prettier rendition of the below 404-result:

  >>> import falcon
  >>> import falcon.testing
  >>> import rich.pretty
  >>> rich.pretty.install()
  >>> client = falcon.testing.TestClient(falcon.App())
  >>> client.get('/endpoint')
  Result<404 Not Found application/json b'{"title": "404 Not Found"}'>

  (The actual appearance may depend on your terminal and/or REPL settings.) (`#2457 <https://github.com/falconry/falcon/issues/2457>`__)
- The :ref:`cythonization <cythonize>` process was revised in the light of the
  performance improvements in newer CPython versions (especially 3.12+), and the
  compilation is now largely confined to hand-crafted C/Cython code.
  As a result, the framework should run even faster on modern CPython. (`#2470 <https://github.com/falconry/falcon/issues/2470>`__)
- :class:`~falcon.media.JSONHandler` can now detect a non-standard
  (not a subclass of :class:`ValueError`) deserialization error type for a custom
  `loads` function.

  (Normally, :func:`json.loads` and third party alternatives do raise a subclass
  of :class:`ValueError` on invalid input data, however, this is not the case
  for, e.g., the popular :ref:`msgspec <msgspec_recipe>` library
  at the time of writing.) (`#2476 <https://github.com/falconry/falcon/issues/2476>`__)


Fixed
-----

- Previously, Falcon's :ref:`WebSocket implementation <ws>` was not documented to
  route the request to any :meth:`sink <falcon.asgi.App.add_sink>`. However, in
  the case of a missing route, a matching sink was actually invoked, passing
  :class:`ws <falcon.asgi.WebSocket>` in place of the incompatible
  :class:`resp <falcon.asgi.Response>`.

  This mismatch has been addressed by introducing a `ws` keyword argument
  (similar to ASGI :meth:`error handlers <falcon.asgi.App.add_error_handler>`)
  for sink functions meant to accept WebSocket connections.

  For backwards-compatibility, when `ws` is absent from the sink's signature, the
  :class:`~falcon.asgi.WebSocket` object is still passed in place of the
  incompatible `resp`.
  This behavior will change in Falcon 5.0: when draining a WebSocket connection,
  `resp` will always be set to ``None``. (`#2414 <https://github.com/falconry/falcon/issues/2414>`__)


Misc
----

- The readability of the :doc:`Contributing docs </community/contributing>` was
  improved by properly rendering GitHub Markdown-flavored checkboxes. (`#2318 <https://github.com/falconry/falcon/issues/2318>`__)
- The ``falcon.testing.httpnow`` compatibility alias is now considered
  deprecated, and will be removed in Falcon 5.0.
  Use the :func:`falcon.http_now` function instead. (`#2389 <https://github.com/falconry/falcon/issues/2389>`__)





Contributors to this Release
----------------------------

Many thanks to all of our talented and stylish contributors for this release!

- `aarcex3 <https://github.com/aarcex3>`__
- `AbduazizZiyodov <https://github.com/AbduazizZiyodov>`__
- `Bombaclath97 <https://github.com/Bombaclath97>`__
- `bssyousefi <https://github.com/bssyousefi>`__
- `CaselIT <https://github.com/CaselIT>`__
- `Cycloctane <https://github.com/Cycloctane>`__
- `diegomirandap <https://github.com/diegomirandap>`__
- `EricGoulart <https://github.com/EricGoulart>`__
- `jap <https://github.com/jap>`__
- `jkmnt <https://github.com/jkmnt>`__
- `kemingy <https://github.com/kemingy>`__
- `Krishn1412 <https://github.com/Krishn1412>`__
- `perodriguezl <https://github.com/perodriguezl>`__
- `Shreshth3 <https://github.com/Shreshth3>`__
- `vytas7 <https://github.com/vytas7>`__
- `webknjaz <https://github.com/webknjaz>`__
- `x612skm <https://github.com/x612skm>`__
