Coverage for opt/mealie/lib/python3.12/site-packages/mealie/schema/response/query_search.py: 95%
37 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:48 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:48 +0000
1import re 1b
3from sqlalchemy import Select 1b
4from sqlalchemy.orm import Session 1b
5from text_unidecode import unidecode 1b
7from ...db.models._model_base import SqlAlchemyBase 1b
8from .._mealie import MealieModel, SearchType 1b
11class SearchFilter: 1b
12 """
13 0. fuzzy search (postgres only) and tokenized search are performed separately
14 1. take search string and do a little pre-normalization
15 2. look for internal quoted strings and keep them together as "literal" parts of the search
16 3. remove special characters from each non-literal search string
17 """
19 punctuation = r"!\#$%&()*+,-./:;<=>?@[\\]^_`{|}~" # string.punctuation with ' & " removed 1b
20 quoted_regex = re.compile(r"""(["'])(?:(?=(\\?))\2.)*?\1""") 1b
21 remove_quotes_regex = re.compile(r"""['"](.*)['"]""") 1b
23 @classmethod 1b
24 def _normalize_search(cls, search: str, normalize_characters: bool) -> str: 1b
25 search = search.translate(str.maketrans(cls.punctuation, " " * len(cls.punctuation))) 1cdefghijklmnqopa
27 if normalize_characters: 1cdefghijklmnqopa
28 search = unidecode(search).lower().strip() 1cdefghijklmnopa
29 else:
30 search = search.strip() 1cdefghijklmnqopa
32 return search 1cdefghijklmnqopa
34 @classmethod 1b
35 def _build_search_list(cls, search: str) -> list[str]: 1b
36 if cls.quoted_regex.search(search): 1cdefghijklmnqopa
37 # all quoted strings
38 quoted_search_list = [match.group() for match in cls.quoted_regex.finditer(search)]
40 # remove outer quotes
41 quoted_search_list = [cls.remove_quotes_regex.sub("\\1", x) for x in quoted_search_list]
43 # punctuation->spaces for splitting, but only on unquoted strings
44 search = cls.quoted_regex.sub("", search) # remove all quoted strings, leaving just non-quoted
45 search = search.translate(str.maketrans(cls.punctuation, " " * len(cls.punctuation)))
47 # all unquoted strings
48 unquoted_search_list = search.split()
49 search_list = quoted_search_list + unquoted_search_list
50 else:
51 search_list = search.translate(str.maketrans(cls.punctuation, " " * len(cls.punctuation))).split() 1cdefghijklmnqopa
53 # remove padding whitespace inside quotes
54 return [x.strip() for x in search_list] 1cdefghijklmnqopa
56 def __init__(self, session: Session, search: str, normalize_characters: bool = False) -> None: 1b
57 if session.get_bind().name != "postgresql" or self.quoted_regex.search(search.strip()): 57 ↛ 60line 57 didn't jump to line 60 because the condition on line 57 was always true1cdefghijklmnqopa
58 self.search_type = SearchType.tokenized 1cdefghijklmnqopa
59 else:
60 self.search_type = SearchType.fuzzy
62 self.session = session 1cdefghijklmnqopa
63 self.search = self._normalize_search(search, normalize_characters) 1cdefghijklmnqopa
64 self.search_list = self._build_search_list(self.search) 1cdefghijklmnqopa
66 def filter_query_by_search(self, query: Select, schema: type[MealieModel], model: type[SqlAlchemyBase]) -> Select: 1b
67 return schema.filter_search_query(model, query, self.session, self.search_type, self.search, self.search_list) 1cdefghijklmnqopa