Skip to content

Commit 0d84a85

Browse files
authored
feat(litestar): Update to new-style parameter markers (litestar-org#736)
Implement new-style parameter markers introduced in litestar-org/litestar#4750
1 parent 955df1d commit 0d84a85

8 files changed

Lines changed: 118 additions & 97 deletions

File tree

advanced_alchemy/extensions/litestar/plugins/init/plugin.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from litestar.di import Provide
66
from litestar.dto import DTOData
7-
from litestar.params import Dependency, Parameter
7+
from litestar.params import Dependency
88
from litestar.plugins import CLIPlugin, InitPluginProtocol
99
from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session
1010
from sqlalchemy.orm import Session, scoped_session
@@ -56,7 +56,6 @@
5656
"FilterTypes": FilterTypes,
5757
"OffsetPagination": OffsetPagination,
5858
"ExistsFilter": ExistsFilter,
59-
"Parameter": Parameter,
6059
"Dependency": Dependency,
6160
"DTOData": DTOData,
6261
"Sequence": Sequence,

advanced_alchemy/extensions/litestar/providers.py

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from collections.abc import AsyncGenerator, Callable, Generator
1212
from typing import (
1313
TYPE_CHECKING,
14+
Annotated,
1415
Any,
1516
Literal,
1617
NamedTuple,
@@ -24,7 +25,7 @@
2425
from uuid import UUID
2526

2627
from litestar.di import Provide
27-
from litestar.params import Dependency, Parameter
28+
from litestar.params import Dependency, FromQuery, QueryParameter
2829
from typing_extensions import NotRequired
2930

3031
from advanced_alchemy.filters import (
@@ -407,7 +408,7 @@ def _create_statement_filters( # noqa: C901
407408
if config.get("id_filter", False):
408409

409410
def provide_id_filter( # pyright: ignore[reportUnknownParameterType]
410-
ids: Optional[list[str]] = Parameter(query="ids", default=None, required=False),
411+
ids: FromQuery[Optional[list[str]]] = None,
411412
) -> CollectionFilter: # pyright: ignore[reportMissingTypeArgument]
412413
return CollectionFilter(field_name=config.get("id_field", "id"), values=ids)
413414

@@ -416,8 +417,8 @@ def provide_id_filter( # pyright: ignore[reportUnknownParameterType]
416417
if config.get("created_at", False):
417418

418419
def provide_created_filter(
419-
before: DTorNone = Parameter(query="createdBefore", default=None, required=False),
420-
after: DTorNone = Parameter(query="createdAfter", default=None, required=False),
420+
before: Annotated[DTorNone, QueryParameter(name="createdBefore")] = None,
421+
after: Annotated[DTorNone, QueryParameter(name="createdAfter")] = None,
421422
) -> BeforeAfter:
422423
return BeforeAfter("created_at", before, after)
423424

@@ -426,8 +427,8 @@ def provide_created_filter(
426427
if config.get("updated_at", False):
427428

428429
def provide_updated_filter(
429-
before: DTorNone = Parameter(query="updatedBefore", default=None, required=False),
430-
after: DTorNone = Parameter(query="updatedAfter", default=None, required=False),
430+
before: Annotated[DTorNone, QueryParameter(name="updatedBefore")] = None,
431+
after: Annotated[DTorNone, QueryParameter(name="updatedAfter")] = None,
431432
) -> BeforeAfter:
432433
return BeforeAfter("updated_at", before, after)
433434

@@ -436,13 +437,14 @@ def provide_updated_filter(
436437
if config.get("pagination_type") == "limit_offset":
437438

438439
def provide_limit_offset_pagination(
439-
current_page: int = Parameter(ge=1, query="currentPage", default=1, required=False),
440-
page_size: int = Parameter(
441-
query="pageSize",
442-
ge=1,
443-
default=config.get("pagination_size", dep_defaults.DEFAULT_PAGINATION_SIZE),
444-
required=False,
445-
),
440+
current_page: Annotated[int, QueryParameter(ge=1, name="currentPage")] = 1,
441+
page_size: Annotated[
442+
int,
443+
QueryParameter(
444+
name="pageSize",
445+
ge=1,
446+
),
447+
] = config.get("pagination_size", dep_defaults.DEFAULT_PAGINATION_SIZE),
446448
) -> LimitOffset:
447449
return LimitOffset(page_size, page_size * (current_page - 1))
448450

@@ -453,18 +455,20 @@ def provide_limit_offset_pagination(
453455
if search_fields := config.get("search"):
454456

455457
def provide_search_filter(
456-
search_string: StringOrNone = Parameter(
457-
title="Field to search",
458-
query="searchString",
459-
default=None,
460-
required=False,
461-
),
462-
ignore_case: BooleanOrNone = Parameter(
463-
title="Search should be case sensitive",
464-
query="searchIgnoreCase",
465-
default=config.get("search_ignore_case", False),
466-
required=False,
467-
),
458+
search_string: Annotated[
459+
StringOrNone,
460+
QueryParameter(
461+
name="searchString",
462+
title="Field to search",
463+
),
464+
] = None,
465+
ignore_case: Annotated[
466+
BooleanOrNone,
467+
QueryParameter(
468+
name="searchIgnoreCase",
469+
title="Search should be case sensitive",
470+
),
471+
] = config.get("search_ignore_case", False),
468472
) -> SearchFilter:
469473
# Handle both string and set input types for search fields
470474
field_names = set(search_fields.split(",")) if isinstance(search_fields, str) else set(search_fields)
@@ -480,18 +484,20 @@ def provide_search_filter(
480484
if sort_field := config.get("sort_field"):
481485

482486
def provide_order_by(
483-
field_name: StringOrNone = Parameter(
484-
title="Order by field",
485-
query="orderBy",
486-
default=sort_field,
487-
required=False,
488-
),
489-
sort_order: SortOrderOrNone = Parameter(
490-
title="Field to search",
491-
query="sortOrder",
492-
default=config.get("sort_order", "desc"),
493-
required=False,
494-
),
487+
field_name: Annotated[
488+
StringOrNone,
489+
QueryParameter(
490+
title="Order by field",
491+
name="orderBy",
492+
),
493+
] = sort_field,
494+
sort_order: Annotated[
495+
SortOrderOrNone,
496+
QueryParameter(
497+
title="Field to search",
498+
name="sortOrder",
499+
),
500+
] = config.get("sort_order", "desc"),
495501
) -> OrderBy:
496502
return OrderBy(field_name=field_name, sort_order=sort_order) # type: ignore[arg-type]
497503

@@ -522,24 +528,24 @@ def provide_not_in_filter( # pyright: ignore
522528
else None
523529
)
524530

531+
annotation = Annotated[
532+
Optional[list[field_name.type_hint]], # type: ignore
533+
QueryParameter(name=camelize(param_name)),
534+
]
525535
provide_not_in_filter.__name__ = f"provide_not_in_filter_{field_name.name}"
526536
provide_not_in_filter.__signature__ = inspect.Signature( # type: ignore[attr-defined]
527537
parameters=[
528538
inspect.Parameter(
529539
name=param_name,
530540
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
531-
default=Parameter(
532-
query=camelize(param_name),
533-
default=None,
534-
required=False,
535-
),
536-
annotation=Optional[list[field_name.type_hint]], # type: ignore
541+
default=None,
542+
annotation=annotation,
537543
)
538544
],
539545
return_annotation=Optional[NotInCollectionFilter[field_name.type_hint]], # type: ignore
540546
)
541547
provide_not_in_filter.__annotations__ = {
542-
param_name: Optional[list[field_name.type_hint]], # type: ignore
548+
param_name: annotation,
543549
"return": Optional[NotInCollectionFilter[field_name.type_hint]], # type: ignore
544550
}
545551
return provide_not_in_filter # pyright: ignore
@@ -573,23 +579,25 @@ def provide_in_filter( # pyright: ignore
573579
)
574580

575581
provide_in_filter.__name__ = f"provide_in_filter_{field_name.name}"
582+
annotation = Annotated[
583+
Optional[list[field_name.type_hint]], # type: ignore
584+
QueryParameter(
585+
name=camelize(param_name),
586+
),
587+
]
576588
provide_in_filter.__signature__ = inspect.Signature( # type: ignore[attr-defined]
577589
parameters=[
578590
inspect.Parameter(
579591
name=param_name,
580592
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
581-
default=Parameter(
582-
query=camelize(param_name),
583-
default=None,
584-
required=False,
585-
),
586-
annotation=Optional[list[field_name.type_hint]], # type: ignore
593+
default=None,
594+
annotation=annotation,
587595
)
588596
],
589597
return_annotation=Optional[CollectionFilter[field_name.type_hint]], # type: ignore
590598
)
591599
provide_in_filter.__annotations__ = {
592-
param_name: Optional[list[field_name.type_hint]], # type: ignore
600+
param_name: annotation,
593601
"return": Optional[CollectionFilter[field_name.type_hint]], # type: ignore
594602
}
595603
return provide_in_filter # pyright: ignore

examples/litestar/litestar_repo_only.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from __future__ import annotations
22

33
import datetime # noqa: TC003
4-
from typing import TYPE_CHECKING, Optional
4+
from typing import TYPE_CHECKING, Annotated, Optional
55
from uuid import UUID # noqa: TC003
66

77
from litestar import Litestar
88
from litestar.controller import Controller
99
from litestar.di import Provide
1010
from litestar.handlers.http_handlers.decorators import delete, get, patch, post
1111
from litestar.pagination import OffsetPagination
12-
from litestar.params import Parameter
1312
from pydantic import BaseModel as _BaseModel
1413
from pydantic import TypeAdapter
1514
from sqlalchemy import ForeignKey
@@ -22,6 +21,7 @@
2221
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
2322

2423
if TYPE_CHECKING:
24+
from litestar.params import PathParameter, QueryParameter
2525
from sqlalchemy.ext.asyncio import AsyncSession
2626

2727

@@ -89,13 +89,8 @@ async def provide_author_details_repo(db_session: AsyncSession) -> AuthorReposit
8989

9090

9191
def provide_limit_offset_pagination(
92-
current_page: int = Parameter(ge=1, query="currentPage", default=1, required=False),
93-
page_size: int = Parameter(
94-
query="pageSize",
95-
ge=1,
96-
default=10,
97-
required=False,
98-
),
92+
current_page: Annotated[int, QueryParameter(ge=1, name="currentPage")] = 1,
93+
page_size: Annotated[int, QueryParameter(name="pageSize", ge=1)] = 10,
9994
) -> LimitOffset:
10095
"""Add offset/limit pagination.
10196
@@ -150,10 +145,13 @@ async def create_author(
150145
async def get_author(
151146
self,
152147
authors_repo: AuthorRepository,
153-
author_id: UUID = Parameter(
154-
title="Author ID",
155-
description="The author to retrieve.",
156-
),
148+
author_id: Annotated[
149+
UUID,
150+
PathParameter(
151+
title="Author ID",
152+
description="The author to retrieve.",
153+
),
154+
],
157155
) -> Author:
158156
"""Get an existing author."""
159157
obj = await authors_repo.get(author_id)
@@ -167,10 +165,13 @@ async def update_author(
167165
self,
168166
authors_repo: AuthorRepository,
169167
data: AuthorUpdate,
170-
author_id: UUID = Parameter(
171-
title="Author ID",
172-
description="The author to update.",
173-
),
168+
author_id: Annotated[
169+
UUID,
170+
PathParameter(
171+
title="Author ID",
172+
description="The author to update.",
173+
),
174+
],
174175
) -> Author:
175176
"""Update an author."""
176177
raw_obj = data.model_dump(exclude_unset=True, exclude_none=True)
@@ -183,10 +184,13 @@ async def update_author(
183184
async def delete_author(
184185
self,
185186
authors_repo: AuthorRepository,
186-
author_id: UUID = Parameter(
187-
title="Author ID",
188-
description="The author to delete.",
189-
),
187+
author_id: Annotated[
188+
UUID,
189+
PathParameter(
190+
title="Author ID",
191+
description="The author to delete.",
192+
),
193+
],
190194
) -> None:
191195
"""Delete a author from the system."""
192196
_ = await authors_repo.delete(author_id)

examples/litestar/litestar_service.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from uuid import UUID
44

55
from litestar import Controller, Litestar, delete, get, patch, post
6-
from litestar.params import Dependency, Parameter
6+
from litestar.params import Dependency, PathParameter
77
from pydantic import BaseModel
88
from sqlalchemy import ForeignKey
99
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -101,10 +101,13 @@ async def create_author(self, authors_service: AuthorService, data: AuthorCreate
101101
async def get_author(
102102
self,
103103
authors_service: AuthorService,
104-
author_id: UUID = Parameter(
105-
title="Author ID",
106-
description="The author to retrieve.",
107-
),
104+
author_id: Annotated[
105+
UUID,
106+
PathParameter(
107+
title="Author ID",
108+
description="The author to retrieve.",
109+
),
110+
],
108111
) -> Author:
109112
"""Get an existing author."""
110113
obj = await authors_service.get(author_id)
@@ -115,10 +118,13 @@ async def update_author(
115118
self,
116119
authors_service: AuthorService,
117120
data: AuthorUpdate,
118-
author_id: UUID = Parameter(
119-
title="Author ID",
120-
description="The author to update.",
121-
),
121+
author_id: Annotated[
122+
UUID,
123+
PathParameter(
124+
title="Author ID",
125+
description="The author to update.",
126+
),
127+
],
122128
) -> Author:
123129
"""Update an author."""
124130
obj = await authors_service.update(data, item_id=author_id, auto_commit=True)
@@ -128,10 +134,13 @@ async def update_author(
128134
async def delete_author(
129135
self,
130136
authors_service: AuthorService,
131-
author_id: UUID = Parameter(
132-
title="Author ID",
133-
description="The author to delete.",
134-
),
137+
author_id: Annotated[
138+
UUID,
139+
PathParameter(
140+
title="Author ID",
141+
description="The author to delete.",
142+
),
143+
],
135144
) -> None:
136145
"""Delete a author from the system."""
137146
_ = await authors_service.delete(author_id)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ lint = [
139139
"types-cryptography",
140140
"types-passlib",
141141
]
142-
litestar = ["litestar[cli]>=2.15.0"]
142+
litestar = ["litestar[cli]>=2.22.0"]
143143
mssql = ["aioodbc>=0.5.0", "pyodbc>=5.2.0"]
144144
mysql = ["asyncmy>=0.2.9"]
145145
oracle = ["oracledb>=2.4.1"]

0 commit comments

Comments
 (0)