Coverage for opt/mealie/lib/python3.12/site-packages/mealie/schema/response/query_search.py: 79%
37 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 17:29 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 17:29 +0000
1import re 1a
3from sqlalchemy import Select 1a
4from sqlalchemy.orm import Session 1a
5from text_unidecode import unidecode 1a
7from ...db.models._model_base import SqlAlchemyBase 1a
8from .._mealie import MealieModel, SearchType 1a
11class SearchFilter: 1a
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 1a
20 quoted_regex = re.compile(r"""(["'])(?:(?=(\\?))\2.)*?\1""") 1a
21 remove_quotes_regex = re.compile(r"""['"](.*)['"]""") 1a
23 @classmethod 1a
24 def _normalize_search(cls, search: str, normalize_characters: bool) -> str: 1a
25 search = search.translate(str.maketrans(cls.punctuation, " " * len(cls.punctuation))) 1qrsbtucdefghivjwkxylzmAnBCDEop
27 if normalize_characters: 1qrsbtucdefghivjwkxylzmAnBCDEop
28 search = unidecode(search).lower().strip() 1btucdefghivjkxylzmAnBCop
29 else:
30 search = search.strip() 1qrsbcdefghijwklmnDEop
32 return search 1qrsbtucdefghivjwkxylzmAnBCDEop
34 @classmethod 1a
35 def _build_search_list(cls, search: str) -> list[str]: 1a
36 if cls.quoted_regex.search(search): 36 ↛ 38line 36 didn't jump to line 38 because the condition on line 36 was never true1qrsbtucdefghivjwkxylzmAnBCDEop
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() 1qrsbtucdefghivjwkxylzmAnBCDEop
53 # remove padding whitespace inside quotes
54 return [x.strip() for x in search_list] 1qrsbtucdefghivjwkxylzmAnBCDEop
56 def __init__(self, session: Session, search: str, normalize_characters: bool = False) -> None: 1a
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 true1qrsbtucdefghivjwkxylzmAnBCDEop
58 self.search_type = SearchType.tokenized 1qrsbtucdefghivjwkxylzmAnBCDEop
59 else:
60 self.search_type = SearchType.fuzzy
62 self.session = session 1qrsbtucdefghivjwkxylzmAnBCDEop
63 self.search = self._normalize_search(search, normalize_characters) 1qrsbtucdefghivjwkxylzmAnBCDEop
64 self.search_list = self._build_search_list(self.search) 1qrsbtucdefghivjwkxylzmAnBCDEop
66 def filter_query_by_search(self, query: Select, schema: type[MealieModel], model: type[SqlAlchemyBase]) -> Select: 1a
67 return schema.filter_search_query(model, query, self.session, self.search_type, self.search, self.search_list) 1qrsbtucdefghivjwkxylzmAnBCDEop