Skip to content

Commit f3cf982

Browse files
ImTotemclaude
andcommitted
refactor: generic to_filter — remove per-domain _to_filter boilerplate
New to_filter(strawberry_input, PydanticFilterClass) auto-maps all fields from Strawberry input to Pydantic filter. Only sorts needs special handling (SortFieldInput → SortField conversion). Adding a new domain filter now requires zero converter code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a930986 commit f3cf982

File tree

3 files changed

+15
-25
lines changed

3 files changed

+15
-25
lines changed

src/bcsd_api/graphql/convert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
from typing import TypeVar
2+
13
import strawberry
24
from pydantic import BaseModel
35

4-
from bcsd_api.filter.base import SortField
6+
from bcsd_api.filter.base import BaseFilter, SortField
7+
8+
F = TypeVar("F", bound=BaseFilter)
59

610

711
@strawberry.input
@@ -16,6 +20,12 @@ def to_sorts(inputs: list[SortFieldInput] | None) -> list[SortField]:
1620
return [SortField(field=s.field, order=s.order) for s in inputs]
1721

1822

23+
def to_filter(inp, filter_cls: type[F]) -> F:
24+
data = {k: v for k, v in inp.__dict__.items() if k != "sorts"}
25+
data["sorts"] = to_sorts(inp.sorts)
26+
return filter_cls(**data)
27+
28+
1929
def from_model(source: BaseModel, target_cls):
2030
return target_cls(**source.model_dump())
2131

src/bcsd_api/member/resolvers.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from bcsd_api.filter.members import MemberFilter
55
from bcsd_api.graphql.context import GqlContext, require_user
6-
from bcsd_api.graphql.convert import from_model, from_paged, to_sorts
6+
from bcsd_api.graphql.convert import from_model, from_paged, to_filter
77

88
from . import service
99
from .types import (
@@ -16,24 +16,13 @@
1616
)
1717

1818

19-
def _to_filter(inp: MemberFilterInput) -> MemberFilter:
20-
return MemberFilter(
21-
page=inp.page, size=inp.size,
22-
sorts=to_sorts(inp.sorts),
23-
status=inp.status, track=inp.track,
24-
team=inp.team, name=inp.name,
25-
email=inp.email, department=inp.department,
26-
student_id=inp.student_id, phone=inp.phone,
27-
)
28-
29-
3019
def resolve_members(
3120
info: Info[GqlContext, None],
3221
filter: MemberFilterInput | None = None,
3322
) -> PagedMembers:
3423
ctx = info.context
3524
require_user(ctx)
36-
filt = _to_filter(filter) if filter else MemberFilter.model_validate({})
25+
filt = to_filter(filter, MemberFilter) if filter else MemberFilter.model_validate({})
3726
paged = service.list_members(ctx.member_repo, filt)
3827
return from_paged(paged, MemberType, PagedMembers)
3928

src/bcsd_api/shorten/resolvers.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from bcsd_api.filter.links import LinkFilter
55
from bcsd_api.graphql.context import GqlContext, require_user
6-
from bcsd_api.graphql.convert import from_model, from_paged, to_sorts
6+
from bcsd_api.graphql.convert import from_model, from_paged, to_filter
77

88
from . import service
99
from .schema import CreateRequest, UpdateRequest
@@ -20,22 +20,13 @@
2020
)
2121

2222

23-
def _to_filter(inp: LinkFilterInput) -> LinkFilter:
24-
return LinkFilter(
25-
page=inp.page, size=inp.size,
26-
sorts=to_sorts(inp.sorts),
27-
creator_id=inp.creator_id, expired=inp.expired,
28-
title=inp.title, code=inp.code,
29-
)
30-
31-
3223
def resolve_links(
3324
info: Info[GqlContext, None],
3425
filter: LinkFilterInput | None = None,
3526
) -> PagedLinks:
3627
ctx = info.context
3728
require_user(ctx)
38-
filt = _to_filter(filter) if filter else LinkFilter.model_validate({})
29+
filt = to_filter(filter, LinkFilter) if filter else LinkFilter.model_validate({})
3930
paged = service.list_links(ctx.link_repo, filt)
4031
return from_paged(paged, LinkType, PagedLinks)
4132

0 commit comments

Comments
 (0)