Skip to content

Commit 2f80070

Browse files
committed
fix: 3.9 typing
1 parent 6930a20 commit 2f80070

5 files changed

Lines changed: 224 additions & 6 deletions

File tree

docs/release-notes/changelog.rst

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,224 @@
33
Litestar 2 Changelog
44
====================
55

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+
6224
.. changelog:: 2.21.1
7225
:date: 2026-03-07
8226

litestar/middleware/allowed_hosts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
6464
return
6565

6666
headers = MutableScopeHeaders(scope=scope)
67-
if host := headers.get("host").split(":")[0]:
67+
if (host := headers.get("host")) is not None and host.split(":")[0]:
6868
if self.allowed_hosts_regex.fullmatch(host):
6969
await self.app(scope, receive, send)
7070
return

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ maintainers = [
6565
name = "litestar"
6666
readme = "docs/PYPI_README.md"
6767
requires-python = ">=3.8,<4.0"
68-
version = "2.21.1"
68+
version = "2.22.0"
6969

7070
[project.urls]
7171
Blog = "https://blog.litestar.dev"

tests/unit/test_middleware/test_allowed_hosts_middleware.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, Any, cast
1+
from typing import TYPE_CHECKING, Any, Optional, cast
22

33
import pytest
44

@@ -77,8 +77,8 @@ def test_allowed_hosts_middleware_redirect_regex() -> None:
7777
],
7878
)
7979
def test_middleware_allowed_hosts(
80-
base_url: str | None,
81-
forwarded_host: str | None,
80+
base_url: Optional[str],
81+
forwarded_host: Optional[str],
8282
expected_status_code: int,
8383
) -> None:
8484
@get("/")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)