Coverage for functions \ flipdare \ search \ result \ typesense_models.py: 98%

60 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13 

14from typing import Annotated, Any, Literal, TypeGuard 

15 

16from pydantic import BaseModel, Field, model_validator 

17from flipdare.app_types import SearchDict 

18 

19__all__ = [ 

20 "TResultModel", 

21 "THitModel", 

22 "TRequestParamsModel", 

23 "TMatchInfoModel", 

24 "TArrayHighlightModel", 

25 "TStringHighlightModel", 

26 "HighlightType", 

27] 

28 

29 

30class TArrayHighlightModel(BaseModel): 

31 """Model for highlight entry with field name.""" 

32 

33 kind: Literal["array"] = "array" 

34 

35 field: str 

36 indices: list[int] 

37 matched_tokens: list[list[str]] 

38 snippets: list[str] 

39 

40 

41class TStringHighlightModel(BaseModel): 

42 """Model for highlight entry with field name.""" 

43 

44 kind: Literal["string"] = "string" 

45 

46 field: str 

47 matched_tokens: list[str] 

48 snippet: str 

49 

50 

51HighlightType = Annotated[ 

52 TArrayHighlightModel | TStringHighlightModel, 

53 Field(discriminator="kind"), 

54] 

55 

56 

57class HighlightGuards: 

58 @staticmethod 

59 def is_array_list(items: list[HighlightType]) -> TypeGuard[list[TArrayHighlightModel]]: 

60 return len(items) > 0 and items[0].kind == "array" 

61 

62 @staticmethod 

63 def is_string_list(items: list[HighlightType]) -> TypeGuard[list[TStringHighlightModel]]: 

64 return len(items) > 0 and items[0].kind == "string" 

65 

66 

67class TRequestParamsModel(BaseModel): 

68 """Model for Typesense request parameters.""" 

69 

70 collection_name: str 

71 first_q: str 

72 per_page: int 

73 q: str 

74 

75 

76class TMatchInfoModel(BaseModel): 

77 """Model for text match info from Typesense.""" 

78 

79 best_field_score: str 

80 best_field_weight: int 

81 fields_matched: int 

82 num_tokens_dropped: int 

83 score: str 

84 tokens_matched: int 

85 typo_prefix_score: int 

86 

87 

88class THitModel(BaseModel): 

89 """Model for a single search hit.""" 

90 

91 document: SearchDict # Flexible to support different document schemas 

92 highlights: list[HighlightType] | None = None 

93 text_match: int | None = None 

94 text_match_info: TMatchInfoModel | None = None 

95 

96 @model_validator(mode="before") 

97 @classmethod 

98 def inject_kind_tags(cls, data: Any) -> Any: 

99 if isinstance(data, dict) and "highlights" in data: 

100 for h in data["highlights"]: 

101 # Check for unique keys to "tag" the data manually 

102 if "snippets" in h and "kind" not in h: 

103 h["kind"] = "array" 

104 elif "snippet" in h and "kind" not in h: 

105 h["kind"] = "string" 

106 return data 

107 

108 

109class TResultModel(BaseModel): 

110 """Model for Typesense search results.""" 

111 

112 facet_counts: list[Any] = Field(default_factory=list) 

113 found: int 

114 out_of: int 

115 page: int 

116 request_params: TRequestParamsModel 

117 hits: list[THitModel] 

118 search_cutoff: bool 

119 search_time_ms: int