Skip to content

Commit 7d31a75

Browse files
committed
Make it async
1 parent 611fbf6 commit 7d31a75

File tree

1 file changed

+69
-54
lines changed

1 file changed

+69
-54
lines changed

generate.py

+69-54
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#!/usr/bin/env python3
2+
23
import argparse
4+
import asyncio
35
import json
46
import logging
57
import os
68
import time
79
from functools import lru_cache
8-
from typing import Dict, Iterator, List, Tuple
10+
from typing import Any, Coroutine, Dict, Iterator, List, Tuple
911

1012
import diskcache
1113
# https://github.com/kerrickstaley/genanki
@@ -25,6 +27,8 @@
2527
CACHE_DIR = "cache"
2628
ALLOWED_EXTENSIONS = {".py", ".go"}
2729

30+
leetcode_api_access_lock = asyncio.Lock()
31+
2832

2933
logging.getLogger().setLevel(logging.INFO)
3034

@@ -66,12 +70,10 @@ def __init__(self) -> None:
6670
os.mkdir(CACHE_DIR)
6771
self._cache = diskcache.Cache(CACHE_DIR)
6872

69-
def _get_problem_data(self, problem_slug: str) -> Dict[str, str]:
73+
async def _get_problem_data(self, problem_slug: str) -> Dict[str, str]:
7074
if problem_slug in self._cache:
7175
return self._cache[problem_slug]
7276

73-
time.sleep(2) # Leetcode has a rate limiter
74-
7577
api_instance = self._api_instance
7678

7779
graphql_request = leetcode.GraphqlQuery(
@@ -134,35 +136,40 @@ def _get_problem_data(self, problem_slug: str) -> Dict[str, str]:
134136
variables=leetcode.GraphqlQueryVariables(title_slug=problem_slug),
135137
operation_name="getQuestionDetail",
136138
)
137-
data = api_instance.graphql_post(body=graphql_request).data.question
139+
140+
# Critical section. Don't allow more than one parallel request to
141+
# the Leetcode API
142+
async with leetcode_api_access_lock:
143+
time.sleep(2) # Leetcode has a rate limiter
144+
data = api_instance.graphql_post(body=graphql_request).data.question
138145

139146
# Save data in the cache
140147
self._cache[problem_slug] = data
141148

142149
return data
143150

144-
def _get_description(self, problem_slug: str) -> str:
145-
data = self._get_problem_data(problem_slug)
151+
async def _get_description(self, problem_slug: str) -> str:
152+
data = await self._get_problem_data(problem_slug)
146153
return data.content or "No content"
147154

148-
def _stats(self, problem_slug: str) -> Dict[str, str]:
149-
data = self._get_problem_data(problem_slug)
155+
async def _stats(self, problem_slug: str) -> Dict[str, str]:
156+
data = await self._get_problem_data(problem_slug)
150157
return json.loads(data.stats)
151158

152-
def submissions_total(self, problem_slug: str) -> int:
153-
return self._stats(problem_slug)["totalSubmissionRaw"]
159+
async def submissions_total(self, problem_slug: str) -> int:
160+
return (await self._stats(problem_slug))["totalSubmissionRaw"]
154161

155-
def submissions_accepted(self, problem_slug: str) -> int:
156-
return self._stats(problem_slug)["totalAcceptedRaw"]
162+
async def submissions_accepted(self, problem_slug: str) -> int:
163+
return (await self._stats(problem_slug))["totalAcceptedRaw"]
157164

158-
def description(self, problem_slug: str) -> str:
159-
return self._get_description(problem_slug)
165+
async def description(self, problem_slug: str) -> str:
166+
return await self._get_description(problem_slug)
160167

161-
def solution(self, problem_slug: str) -> str:
168+
async def solution(self, problem_slug: str) -> str:
162169
return ""
163170

164-
def difficulty(self, problem_slug: str) -> str:
165-
data = self._get_problem_data(problem_slug)
171+
async def difficulty(self, problem_slug: str) -> str:
172+
data = await self._get_problem_data(problem_slug)
166173
diff = data.difficulty
167174

168175
if diff == "Easy":
@@ -174,34 +181,34 @@ def difficulty(self, problem_slug: str) -> str:
174181
else:
175182
raise ValueError(f"Incorrect difficulty: {diff}")
176183

177-
def paid(self, problem_slug: str) -> str:
178-
data = self._get_problem_data(problem_slug)
184+
async def paid(self, problem_slug: str) -> str:
185+
data = await self._get_problem_data(problem_slug)
179186
return data.is_paid_only
180187

181-
def problem_id(self, problem_slug: str) -> str:
182-
data = self._get_problem_data(problem_slug)
188+
async def problem_id(self, problem_slug: str) -> str:
189+
data = await self._get_problem_data(problem_slug)
183190
return data.question_frontend_id
184191

185-
def likes(self, problem_slug: str) -> int:
186-
data = self._get_problem_data(problem_slug)
192+
async def likes(self, problem_slug: str) -> int:
193+
data = await self._get_problem_data(problem_slug)
187194
likes = data.likes
188195

189196
if not isinstance(likes, int):
190197
raise ValueError(f"Likes should be int: {likes}")
191198

192199
return likes
193200

194-
def dislikes(self, problem_slug: str) -> int:
195-
data = self._get_problem_data(problem_slug)
201+
async def dislikes(self, problem_slug: str) -> int:
202+
data = await self._get_problem_data(problem_slug)
196203
dislikes = data.dislikes
197204

198205
if not isinstance(dislikes, int):
199206
raise ValueError(f"Dislikes should be int: {dislikes}")
200207

201208
return dislikes
202209

203-
def tags(self, problem_slug: str) -> List[str]:
204-
data = self._get_problem_data(problem_slug)
210+
async def tags(self, problem_slug: str) -> List[str]:
211+
data = await self._get_problem_data(problem_slug)
205212
return list(map(lambda x: x.slug, data.topic_tags))
206213

207214

@@ -237,7 +244,7 @@ def get_leetcode_task_handles() -> Iterator[Tuple[str, str, str]]:
237244
yield (topic, stat.question__title, stat.question__title_slug)
238245

239246

240-
def generate_anki_note(
247+
async def generate_anki_note(
241248
leetcode_data: LeetcodeData,
242249
leetcode_model: genanki.Model,
243250
leetcode_task_handle: str,
@@ -248,29 +255,29 @@ def generate_anki_note(
248255
model=leetcode_model,
249256
fields=[
250257
leetcode_task_handle,
251-
str(leetcode_data.problem_id(leetcode_task_handle)),
258+
str(await leetcode_data.problem_id(leetcode_task_handle)),
252259
leetcode_task_title,
253260
topic,
254-
leetcode_data.description(leetcode_task_handle),
255-
leetcode_data.difficulty(leetcode_task_handle),
256-
"yes" if leetcode_data.paid(leetcode_task_handle) else "no",
257-
str(leetcode_data.likes(leetcode_task_handle)),
258-
str(leetcode_data.dislikes(leetcode_task_handle)),
259-
str(leetcode_data.submissions_total(leetcode_task_handle)),
260-
str(leetcode_data.submissions_accepted(leetcode_task_handle)),
261+
await leetcode_data.description(leetcode_task_handle),
262+
await leetcode_data.difficulty(leetcode_task_handle),
263+
"yes" if await leetcode_data.paid(leetcode_task_handle) else "no",
264+
str(await leetcode_data.likes(leetcode_task_handle)),
265+
str(await leetcode_data.dislikes(leetcode_task_handle)),
266+
str(await leetcode_data.submissions_total(leetcode_task_handle)),
267+
str(await leetcode_data.submissions_accepted(leetcode_task_handle)),
261268
str(
262269
int(
263-
leetcode_data.submissions_accepted(leetcode_task_handle)
264-
/ leetcode_data.submissions_total(leetcode_task_handle)
270+
await leetcode_data.submissions_accepted(leetcode_task_handle)
271+
/ await leetcode_data.submissions_total(leetcode_task_handle)
265272
* 100
266273
)
267274
),
268275
],
269-
tags=leetcode_data.tags(leetcode_task_handle),
276+
tags=await leetcode_data.tags(leetcode_task_handle),
270277
)
271278

272279

273-
def generate(start: int, stop: int) -> None:
280+
async def generate(start: int, stop: int) -> None:
274281
leetcode_model = genanki.Model(
275282
LEETCODE_ANKI_MODEL_ID,
276283
"Leetcode model",
@@ -328,27 +335,35 @@ def generate(start: int, stop: int) -> None:
328335
)
329336
leetcode_deck = genanki.Deck(LEETCODE_ANKI_DECK_ID, "leetcode")
330337
leetcode_data = LeetcodeData()
331-
for topic, leetcode_task_title, leetcode_task_handle in tqdm(
332-
list(get_leetcode_task_handles())[start:stop]
333-
):
334-
leetcode_note = generate_anki_note(
335-
leetcode_data,
336-
leetcode_model,
337-
leetcode_task_handle,
338-
leetcode_task_title,
339-
topic,
338+
339+
note_generators: List[Coroutine[Any, Any, LeetcodeNote]] = []
340+
341+
for topic, leetcode_task_title, leetcode_task_handle in list(
342+
get_leetcode_task_handles()
343+
)[start:stop]:
344+
note_generators.append(
345+
generate_anki_note(
346+
leetcode_data,
347+
leetcode_model,
348+
leetcode_task_handle,
349+
leetcode_task_title,
350+
topic,
351+
)
340352
)
341-
leetcode_deck.add_note(leetcode_note)
353+
354+
for leetcode_note in tqdm(note_generators):
355+
leetcode_deck.add_note(await leetcode_note)
342356

343357
genanki.Package(leetcode_deck).write_to_file(OUTPUT_FILE)
344358

345359

346-
def main() -> None:
360+
async def main() -> None:
347361
args = parse_args()
348362

349363
start, stop = args.start, args.stop
350-
generate(start, stop)
364+
await generate(start, stop)
351365

352366

353367
if __name__ == "__main__":
354-
main()
368+
loop = asyncio.get_event_loop()
369+
loop.run_until_complete(main())

0 commit comments

Comments
 (0)