Skip to content

Commit 86a3739

Browse files
authored
Merge pull request #23 from gub-7/cat_search_and_set_stream_info
2 parents 3a57b7d + 8ba9e02 commit 86a3739

File tree

6 files changed

+599
-18
lines changed

6 files changed

+599
-18
lines changed

kick/categories.py

+296-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,193 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any
3+
from typing import TYPE_CHECKING
44

55
from .assets import Asset
6-
from .object import HTTPDataclass
6+
from .object import HTTPDataclass, BaseDataclass
77
from .utils import cached_property
88

99
if TYPE_CHECKING:
10-
from .types.categories import Category as CategoryPayload
11-
from .types.categories import InnerCategory as ParentCategoryPayload
10+
from .types.categories import (
11+
Category as CategoryPayload,
12+
InnerCategory as ParentCategoryPayload,
13+
CategoryDocument,
14+
CategorySearchResponse,
15+
TextHighlight as TextHighlightPayload,
16+
SearchHighlight as SearchHighlightPayload,
17+
TextMatchInfo as TextMatchInfoPayload,
18+
CategorySearchHit as CategorySearchHitPayload,
19+
)
20+
21+
__all__ = ("Category", "ParentCategory", "SearchCategory",
22+
"CategorySearchResult", "TextHighlight", "SearchHighlight",
23+
"TextMatchInfo", "CategorySearchHit")
24+
25+
26+
class TextHighlight(BaseDataclass["TextHighlightPayload"]):
27+
"""
28+
A dataclass representing text highlighting information
29+
30+
Attributes
31+
-----------
32+
matched_tokens: list[str]
33+
List of tokens that matched
34+
snippet: str
35+
The highlighted text snippet
36+
"""
37+
38+
@property
39+
def matched_tokens(self) -> list[str]:
40+
"""List of search tokens that matched in the text"""
41+
return self._data["matched_tokens"]
42+
43+
@property
44+
def snippet(self) -> str:
45+
"""The highlighted text snippet containing matches"""
46+
return self._data["snippet"]
47+
48+
def __repr__(self) -> str:
49+
return f"<TextHighlight matched_tokens={self.matched_tokens!r} snippet={self.snippet!r}>"
50+
51+
52+
class SearchHighlight(BaseDataclass["SearchHighlightPayload"]):
53+
"""
54+
A dataclass representing search highlight information
55+
56+
Attributes
57+
-----------
58+
field: str
59+
The field that was highlighted
60+
matched_tokens: list[str]
61+
List of tokens that matched
62+
snippet: str
63+
The highlighted text snippet
64+
"""
65+
66+
@property
67+
def field(self) -> str:
68+
"""The field name where the match was found"""
69+
return self._data["field"]
70+
71+
@property
72+
def matched_tokens(self) -> list[str]:
73+
"""List of search tokens that matched in the field"""
74+
return self._data["matched_tokens"]
75+
76+
@property
77+
def snippet(self) -> str:
78+
"""The highlighted text snippet from the matched field"""
79+
return self._data["snippet"]
80+
81+
def __repr__(self) -> str:
82+
return f"<SearchHighlight field={self.field!r} matched_tokens={self.matched_tokens!r} snippet={self.snippet!r}>"
83+
84+
85+
class TextMatchInfo(BaseDataclass["TextMatchInfoPayload"]):
86+
"""
87+
A dataclass representing text match scoring information
88+
89+
Attributes
90+
-----------
91+
best_field_score: str
92+
Score of the best matching field
93+
best_field_weight: int
94+
Weight of the best matching field
95+
fields_matched: int
96+
Number of fields that matched
97+
num_tokens_dropped: int
98+
Number of tokens that were dropped
99+
score: str
100+
Overall match score
101+
tokens_matched: int
102+
Number of tokens that matched
103+
typo_prefix_score: int
104+
Score for typo/prefix matches
105+
"""
106+
107+
@property
108+
def best_field_score(self) -> str:
109+
"""The score of the best matching field"""
110+
return self._data["best_field_score"]
111+
112+
@property
113+
def best_field_weight(self) -> int:
114+
"""The weight assigned to the best matching field"""
115+
return self._data["best_field_weight"]
116+
117+
@property
118+
def fields_matched(self) -> int:
119+
"""Number of fields that contained matches"""
120+
return self._data["fields_matched"]
121+
122+
@property
123+
def num_tokens_dropped(self) -> int:
124+
"""Number of search tokens that were ignored"""
125+
return self._data["num_tokens_dropped"]
126+
127+
@property
128+
def score(self) -> str:
129+
"""Overall match score for the search result"""
130+
return self._data["score"]
131+
132+
@property
133+
def tokens_matched(self) -> int:
134+
"""Number of search tokens that were matched"""
135+
return self._data["tokens_matched"]
136+
137+
@property
138+
def typo_prefix_score(self) -> int:
139+
"""Score adjustment for typos and prefix matches"""
140+
return self._data["typo_prefix_score"]
141+
142+
def __repr__(self) -> str:
143+
return f"<TextMatchInfo score={self.score!r} tokens_matched={self.tokens_matched} fields_matched={self.fields_matched}>"
144+
145+
146+
class CategorySearchHit(BaseDataclass["CategorySearchHitPayload"]):
147+
"""
148+
A dataclass representing a category search hit result
149+
150+
Attributes
151+
-----------
152+
document: SearchCategory
153+
The matching category document
154+
highlight: dict[str, TextHighlight]
155+
Highlights for each field
156+
highlights: list[SearchHighlight]
157+
List of all highlights
158+
text_match: int
159+
Text match score
160+
text_match_info: TextMatchInfo
161+
Detailed text match information
162+
"""
163+
164+
@cached_property
165+
def document(self) -> SearchCategory:
166+
"""The matching category document"""
167+
return SearchCategory(data=self._data["document"])
168+
169+
@cached_property
170+
def highlight(self) -> dict[str, TextHighlight]:
171+
"""Dictionary of field names to their highlight information"""
172+
return {k: TextHighlight(data=v) for k, v in self._data["highlight"].items()}
12173

13-
__all__ = ("Category", "ParentCategory")
174+
@cached_property
175+
def highlights(self) -> list[SearchHighlight]:
176+
"""List of all highlight information across fields"""
177+
return [SearchHighlight(data=h) for h in self._data["highlights"]]
178+
179+
@property
180+
def text_match(self) -> int:
181+
"""Overall text match score"""
182+
return self._data["text_match"]
183+
184+
@cached_property
185+
def text_match_info(self) -> TextMatchInfo:
186+
"""Detailed information about the text matching"""
187+
return TextMatchInfo(data=self._data["text_match_info"])
188+
189+
def __repr__(self) -> str:
190+
return f"<CategorySearchHit document={self.document!r} text_match={self.text_match}>"
14191

15192

16193
class ParentCategory(HTTPDataclass["ParentCategoryPayload"]):
@@ -67,6 +244,120 @@ def __eq__(self, other: object) -> bool:
67244
def __repr__(self) -> str:
68245
return f"<ParentCategory id={self.id!r} name={self.name!r} icon={self.icon!r}>"
69246

247+
class SearchCategory(BaseDataclass["CategoryDocument"]):
248+
"""
249+
A dataclass which represents a searchable category on kick
250+
Attributes
251+
-----------
252+
category_id: int
253+
The id of the parent category.
254+
id: str
255+
The id of the sub-category (game).
256+
name: str
257+
The category's name
258+
slug: str
259+
The category's slug
260+
description: str
261+
The category's description
262+
is_live: bool
263+
Whether the category is live
264+
is_mature: bool
265+
Whether the category is marked as mature
266+
src: str
267+
The category's banner image URL
268+
srcset: str
269+
The category's responsive image srcset
270+
parent: str
271+
The name of the parent category.
272+
"""
273+
274+
@property
275+
def category_id(self) -> int:
276+
"""The ID of the parent category"""
277+
return self._data["category_id"]
278+
279+
@property
280+
def id(self) -> str:
281+
"""The unique identifier of the sub-category"""
282+
return self._data["id"]
283+
284+
@property
285+
def name(self) -> str:
286+
"""The name of the category"""
287+
return self._data["name"]
288+
289+
@property
290+
def slug(self) -> str:
291+
"""The URL-friendly version of the category name"""
292+
return self._data["slug"]
293+
294+
@property
295+
def description(self) -> str:
296+
"""The detailed description of the category"""
297+
return self._data["description"]
298+
299+
@property
300+
def is_live(self) -> bool:
301+
"""Whether the category currently has live streams"""
302+
return self._data["is_live"]
303+
304+
@property
305+
def is_mature(self) -> bool:
306+
"""Whether the category is marked as mature content"""
307+
return self._data["is_mature"]
308+
309+
@property
310+
def src(self) -> str:
311+
"""The URL of the category's banner image"""
312+
return self._data["src"]
313+
314+
@property
315+
def srcset(self) -> str:
316+
"""The responsive image srcset for different screen sizes"""
317+
return self._data["srcset"]
318+
319+
@property
320+
def parent(self) -> str:
321+
"""The name of the parent category"""
322+
return self._data["parent"]
323+
324+
def __repr__(self) -> str:
325+
return f"<SearchCategory name={self.name!r} slug={self.slug!r} is_live={self.is_live}>"
326+
327+
328+
class CategorySearchResult(BaseDataclass["CategorySearchResponse"]):
329+
"""
330+
A dataclass which represents a category search response
331+
332+
Attributes
333+
-----------
334+
found: int
335+
Total number of results found
336+
hits: list[SearchCategory]
337+
List of matching categories
338+
page: int
339+
Current page number
340+
"""
341+
342+
@property
343+
def found(self) -> int:
344+
"""Total number of search results found"""
345+
return self._data["found"]
346+
347+
@property
348+
def page(self) -> int:
349+
"""Current page number in paginated results"""
350+
return self._data["page"]
351+
352+
@cached_property
353+
def hits(self) -> list[CategorySearchHit]:
354+
"""List of category search hits matching the search criteria"""
355+
return [CategorySearchHit(data=hit) for hit in self._data["hits"]]
356+
357+
def __repr__(self) -> str:
358+
return f"<CategorySearchResult found={self.found} page={self.page} hits={len(self.hits)}>"
359+
360+
70361

71362
class Category(HTTPDataclass["CategoryPayload"]):
72363
"""

0 commit comments

Comments
 (0)