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
« 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#
14from typing import Annotated, Any, Literal, TypeGuard
16from pydantic import BaseModel, Field, model_validator
17from flipdare.app_types import SearchDict
19__all__ = [
20 "TResultModel",
21 "THitModel",
22 "TRequestParamsModel",
23 "TMatchInfoModel",
24 "TArrayHighlightModel",
25 "TStringHighlightModel",
26 "HighlightType",
27]
30class TArrayHighlightModel(BaseModel):
31 """Model for highlight entry with field name."""
33 kind: Literal["array"] = "array"
35 field: str
36 indices: list[int]
37 matched_tokens: list[list[str]]
38 snippets: list[str]
41class TStringHighlightModel(BaseModel):
42 """Model for highlight entry with field name."""
44 kind: Literal["string"] = "string"
46 field: str
47 matched_tokens: list[str]
48 snippet: str
51HighlightType = Annotated[
52 TArrayHighlightModel | TStringHighlightModel,
53 Field(discriminator="kind"),
54]
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"
62 @staticmethod
63 def is_string_list(items: list[HighlightType]) -> TypeGuard[list[TStringHighlightModel]]:
64 return len(items) > 0 and items[0].kind == "string"
67class TRequestParamsModel(BaseModel):
68 """Model for Typesense request parameters."""
70 collection_name: str
71 first_q: str
72 per_page: int
73 q: str
76class TMatchInfoModel(BaseModel):
77 """Model for text match info from Typesense."""
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
88class THitModel(BaseModel):
89 """Model for a single search hit."""
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
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
109class TResultModel(BaseModel):
110 """Model for Typesense search results."""
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