Skip to content

Commit 89e8819

Browse files
committed
Add precise type annotations to search helpers
Signed-off-by: Tanishq Meshram <tnshqmeshram@gmail.com>
1 parent 236b916 commit 89e8819

8 files changed

Lines changed: 165 additions & 15 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Type definitions for search index results."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TypedDict
6+
7+
8+
class UserSearchHit(TypedDict, total=False):
9+
"""GitHub user search hit."""
10+
11+
idx_bio: str | None
12+
idx_company: str | None
13+
idx_followers_count: int
14+
idx_following_count: int
15+
idx_location: str | None
16+
idx_login: str
17+
idx_name: str | None
18+
idx_public_repositories_count: int
19+
idx_url: str
20+
21+
22+
class ChapterSearchHit(TypedDict, total=False):
23+
"""OWASP chapter search hit."""
24+
25+
idx_country: str
26+
idx_key: str
27+
idx_leaders: list[str]
28+
idx_name: str
29+
idx_suggested_location: str | None
30+
idx_summary: str
31+
idx_url: str
32+
33+
34+
class CommitteeSearchHit(TypedDict, total=False):
35+
"""OWASP committee search hit."""
36+
37+
idx_leaders: list[str]
38+
idx_name: str
39+
idx_summary: str
40+
idx_url: str
41+
42+
43+
class IssueSearchHit(TypedDict, total=False):
44+
"""GitHub issue search hit."""
45+
46+
idx_project_name: str
47+
idx_project_url: str
48+
idx_summary: str
49+
idx_title: str
50+
idx_url: str
51+
52+
53+
class ProjectSearchHit(TypedDict, total=False):
54+
"""OWASP project search hit."""
55+
56+
idx_contributors_count: int
57+
idx_forks_count: int
58+
idx_key: str
59+
idx_leaders: list[str]
60+
idx_name: str
61+
idx_stars_count: int
62+
idx_summary: str
63+
idx_updated_at: int
64+
idx_url: str
65+
66+
67+
class UserSearchResult(TypedDict):
68+
"""Result returned by a GitHub user search."""
69+
70+
hits: list[UserSearchHit]
71+
nbPages: int
72+
73+
74+
class ChapterSearchResult(TypedDict):
75+
"""Result returned by an OWASP chapter search."""
76+
77+
hits: list[ChapterSearchHit]
78+
nbPages: int
79+
80+
81+
class CommitteeSearchResult(TypedDict):
82+
"""Result returned by an OWASP committee search."""
83+
84+
hits: list[CommitteeSearchHit]
85+
nbPages: int
86+
87+
88+
class IssueSearchResult(TypedDict):
89+
"""Result returned by a GitHub issue search."""
90+
91+
hits: list[IssueSearchHit]
92+
nbPages: int
93+
94+
95+
class ProjectSearchResult(TypedDict):
96+
"""Result returned by an OWASP project search."""
97+
98+
hits: list[ProjectSearchHit]
99+
nbPages: int

backend/src/apps/github/index/search/user.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from apps.common.index_types import UserSearchResult
9+
510
from algoliasearch_django import raw_search
611

712
from apps.github.models.user import User
813

914

1015
def get_users(
1116
query: str,
12-
attributes: list | None = None,
17+
attributes: list[str] | None = None,
1318
limit: int = 25,
1419
page: int = 1,
15-
searchable_attributes: list | None = None,
16-
) -> dict:
20+
searchable_attributes: list[str] | None = None,
21+
) -> UserSearchResult:
1722
"""Return users relevant to a search query.
1823
1924
Args:

backend/src/apps/owasp/index/search/chapter.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from apps.common.index_types import ChapterSearchResult
9+
510
from algoliasearch_django import raw_search
611

712
from apps.owasp.models.chapter import Chapter
@@ -10,11 +15,11 @@
1015
def get_chapters(
1116
query: str,
1217
*,
13-
attributes: list | None = None,
18+
attributes: list[str] | None = None,
1419
limit: int = 25,
1520
page: int = 1,
16-
searchable_attributes: list | None = None,
17-
) -> dict:
21+
searchable_attributes: list[str] | None = None,
22+
) -> ChapterSearchResult:
1823
"""Return chapters relevant to a search query.
1924
2025
Args:

backend/src/apps/owasp/index/search/committee.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from apps.common.index_types import CommitteeSearchResult
9+
510
from algoliasearch_django import raw_search
611

712
from apps.owasp.models.committee import Committee
@@ -10,10 +15,10 @@
1015
def get_committees(
1116
query: str,
1217
*,
13-
attributes: list | None = None,
18+
attributes: list[str] | None = None,
1419
limit: int = 25,
1520
page: int = 1,
16-
) -> dict:
21+
) -> CommitteeSearchResult:
1722
"""Return committees relevant to a search query.
1823
1924
Args:

backend/src/apps/owasp/index/search/issue.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from apps.common.index_types import IssueSearchResult
9+
510
from algoliasearch_django import raw_search
611

712
from apps.github.models.issue import Issue
@@ -12,11 +17,11 @@
1217
def get_issues(
1318
query: str,
1419
*,
15-
attributes: list | None = None,
20+
attributes: list[str] | None = None,
1621
distinct: bool = False,
1722
limit: int = 25,
1823
page: int = 1,
19-
) -> dict:
24+
) -> IssueSearchResult:
2025
"""Return issues relevant to a search query.
2126
2227
Args:

backend/src/apps/owasp/index/search/project.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from apps.common.index_types import ProjectSearchResult
9+
510
from algoliasearch_django import raw_search
611

712
from apps.owasp.models.project import Project
@@ -10,11 +15,11 @@
1015
def get_projects(
1116
query: str,
1217
*,
13-
attributes: list | None = None,
18+
attributes: list[str] | None = None,
1419
limit: int = 25,
1520
page: int = 1,
16-
searchable_attributes: list | None = None,
17-
) -> dict:
21+
searchable_attributes: list[str] | None = None,
22+
) -> ProjectSearchResult:
1823
"""Return projects relevant to a search query.
1924
2025
Args:

backend/src/apps/slack/common/handlers/users.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ def get_blocks(
7878

7979
bio = truncate(escape(user.get("idx_bio", "") or ""), presentation.summary_truncation)
8080

81-
location = escape(user.get("idx_location", ""))
82-
company = escape(user.get("idx_company", ""))
81+
location = escape(user.get("idx_location", "") or "")
82+
company = escape(user.get("idx_company", "") or "")
8383
followers_count = user.get("idx_followers_count", 0)
8484
following_count = user.get("idx_following_count", 0)
8585
public_repositories = user.get("idx_public_repositories_count", 0)

backend/tests/unit/apps/slack/common/handlers/users_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,32 @@ def test_get_blocks_with_empty_metadata_fields(self, mocker):
123123
assert "Location:" not in user_block_text
124124
assert "Followers:" not in user_block_text
125125

126+
def test_get_blocks_with_none_metadata_fields(self, mocker):
127+
"""Test users with nullable metadata fields."""
128+
mock_data = {
129+
"hits": [
130+
{
131+
"idx_name": "User NoMeta",
132+
"idx_login": "user_nometa",
133+
"idx_url": "https://github.com/user_nometa",
134+
"idx_bio": None,
135+
"idx_location": None,
136+
"idx_company": None,
137+
"idx_followers_count": 0,
138+
"idx_following_count": 0,
139+
"idx_public_repositories_count": 0,
140+
}
141+
],
142+
"nbPages": 1,
143+
}
144+
mocker.patch("apps.github.index.search.user.get_users", return_value=mock_data)
145+
146+
blocks = get_blocks()
147+
148+
user_block_text = blocks[1]["text"]["text"]
149+
assert "Company:" not in user_block_text
150+
assert "Location:" not in user_block_text
151+
126152
def test_get_blocks_with_no_name_uses_login(self, mocker):
127153
"""Test users with no name field uses login instead."""
128154
mock_data = {

0 commit comments

Comments
 (0)