11"""Helper to create sqlalchemy filters according to filter querystring parameter"""
22import inspect
33import logging
4+ from collections .abc import Sequence
45from typing import (
56 Any ,
67 Callable ,
1617from pydantic import BaseConfig , BaseModel
1718from pydantic .fields import ModelField
1819from pydantic .validators import _VALIDATORS , find_validators
19- from sqlalchemy import and_ , not_ , or_
20+ from sqlalchemy import and_ , false , not_ , or_
2021from sqlalchemy .orm import aliased
2122from sqlalchemy .orm .attributes import InstrumentedAttribute
2223from sqlalchemy .orm .util import AliasedClass
@@ -396,11 +397,83 @@ def prepare_relationships_info(
396397 )
397398
398399
400+ def build_terminal_node_filter_expressions (
401+ filter_item : Dict ,
402+ target_schema : Type [TypeSchema ],
403+ target_model : Type [TypeModel ],
404+ relationships_info : Dict [RelationshipPath , RelationshipFilteringInfo ],
405+ ):
406+ name : str = filter_item ["name" ]
407+ if is_relationship_filter (name ):
408+ * relationship_path , field_name = name .split (RELATIONSHIP_SPLITTER )
409+ relationship_info : RelationshipFilteringInfo = relationships_info [
410+ RELATIONSHIP_SPLITTER .join (relationship_path )
411+ ]
412+ model_column = get_model_column (
413+ model = relationship_info .aliased_model ,
414+ schema = relationship_info .target_schema ,
415+ field_name = field_name ,
416+ )
417+ target_schema = relationship_info .target_schema
418+ else :
419+ field_name = name
420+ model_column = get_model_column (
421+ model = target_model ,
422+ schema = target_schema ,
423+ field_name = field_name ,
424+ )
425+
426+ schema_field = target_schema .__fields__ [field_name ]
427+
428+ filter_operator = filter_item ["op" ]
429+ custom_filter_expression : Callable = get_custom_filter_expression_callable (
430+ schema_field = schema_field ,
431+ operator = filter_operator ,
432+ )
433+ if custom_filter_expression is None :
434+ return build_filter_expression (
435+ schema_field = schema_field ,
436+ model_column = model_column ,
437+ operator = get_operator (
438+ model_column = model_column ,
439+ operator_name = filter_operator ,
440+ ),
441+ value = filter_item ["val" ],
442+ )
443+
444+ custom_call_result = custom_filter_expression (
445+ schema_field = schema_field ,
446+ model_column = model_column ,
447+ value = filter_item ["val" ],
448+ operator = filter_operator ,
449+ )
450+ if isinstance (custom_call_result , Sequence ):
451+ expected_len = 2
452+ if len (custom_call_result ) != expected_len :
453+ log .error (
454+ "Invalid filter, returned sequence length is not %s: %s, len=%s" ,
455+ expected_len ,
456+ custom_call_result ,
457+ len (custom_call_result ),
458+ )
459+ raise InvalidFilters (detail = "Custom sql filter backend error." )
460+ log .warning (
461+ "Custom filter result of `[expr, [joins]]` is deprecated."
462+ " Please return only filter expression from now on. "
463+ "(triggered on schema field %s for filter operator %s on column %s)" ,
464+ schema_field ,
465+ filter_operator ,
466+ model_column ,
467+ )
468+ custom_call_result = custom_call_result [0 ]
469+ return custom_call_result
470+
471+
399472def build_filter_expressions (
400- filter_item : Union [ dict , list ] ,
473+ filter_item : Dict ,
401474 target_schema : Type [TypeSchema ],
402475 target_model : Type [TypeModel ],
403- relationships_info : dict [RelationshipPath , RelationshipFilteringInfo ],
476+ relationships_info : Dict [RelationshipPath , RelationshipFilteringInfo ],
404477) -> Union [BinaryExpression , BooleanClauseList ]:
405478 """
406479 Return sqla expressions.
@@ -409,93 +482,59 @@ def build_filter_expressions(
409482 in where condition: query(Model).where(build_filter_expressions(...))
410483 """
411484 if is_terminal_node (filter_item ):
412- name = filter_item ["name" ]
485+ return build_terminal_node_filter_expressions (
486+ filter_item = filter_item ,
487+ target_schema = target_schema ,
488+ target_model = target_model ,
489+ relationships_info = relationships_info ,
490+ )
413491
414- if is_relationship_filter (name ):
415- * relationship_path , field_name = name .split (RELATIONSHIP_SPLITTER )
416- relationship_info : RelationshipFilteringInfo = relationships_info [
417- RELATIONSHIP_SPLITTER .join (relationship_path )
418- ]
419- model_column = get_model_column (
420- model = relationship_info .aliased_model ,
421- schema = relationship_info .target_schema ,
422- field_name = field_name ,
423- )
424- target_schema = relationship_info .target_schema
425- else :
426- field_name = name
427- model_column = get_model_column (
428- model = target_model ,
429- schema = target_schema ,
430- field_name = field_name ,
431- )
492+ if not isinstance (filter_item , dict ):
493+ log .warning ("Could not build filtering expressions %s" , locals ())
494+ # dirty. refactor.
495+ return not_ (false ())
432496
433- schema_field = target_schema .__fields__ [field_name ]
497+ sqla_logic_operators = {
498+ "or" : or_ ,
499+ "and" : and_ ,
500+ "not" : not_ ,
501+ }
434502
435- custom_filter_expression = get_custom_filter_expression_callable (
436- schema_field = schema_field ,
437- operator = filter_item ["op" ],
503+ if len (logic_operators := set (filter_item .keys ())) > 1 :
504+ msg = (
505+ f"In each logic node expected one of operators: { set (sqla_logic_operators .keys ())} "
506+ f"but got { len (logic_operators )} : { logic_operators } "
438507 )
439- if custom_filter_expression :
440- return custom_filter_expression (
441- schema_field = schema_field ,
442- model_column = model_column ,
443- value = filter_item ["val" ],
444- operator = filter_item ["op" ],
445- )
446- else :
447- return build_filter_expression (
448- schema_field = schema_field ,
449- model_column = model_column ,
450- operator = get_operator (
451- model_column = model_column ,
452- operator_name = filter_item ["op" ],
453- ),
454- value = filter_item ["val" ],
455- )
508+ raise InvalidFilters (msg )
456509
457- if isinstance (filter_item , dict ):
458- sqla_logic_operators = {
459- "or" : or_ ,
460- "and" : and_ ,
461- "not" : not_ ,
462- }
463-
464- if len (logic_operators := set (filter_item .keys ())) > 1 :
465- msg = (
466- f"In each logic node expected one of operators: { set (sqla_logic_operators .keys ())} "
467- f"but got { len (logic_operators )} : { logic_operators } "
468- )
469- raise InvalidFilters (msg )
470-
471- if (logic_operator := logic_operators .pop ()) not in set (sqla_logic_operators .keys ()):
472- msg = f"Not found logic operator { logic_operator } expected one of { set (sqla_logic_operators .keys ())} "
473- raise InvalidFilters (msg )
474-
475- op = sqla_logic_operators [logic_operator ]
476-
477- if logic_operator == "not" :
478- return op (
479- build_filter_expressions (
480- filter_item = filter_item [logic_operator ],
481- target_schema = target_schema ,
482- target_model = target_model ,
483- relationships_info = relationships_info ,
484- ),
485- )
510+ if (logic_operator := logic_operators .pop ()) not in set (sqla_logic_operators .keys ()):
511+ msg = f"Not found logic operator { logic_operator } expected one of { set (sqla_logic_operators .keys ())} "
512+ raise InvalidFilters (msg )
486513
487- expressions = []
488- for filter_sub_item in filter_item [logic_operator ]:
489- expressions .append (
490- build_filter_expressions (
491- filter_item = filter_sub_item ,
492- target_schema = target_schema ,
493- target_model = target_model ,
494- relationships_info = relationships_info ,
495- ),
496- )
514+ op = sqla_logic_operators [logic_operator ]
515+
516+ if logic_operator == "not" :
517+ return op (
518+ build_filter_expressions (
519+ filter_item = filter_item [logic_operator ],
520+ target_schema = target_schema ,
521+ target_model = target_model ,
522+ relationships_info = relationships_info ,
523+ ),
524+ )
525+
526+ expressions = []
527+ for filter_sub_item in filter_item [logic_operator ]:
528+ expressions .append (
529+ build_filter_expressions (
530+ filter_item = filter_sub_item ,
531+ target_schema = target_schema ,
532+ target_model = target_model ,
533+ relationships_info = relationships_info ,
534+ ),
535+ )
497536
498- return op (* expressions )
537+ return op (* expressions )
499538
500539
501540def create_filters_and_joins (
0 commit comments