1
1
"""Helper to create sqlalchemy filters according to filter querystring parameter"""
2
2
import inspect
3
3
import logging
4
+ from collections .abc import Sequence
4
5
from typing import (
5
6
Any ,
6
7
Callable ,
16
17
from pydantic import BaseConfig , BaseModel
17
18
from pydantic .fields import ModelField
18
19
from pydantic .validators import _VALIDATORS , find_validators
19
- from sqlalchemy import and_ , not_ , or_
20
+ from sqlalchemy import and_ , false , not_ , or_
20
21
from sqlalchemy .orm import aliased
21
22
from sqlalchemy .orm .attributes import InstrumentedAttribute
22
23
from sqlalchemy .orm .util import AliasedClass
@@ -396,11 +397,83 @@ def prepare_relationships_info(
396
397
)
397
398
398
399
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
+
399
472
def build_filter_expressions (
400
- filter_item : Union [ dict , list ] ,
473
+ filter_item : Dict ,
401
474
target_schema : Type [TypeSchema ],
402
475
target_model : Type [TypeModel ],
403
- relationships_info : dict [RelationshipPath , RelationshipFilteringInfo ],
476
+ relationships_info : Dict [RelationshipPath , RelationshipFilteringInfo ],
404
477
) -> Union [BinaryExpression , BooleanClauseList ]:
405
478
"""
406
479
Return sqla expressions.
@@ -409,93 +482,59 @@ def build_filter_expressions(
409
482
in where condition: query(Model).where(build_filter_expressions(...))
410
483
"""
411
484
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
+ )
413
491
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 ())
432
496
433
- schema_field = target_schema .__fields__ [field_name ]
497
+ sqla_logic_operators = {
498
+ "or" : or_ ,
499
+ "and" : and_ ,
500
+ "not" : not_ ,
501
+ }
434
502
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 } "
438
507
)
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 )
456
509
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 )
486
513
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
+ )
497
536
498
- return op (* expressions )
537
+ return op (* expressions )
499
538
500
539
501
540
def create_filters_and_joins (
0 commit comments