Skip to content

Commit aa00a2d

Browse files
committed
Merge branch 'dev-fix-semantic_search' into login-mechanism
2 parents 2467ed8 + 3f36d77 commit aa00a2d

2 files changed

Lines changed: 168 additions & 30 deletions

File tree

src/osw/wiki_tools.py

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def __init__(self, **data):
150150

151151
def prefix_search(
152152
site: mwclient.client.Site, text: Union[str, SearchParam]
153-
) -> List[str]:
153+
) -> Union[List[str], List[dict]]:
154154
"""Standard query. Equivalent to the following mediawiki API call
155155
api.php?action=query&list=prefixsearch&pssearch=Star Wars.
156156
@@ -165,15 +165,17 @@ def prefix_search(
165165
166166
Returns
167167
-------
168-
page_list :
169-
List of page titles
168+
result:
169+
With ``return_json=False`` (default): a flat list of page titles. With
170+
``return_json=True``: a list of raw MediaWiki ``prefixsearch`` API response
171+
dicts, one per query (always a list, even for a single query).
170172
"""
171173
if not isinstance(text, SearchParam):
172174
query = SearchParam(query=text)
173175
else:
174176
query = text
175177

176-
def prefix_search_(single_text):
178+
def prefix_search_(single_text) -> Union[List[str], dict]:
177179
page_list = list()
178180
result = site.api(
179181
"query",
@@ -182,15 +184,16 @@ def prefix_search_(single_text):
182184
pslimit=query.limit,
183185
format="json",
184186
)
185-
if len(result["query"]["prefixsearch"]) == 0:
187+
if query.debug and len(result["query"]["prefixsearch"]) == 0:
188+
print("No results")
189+
if query.return_json:
190+
return result
191+
192+
for page in result["query"]["prefixsearch"]:
193+
title = page["title"]
186194
if query.debug:
187-
print("No results")
188-
else:
189-
for page in result["query"]["prefixsearch"]:
190-
title = page["title"]
191-
if query.debug:
192-
print(title)
193-
page_list.append(title)
195+
print(title)
196+
page_list.append(title)
194197
return page_list
195198

196199
if query.parallel:
@@ -200,11 +203,17 @@ def prefix_search_(single_text):
200203
else:
201204
query_results = [prefix_search_(single_text=sq) for sq in query.query]
202205

206+
if query.return_json:
207+
# Each entry of query_results is the raw API response dict for one query.
208+
# Do not flatten dicts; always return the list of responses (one per query),
209+
# even when only a single query was passed.
210+
return query_results
211+
203212
return [item for sublist in query_results for item in sublist]
204213
# todo: @Simon: a list of lists of strings (sublist for each query in query list)
205214
# or a list of strings (results of all queries combined)?
206215
# The last option would not change the behavior of the function, but would
207-
# return page_list # original return
216+
# return page_list # original return
208217

209218

210219
def semantic_search(
@@ -221,8 +230,10 @@ def semantic_search(
221230
222231
Returns
223232
-------
224-
page_list:
225-
List of page titles
233+
result:
234+
With ``return_json=False`` (default): a flat list of page-title fulltext
235+
strings. With ``return_json=True``: a list of raw SMW ``ask`` result dicts,
236+
one per query (always a list, even for a single query).
226237
"""
227238
if not isinstance(query, SearchParam):
228239
query = SearchParam(query=query)
@@ -231,27 +242,26 @@ def semantic_search_(single_query):
231242
page_list = list()
232243
single_query += f"|limit={query.limit}"
233244
result = site.api("ask", query=single_query, format="json")
234-
if len(result["query"]["results"]) == 0:
235-
if query.debug:
245+
if query.debug:
246+
if len(result["query"]["results"]) == 0:
236247
print("Query '{}' returned no results".format(single_query))
237-
else:
238-
if query.debug:
248+
else:
239249
print(
240250
"Query '{}' returned {} results".format(
241251
single_query, len(result["query"]["results"])
242252
)
243253
)
244-
if query.return_json:
245-
return result
246-
247-
for page in result["query"]["results"].values():
248-
title = page["fulltext"]
249-
exists = page["exists"]
250-
if "#" not in title and query.debug:
251-
print(title)
252-
# original position of "page_list.append(title)" line
253-
if exists == "1":
254-
page_list.append(title)
254+
if query.return_json:
255+
return result
256+
257+
for page in result["query"]["results"].values():
258+
title = page["fulltext"]
259+
exists = page["exists"]
260+
if "#" not in title and query.debug:
261+
print(title)
262+
# original position of "page_list.append(title)" line
263+
if exists == "1":
264+
page_list.append(title)
255265
return page_list
256266

257267
if query.parallel:
@@ -261,6 +271,12 @@ def semantic_search_(single_query):
261271
else:
262272
query_results = [semantic_search_(single_query=sq) for sq in query.query]
263273

274+
if query.return_json:
275+
# Each entry of query_results is the raw SMW result dict for one query.
276+
# Do not flatten dicts; always return the list of result dicts (one per
277+
# query), even when only a single query was passed.
278+
return query_results
279+
264280
return [item for sublist in query_results for item in sublist]
265281
# todo: @Simon: a list of lists of strings (sublist for each query in query list)
266282
# or a list of strings (results of all queries combined)?

tests/test_wiki_tools.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest.mock import MagicMock
2+
13
import osw.wiki_tools as wt
24

35

@@ -6,3 +8,123 @@ def test_create_flat_content_structure_from_wikitext():
68
expected = [{"Template": {"param": ["value"]}}]
79
result = wt.create_flat_content_structure_from_wikitext(wikitext)
810
assert result == expected
11+
12+
13+
def _ask_result(*titles):
14+
"""Build a minimal SMW ``ask`` API result dict for the given page titles."""
15+
return {
16+
"query": {
17+
"results": {
18+
title: {
19+
"fulltext": title,
20+
"fullurl": f"https://example.org/wiki/{title}",
21+
"namespace": 0,
22+
"exists": "1",
23+
"displaytitle": "",
24+
"printouts": {"SomeProperty": [f"{title}-value"]},
25+
}
26+
for title in titles
27+
}
28+
}
29+
}
30+
31+
32+
def test_semantic_search_return_json_single_query_returns_list_with_dict():
33+
result = _ask_result("Item:OSW1")
34+
site = MagicMock()
35+
site.api.return_value = result
36+
37+
out = wt.semantic_search(
38+
site, wt.SearchParam(query="[[HasType::Category:Item]]", return_json=True)
39+
)
40+
41+
# A single query still returns a list (of one raw result dict), not a bare dict
42+
# and not a flattened list of keys
43+
assert isinstance(out, list)
44+
assert out == [result]
45+
# printouts must be preserved intact
46+
assert out[0]["query"]["results"]["Item:OSW1"]["printouts"] == {
47+
"SomeProperty": ["Item:OSW1-value"]
48+
}
49+
50+
51+
def test_semantic_search_return_json_list_returns_list_of_dicts():
52+
result_a = _ask_result("Item:OSW1")
53+
result_b = _ask_result("Item:OSW2")
54+
site = MagicMock()
55+
site.api.side_effect = [result_a, result_b]
56+
57+
out = wt.semantic_search(
58+
site,
59+
wt.SearchParam(
60+
query=["[[HasType::Category:Item]]", "[[HasType::Category:Person]]"],
61+
return_json=True,
62+
),
63+
)
64+
65+
# A list of queries must return a list of result dicts, one per query
66+
assert isinstance(out, list)
67+
assert out == [result_a, result_b]
68+
69+
70+
def test_semantic_search_returns_flat_list_of_titles():
71+
result = _ask_result("Item:OSW1", "Item:OSW2")
72+
site = MagicMock()
73+
site.api.return_value = result
74+
75+
out = wt.semantic_search(site, "[[HasType::Category:Item]]")
76+
77+
# return_json=False (the default) still yields a flat list of page titles
78+
assert out == ["Item:OSW1", "Item:OSW2"]
79+
80+
81+
def _prefixsearch_result(*titles):
82+
"""Build a minimal MediaWiki ``prefixsearch`` API result dict."""
83+
return {
84+
"batchcomplete": "",
85+
"query": {
86+
"prefixsearch": [
87+
{"ns": 0, "title": title, "pageid": idx}
88+
for idx, title in enumerate(titles, start=1)
89+
]
90+
},
91+
}
92+
93+
94+
def test_prefix_search_return_json_single_query_returns_list_with_full_response():
95+
result = _prefixsearch_result("Star Wars", "Star Trek")
96+
site = MagicMock()
97+
site.api.return_value = result
98+
99+
out = wt.prefix_search(site, wt.SearchParam(query="Star", return_json=True))
100+
101+
# A single query returns a list of one full API response dict, not a flattened
102+
# list of individual prefixsearch entries
103+
assert isinstance(out, list)
104+
assert out == [result]
105+
assert out[0]["query"]["prefixsearch"][0]["title"] == "Star Wars"
106+
107+
108+
def test_prefix_search_return_json_list_returns_list_of_full_responses():
109+
result_a = _prefixsearch_result("Star Wars")
110+
result_b = _prefixsearch_result("Voyager")
111+
site = MagicMock()
112+
site.api.side_effect = [result_a, result_b]
113+
114+
out = wt.prefix_search(
115+
site, wt.SearchParam(query=["Star", "Voy"], return_json=True)
116+
)
117+
118+
# One full API response dict per query, not merged/flattened across queries
119+
assert out == [result_a, result_b]
120+
121+
122+
def test_prefix_search_returns_flat_list_of_titles():
123+
result = _prefixsearch_result("Star Wars", "Star Trek")
124+
site = MagicMock()
125+
site.api.return_value = result
126+
127+
out = wt.prefix_search(site, "Star")
128+
129+
# return_json=False (the default) still yields a flat list of page titles
130+
assert out == ["Star Wars", "Star Trek"]

0 commit comments

Comments
 (0)