|
3 | 3 | Litestar 2 Changelog |
4 | 4 | ==================== |
5 | 5 |
|
| 6 | +.. changelog:: 2.22.0 |
| 7 | + :date: 2026-05-20 |
| 8 | + |
| 9 | + .. change:: Fix HTML injection in template engine through ``csrf_input`` |
| 10 | + :type: bugfix |
| 11 | + |
| 12 | + Fix a CSRF vulnerability due to an unescaped ``csrf_input`` tag provided by |
| 13 | + Litestar. When using ``CSRFConfig`` and providing the CSRF token via a cookie, |
| 14 | + the cookie value was not properly escaped, which could lead to an attacker |
| 15 | + being able to craft a cookie containing malicious HTML, which would be rendered |
| 16 | + on the client side. |
| 17 | + |
| 18 | + .. change:: Deprecate ``litestar.contrib.opentelemetry`` in favour of ``litestar.plugins.opentelemetry`` |
| 19 | + :type: feature |
| 20 | + :pr: 4759 |
| 21 | + |
| 22 | + Deprecate ``litestar.contrib.opentelemetry`` in favour of |
| 23 | + :mod:`litestar.plugins.opentelemetry`. |
| 24 | + The contrib location will be removed in 3.0.0. |
| 25 | + |
| 26 | + .. change:: Deprecate ``litestar.repository`` in favor of ``advanced_alchemy`` equivalents |
| 27 | + :type: feature |
| 28 | + :pr: 4758 |
| 29 | + :issue: 4721 |
| 30 | + |
| 31 | + Deprecate ``litestar.repository`` and its submodules (``filters``, |
| 32 | + ``exceptions``, ``handlers``, ``abc``, ``testing``) in favor of |
| 33 | + ``advanced_alchemy``. The module will be removed before 3.0.0. |
| 34 | + |
| 35 | + .. change:: Deprecate ``litestar.contrib.jinja`` in favor of ``litestar.plugins.jinja`` |
| 36 | + :type: feature |
| 37 | + :pr: 4773 |
| 38 | + |
| 39 | + Deprecate ``litestar.contrib.jinja`` in favor of :mod:`litestar.plugins.jinja`. |
| 40 | + The contrib location will be removed in 3.0.0. |
| 41 | + |
| 42 | + |
| 43 | + .. change:: Deprecate ``litestar.contrib.mako`` in favor of ``litestar.plugins.mako`` |
| 44 | + :type: feature |
| 45 | + :pr: 4774 |
| 46 | + |
| 47 | + Deprecate ``litestar.contrib.mako`` in favor of :mod:`litestar.plugins.mako`. |
| 48 | + The contrib location will be removed in 3.0.0. |
| 49 | + |
| 50 | + .. change:: Deprecate ``litestar.contrib.minijinja`` in favor of ``litestar.plugins.minijinja`` |
| 51 | + :type: feature |
| 52 | + :pr: 4775 |
| 53 | + |
| 54 | + Deprecate ``litestar.contrib.minijinja`` in favor of |
| 55 | + :mod:`litestar.plugins.minijinja`. |
| 56 | + The contrib location will be removed in 3.0.0. |
| 57 | + |
| 58 | + .. change:: Add ``oauth2_redirect_url`` parameter to ``SwaggerRenderPlugin`` |
| 59 | + :type: feature |
| 60 | + :pr: 4676 |
| 61 | + |
| 62 | + Add an ``oauth2_redirect_url`` parameter to |
| 63 | + :class:`~litestar.openapi.plugins.SwaggerRenderPlugin`. When set, the value |
| 64 | + is emitted as ``oauth2RedirectUrl`` in the Swagger UI bundle config, |
| 65 | + overriding the default behavior of computing the URL from the current page |
| 66 | + location. This allows the OAuth2 redirect to be set explicitly when the API |
| 67 | + is mounted under a sub-path. |
| 68 | + |
| 69 | + .. change:: Enhanced request parameter declarations |
| 70 | + :type: feature |
| 71 | + :pr: 4750 |
| 72 | + |
| 73 | + Introduce a new way to declare parameters, with improved typing ergonomics. |
| 74 | + These replaces / enhance the existing parameter declaration mechanics, and |
| 75 | + deprecate the implicit declaration style for query and path parameters. |
| 76 | + |
| 77 | + New parameter markers generics: |
| 78 | + |
| 79 | + - :data:`FromQuery[\<type\>] <litestar.params.FromQuery>` |
| 80 | + - :data:`FromPath[\<type\>] <litestar.params.FromPath>` |
| 81 | + - :data:`FromCookie[\<type\>] <litestar.params.FromCookie>` |
| 82 | + - :data:`FromHeader[\<type\>] <litestar.params.FromHeader>` |
| 83 | + |
| 84 | + And their explicit ``Annotated`` form: |
| 85 | + |
| 86 | + - :class:`~litestar.params.QueryParameter` |
| 87 | + - :class:`~litestar.params.HeaderParameter` |
| 88 | + - :class:`~litestar.params.CookieParameter` |
| 89 | + - :class:`~litestar.params.PathParameter` |
| 90 | + |
| 91 | + **Rationale** |
| 92 | + |
| 93 | + Given: |
| 94 | + |
| 95 | + .. code-block:: python |
| 96 | +
|
| 97 | + @get("/") |
| 98 | + async def handler(one: str, two: str) -> None: |
| 99 | + pass |
| 100 | +
|
| 101 | + router = Router("/{two:str}", route_handlers=[handler]) |
| 102 | +
|
| 103 | + ``one`` is implicitly created as a query parameter, while ``two`` is a path |
| 104 | + parameter declared on the router. Including ``handler`` in a different |
| 105 | + context *without* that path parameter would implicitly treat ``two`` as a |
| 106 | + query parameter as well. Using explicit markers, the source of each |
| 107 | + parameter is clear and errors can be caught earlier: |
| 108 | + |
| 109 | + .. code-block:: python |
| 110 | +
|
| 111 | + @get("/") |
| 112 | + async def handler(one: FromQuery[str], two: FromPath[str]) -> None: |
| 113 | + pass |
| 114 | +
|
| 115 | + router = Router("/{two:str}", route_handlers=[handler]) |
| 116 | +
|
| 117 | + **Migration** |
| 118 | + |
| 119 | + - Replace unmarked query / path parameters in handler and dependency signatures |
| 120 | + with ``FromQuery[<type>]``/ ``FromPath[<type]`` markers |
| 121 | + - Replace ``Parameter(query=...)`` with ``QueryParameter(name=...)`` |
| 122 | + - Replace ``Parameter(header=...)`` with ``HeaderParameter(name=...)`` |
| 123 | + - Replace ``Parameter(cookie=...)`` with ``CookieParameter(name=...)`` |
| 124 | + - Hoist defaults: ``param: int = Parameter(default="x")`` -> ``param: FromQuery[int] = "x"`` |
| 125 | + |
| 126 | + .. seealso:: |
| 127 | + |
| 128 | + :doc:`/usage/routing/parameters` |
| 129 | + |
| 130 | + .. change:: Validate missing and invalid claims in JWT decoding |
| 131 | + :type: bugfix |
| 132 | + :pr: 4636 |
| 133 | + |
| 134 | + Fix :meth:`~litestar.security.jwt.Token.decode` leaking :exc:`KeyError` or |
| 135 | + :exc:`TypeError` when required claims (``sub``, ``exp``, ``iat``) are |
| 136 | + missing or malformed and claim verification is disabled. Decoding now |
| 137 | + raises :exc:`~litestar.exceptions.NotAuthorizedException` consistently in |
| 138 | + these cases. |
| 139 | + |
| 140 | + .. change:: Treat empty multipart file fields as ``None`` for ``Optional[UploadFile]`` |
| 141 | + :type: bugfix |
| 142 | + :pr: 4659 |
| 143 | + :issue: 4204, 4647 |
| 144 | + |
| 145 | + Fix a regression introduced in 2.18.0 (#4271) where an empty file input in |
| 146 | + a multipart form would arrive as an empty string instead of ``None``, |
| 147 | + causing a ``400`` validation error for ``Optional[UploadFile]`` fields. |
| 148 | + Empty file inputs annotated as ``Optional[UploadFile]`` are now converted |
| 149 | + to ``None`` before reaching the conversion layer, matching the semantics |
| 150 | + of "no file selected". |
| 151 | + |
| 152 | + .. change:: Fix ``KeyError`` when ``ClassVar`` exists on a msgspec DTO struct |
| 153 | + :type: bugfix |
| 154 | + :pr: 4669 |
| 155 | + :issue: 4665 |
| 156 | + |
| 157 | + Fix a :exc:`KeyError` raised when a msgspec ``Struct`` used as a DTO |
| 158 | + contains a ``ClassVar`` field. |
| 159 | + |
| 160 | + .. change:: Fix data race in ``RateLimitMiddleware`` |
| 161 | + :type: bugfix |
| 162 | + :pr: 4681 |
| 163 | + |
| 164 | + Fix a data race in |
| 165 | + :class:`~litestar.middleware.rate_limit.RateLimitMiddleware` where |
| 166 | + ``retrieve_cached_history`` and ``set_cached_history`` could interleave |
| 167 | + between coroutines. Both calls now execute under the same |
| 168 | + :class:`anyio.Lock`. |
| 169 | + |
| 170 | + .. change:: Remove unreachable branch in ``RateLimitMiddleware`` |
| 171 | + :type: bugfix |
| 172 | + :pr: 4683 |
| 173 | + |
| 174 | + Remove an unreachable branch in |
| 175 | + :class:`~litestar.middleware.rate_limit.RateLimitMiddleware`. ``reset`` is |
| 176 | + set to ``now + duration``, and the surrounding ``while`` loop already |
| 177 | + removes all items where ``reset <= now``, so the removed code path could |
| 178 | + never trigger. |
| 179 | + |
| 180 | + .. change:: Fix OpenAPI schema type for ``Decimal`` to match serialization |
| 181 | + :type: bugfix |
| 182 | + :pr: 4685 |
| 183 | + :issue: 4682 |
| 184 | + |
| 185 | + Fix OpenAPI schema generation to emit ``string`` for |
| 186 | + :class:`decimal.Decimal` fields instead of ``number``, matching the |
| 187 | + default string serialization used by msgspec. |
| 188 | + |
| 189 | + .. change:: Include ``minItems`` / ``maxItems`` in OpenAPI schema for msgspec ``Meta`` on collections |
| 190 | + :type: bugfix |
| 191 | + :pr: 4605 |
| 192 | + :issue: 4514 |
| 193 | + |
| 194 | + Fix OpenAPI schema generation to include ``minItems``/``maxItems`` for |
| 195 | + ``Annotated[list[T], Meta(min_length=N)]`` and similar collection types |
| 196 | + on msgspec ``Struct`` fields. Previously, length constraints were |
| 197 | + extracted only for string and bytes types, leaving collection length |
| 198 | + metadata absent from the generated schema even though runtime validation |
| 199 | + worked correctly. The fix covers ``ListType``, ``SetType``, |
| 200 | + ``FrozenSetType``, and ``VarTupleType``, and adds a branch for |
| 201 | + ``msgspec.Meta`` (which is not a subclass of ``annotated_types``) in the |
| 202 | + type extractor. |
| 203 | + |
| 204 | + .. change:: Fix content-length check incompatible with decompression middleware |
| 205 | + :type: bugfix |
| 206 | + :pr: 4690 |
| 207 | + :issue: 4125 |
| 208 | + |
| 209 | + Remove the content-length integrity check in |
| 210 | + :meth:`~litestar.connection.Request.stream` that raised |
| 211 | + ``ClientException("Malformed request")`` when streamed bytes exceeded the |
| 212 | + ``Content-Length`` header. This check broke when request decompression |
| 213 | + middleware wrapped the ASGI ``receive`` function, since the decompressed |
| 214 | + body is legitimately larger than the wire-format ``Content-Length``. |
| 215 | + |
| 216 | + Body size enforcement now relies on ``request_max_body_size`` |
| 217 | + unconditionally, returning a ``413`` for oversized bodies. Content-length |
| 218 | + validation at the transport layer remains the responsibility of the ASGI |
| 219 | + server. The previous ``if``/``elif`` structure also meant that |
| 220 | + ``request_max_body_size`` enforcement was unreachable when |
| 221 | + ``Content-Length`` was set; this is now corrected. |
| 222 | + |
| 223 | + |
6 | 224 | .. changelog:: 2.21.1 |
7 | 225 | :date: 2026-03-07 |
8 | 226 |
|
|
0 commit comments