1- from enum import Enum
2- from typing import Annotated , Generic , TypeVar
1+ from typing import Annotated , Generic
32
43from common_library .basic_types import DEFAULT_FACTORY
54from common_library .json_serialization import json_dumps
1312from pydantic .generics import GenericModel
1413
1514from .basic_types import IDStr
15+ from .list_operations import OrderClause , OrderDirection , TField , check_ordering_list
1616from .rest_base import RequestParameters
17- from .utils .common_validators import parse_json_pre_validator
18-
17+ from .utils .common_validators import (
18+ parse_json_pre_validator ,
19+ )
1920
20- class OrderDirection (str , Enum ):
21- ASC = "asc"
22- DESC = "desc"
21+ __all__ : tuple [str , ...] = ("OrderDirection" ,)
2322
2423
2524class OrderBy (BaseModel ):
25+ # NOTE: use instead OrderClause[TField] where TField is Literal of valid fields
2626 field : Annotated [IDStr , Field (description = "field name identifier" )]
2727 direction : Annotated [
2828 OrderDirection ,
@@ -128,21 +128,10 @@ class _OrderJsonQueryParams(_BaseOrderQueryParams):
128128 return _OrderJsonQueryParams
129129
130130
131- #
132- #
133- # NOTE: OrderingQueryParams is a more flexible variant for generic usage and that
134- # does include multiple ordering clauses
135- #
136-
137- TField = TypeVar ("TField" , bound = str )
138-
139-
140- class OrderClause (GenericModel , Generic [TField ]):
141- field : TField
142- direction : OrderDirection = OrderDirection .ASC
143-
144-
145131class OrderingQueryParams (GenericModel , Generic [TField ]):
132+ # NOTE: OrderingQueryParams is a more flexible variant for generic usage and that
133+ # does include multiple ordering clauses
134+ #
146135 order_by : Annotated [
147136 list [OrderClause [TField ]],
148137 Field (
@@ -153,41 +142,39 @@ class OrderingQueryParams(GenericModel, Generic[TField]):
153142
154143 @field_validator ("order_by" , mode = "before" )
155144 @classmethod
156- def _parse_order_by (cls , v ):
157- """
158- Example:
159-
160- GET /items?order_by=-created_at,name
161-
162- Parses to:
163- [
164- OrderClause(field="created_at", direction=OrderDirection.DESC),
165- OrderClause(field="name", direction=OrderDirection.ASC),
166- ]
145+ def _parse_order_by_string (cls , v ):
146+ """Parses a comma-separated string into a list of OrderClause
147+
148+ Example, given the query parameter `order_by` in a request like `GET /items?order_by=-created_at,name`
149+ It parses to:
150+ [
151+ OrderClause(field="created_at", direction=OrderDirection.DESC),
152+ OrderClause(field="name", direction=OrderDirection.ASC),
153+ ]
167154 """
168155 if not v :
169156 return []
157+
170158 if isinstance (v , str ):
159+ # 1. from comma-separated string to list of OrderClause
171160 v = v .split ("," )
172- clauses = []
173- for t in v :
174- token = t .strip ()
175- if not token :
176- continue
177- if token .startswith ("-" ):
178- clauses .append (
179- OrderClause [TField ](
180- field = token [1 :].strip (), direction = OrderDirection .DESC
181- )
182- )
183- elif token .startswith ("+" ):
184- clauses .append (
185- OrderClause [TField ](
186- field = token [1 :].strip (), direction = OrderDirection .ASC
187- )
188- )
189- else :
190- clauses .append (
191- OrderClause [TField ](field = token , direction = OrderDirection .ASC )
192- )
193- return clauses
161+ clauses : list [tuple [str , OrderDirection ]] = []
162+ for t in v :
163+ token = t .strip ()
164+ if not token :
165+ continue
166+ if token .startswith ("-" ):
167+ clauses .append ((token [1 :].strip (), OrderDirection .DESC ))
168+ elif token .startswith ("+" ):
169+ clauses .append ((token [1 :].strip (), OrderDirection .ASC ))
170+ else :
171+ clauses .append ((token , OrderDirection .ASC ))
172+ # 2. check for duplicates and conflicting directions
173+ return [
174+ {"field" : field , "direction" : direction }
175+ for field , direction in check_ordering_list (clauses )
176+ ]
177+
178+ # NOTE: Parses ONLY strings into list[OrderClause], otherwise raises TypeError
179+ msg = f"Invalid type for order_by: expected str, got { type (v )} "
180+ raise TypeError (msg )
0 commit comments