1- import json
2-
31from typing import Any , Type
42
5- import pydantic
63from fastapi import HTTPException
7- from pydantic import create_model
8- from pydantic ._internal ._model_construction import ModelMetaclass
9- from sqlalchemy_to_pydantic import sqlalchemy_to_pydantic
10- from sqlalchemy import select , inspect
11- from sqlalchemy .orm import InstrumentedAttribute , DeclarativeBase
4+ from sqlalchemy import select
5+ from sqlalchemy .orm import DeclarativeBase
126from sqlalchemy .sql .elements import BinaryExpression , UnaryExpression
13- from sqlalchemy .sql .expression import and_ , or_
7+ from sqlalchemy .sql .expression import or_
148from starlette import status
159from sqlalchemy .sql import Select
1610
1711from fastapi_sa_orm_filter .exceptions import SAFilterOrmException
1812from fastapi_sa_orm_filter .operators import Operators as ops
19- from fastapi_sa_orm_filter .parsers import _FilterQueryParser , _OrderByQueryParser
13+ from fastapi_sa_orm_filter .parsers import FilterQueryParser , OrderByQueryParser
14+ from fastapi_sa_orm_filter .sa_expression_builder import SAFilterExpressionBuilder
2015
2116
2217class FilterCore :
@@ -43,9 +38,7 @@ def __init__(
4338 }
4439 """
4540 self .model = model
46- self .relationships = inspect (self .model ).relationships .items ()
4741 self ._allowed_filters = allowed_filters
48- self ._model_serializers = self ._create_pydantic_serializers ()
4942 self .select_query_part = select_query_part
5043
5144 def get_query (self , custom_filter : str ) -> Select [Any ]:
@@ -104,108 +97,22 @@ def get_group_by_query_part(self) -> list:
10497 return []
10598
10699 def get_order_by_query_part (self , order_by_query_str : str ) -> list [UnaryExpression ]:
107- order_by_parser = _OrderByQueryParser (self .model )
100+ order_by_parser = OrderByQueryParser (self .model )
108101 return order_by_parser .get_order_by_query (order_by_query_str )
109102
110103 def _get_filter_query (self , custom_filter : str ) -> list [BinaryExpression ]:
111104 filter_conditions = []
112105 if custom_filter == "" :
113106 return filter_conditions
114- query_parser = _FilterQueryParser (custom_filter , self .model , self ._allowed_filters )
115-
116- for and_expressions in query_parser .get_parsed_query ():
117- and_condition = []
118- for expression in and_expressions :
119- table , column , operator , value = expression
120- serialized_dict = self ._format_expression (table , column , operator , value )
121- value = serialized_dict [column .name ]
122- param = self ._get_orm_for_field (column , operator , value )
123- and_condition .append (param )
124- filter_conditions .append (and_ (* and_condition ))
125- return filter_conditions
126-
127- def _create_pydantic_serializers (self ) -> dict [str , dict [str , ModelMetaclass ]]:
128- """
129- Create two pydantic models (optional and list field types)
130- for value: str serialization
131-
132- :return: {
133- 'optional_model':
134- class model.__name__(BaseModel):
135- field: Optional[type]
136- 'list_model':
137- class model.__name__(BaseModel):
138- field: Optional[List[type]]
139- }
140- """
141-
142- models = [self .model ]
143- models .extend (self ._get_relations ())
144-
145- serializers = {}
146-
147- for model in models :
148- pydantic_serializer = sqlalchemy_to_pydantic (model )
149- optional_model = self ._get_optional_pydantic_model (model , pydantic_serializer )
150- optional_list_model = self ._get_optional_pydantic_model (model , pydantic_serializer , is_list = True )
151-
152- serializers [model .__tablename__ ] = {
153- "optional_model" : optional_model , "optional_list_model" : optional_list_model
154- }
155-
156- return serializers
157107
158- def _get_relations (self ) -> list :
159- return [relation [1 ].mapper .class_ for relation in self .relationships ]
160-
161- def _get_orm_for_field (
162- self , column : InstrumentedAttribute , operator : str , value : Any
163- ) -> BinaryExpression :
164- """
165- Create SQLAlchemy orm expression for the field
166- """
167- if operator in [ops .between ]:
168- param = getattr (column , ops [operator ].value )(* value )
169- else :
170- param = getattr (column , ops [operator ].value )(value )
171- return param
172-
173- def _format_expression (
174- self , table : str , column : InstrumentedAttribute , operator : str , value : str
175- ) -> dict [str , Any ]:
176- """
177- Serialize expression value from string to python type value,
178- according to db model types
179-
180- :return: {'field_name': [value, value]}
181- """
182- value = value .split ("," )
183- try :
184- if operator not in [ops .between , ops .in_ ]:
185- value = value [0 ]
186- model_serializer = self ._model_serializers [table ]["optional_model" ]
187- else :
188- model_serializer = self ._model_serializers [table ]["optional_list_model" ]
189- return model_serializer (** {column .name : value }).model_dump (exclude_none = True )
190- except pydantic .ValidationError as e :
191- raise SAFilterOrmException (json .loads (e .json ()))
192- except ValueError :
193- raise SAFilterOrmException (f"Incorrect filter value '{ value } '" )
108+ parser = FilterQueryParser (custom_filter , self ._allowed_filters )
109+ parsed_filters = parser .get_parsed_query ()
110+ sa_builder = SAFilterExpressionBuilder (self .model )
111+ return sa_builder .get_expressions (parsed_filters )
194112
195113 @staticmethod
196114 def _split_by_order_by (query ) -> list :
197115 split_query = [query_part .strip ("&" ) for query_part in query .split ("order_by=" )]
198116 if len (split_query ) > 2 :
199117 raise SAFilterOrmException ("Use only one order_by directive" )
200118 return split_query
201-
202- def _get_optional_pydantic_model (self , model , pydantic_serializer , is_list : bool = False ):
203- fields = {}
204- for k , v in pydantic_serializer .model_fields .items ():
205- origin_annotation = getattr (v , 'annotation' )
206- if is_list :
207- fields [k ] = (list [origin_annotation ], None )
208- else :
209- fields [k ] = (origin_annotation , None )
210- pydantic_model = create_model (model .__name__ , ** fields )
211- return pydantic_model
0 commit comments