Skip to content

Commit 1d26875

Browse files
authored
Merge 7b554bf into 052b56b
2 parents 052b56b + 7b554bf commit 1d26875

File tree

7 files changed

+109
-21
lines changed

7 files changed

+109
-21
lines changed

.github/workflows/style-check.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ jobs:
1313
python-version: 3.9
1414
- name: Install requirements
1515
run: pip install -r requirements.txt
16+
- name: Install test requirements
17+
run: pip install -r test-requirements.txt
1618
- name: Install pylint
1719
run: pip install pylint
1820
- name: Run pylint
19-
run: pylint -E generate.py
21+
run: find . -type f -name "*.py" | xargs pylint -E
2022
black:
2123
name: black
2224
runs-on: ubuntu-latest
@@ -46,4 +48,4 @@ jobs:
4648
- name: Install isort
4749
run: pip install isort
4850
- name: Run isort
49-
run: isort --ensure-newline-before-comments --diff generate.py
51+
run: isort --ensure-newline-before-comments --diff -v .

.github/workflows/type-check.yml

+17
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,20 @@ jobs:
1919
run: pip install mypy
2020
- name: Run mypy
2121
run: mypy .
22+
pyre:
23+
name: pyre
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@master
27+
- name: Set up Python 3.9
28+
uses: actions/setup-python@v1
29+
with:
30+
python-version: 3.9
31+
- name: Install requirements
32+
run: pip install -r requirements.txt
33+
- name: Install test requirements
34+
run: pip install -r test-requirements.txt
35+
- name: Install pyre
36+
run: pip install pyre-check
37+
- name: Run pyre
38+
run: pyre check

.pyre_configuration

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"source_directories": [
33
"."
4-
]
4+
],
5+
"site_package_search_strategy": "all",
6+
"strict": true
57
}

generate.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import asyncio
99
import logging
1010
from pathlib import Path
11-
from typing import Any, Coroutine, List
11+
from typing import Any, Awaitable, Callable, Coroutine, List
1212

1313
# https://github.com/kerrickstaley/genanki
1414
import genanki # type: ignore
@@ -34,7 +34,7 @@ def parse_args() -> argparse.Namespace:
3434
"--start", type=int, help="Start generation from this problem", default=0
3535
)
3636
parser.add_argument(
37-
"--stop", type=int, help="Stop generation on this problem", default=2 ** 64
37+
"--stop", type=int, help="Stop generation on this problem", default=2**64
3838
)
3939
parser.add_argument(
4040
"--page-size",
@@ -64,7 +64,7 @@ class LeetcodeNote(genanki.Note):
6464
"""
6565

6666
@property
67-
def guid(self):
67+
def guid(self) -> str:
6868
# Hash by leetcode task handle
6969
return genanki.guid_for(self.fields[0])
7070

@@ -179,7 +179,7 @@ async def generate(
179179
start, stop, page_size, list_id
180180
)
181181

182-
note_generators: List[Coroutine[Any, Any, LeetcodeNote]] = []
182+
note_generators: List[Awaitable[LeetcodeNote]] = []
183183

184184
task_handles = await leetcode_data.all_problems_handles()
185185

@@ -212,5 +212,5 @@ async def main() -> None:
212212

213213

214214
if __name__ == "__main__":
215-
loop = asyncio.get_event_loop()
215+
loop: asyncio.events.AbstractEventLoop = asyncio.get_event_loop()
216216
loop.run_until_complete(main())

leetcode_anki/helpers/leetcode.py

+36-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
# pylint: disable=missing-module-docstring
12
import functools
23
import json
34
import logging
45
import math
56
import os
67
import time
78
from functools import cached_property
8-
from typing import Callable, Dict, List, Tuple, Type
9+
from typing import Any, Callable, Dict, List, Tuple, Type, TypeVar
910

1011
# https://github.com/prius/python-leetcode
1112
import leetcode.api.default_api # type: ignore
@@ -48,16 +49,28 @@ def _get_leetcode_api_client() -> leetcode.api.default_api.DefaultApi:
4849
return api_instance
4950

5051

51-
def retry(times: int, exceptions: Tuple[Type[Exception]], delay: float) -> Callable:
52-
"""
53-
Retry Decorator
54-
Retries the wrapped function/method `times` times if the exceptions listed
55-
in `exceptions` are thrown
56-
"""
52+
_T = TypeVar("_T")
53+
54+
55+
class _RetryDecorator:
56+
_times: int
57+
_exceptions: Tuple[Type[Exception]]
58+
_delay: float
59+
60+
def __init__(
61+
self, times: int, exceptions: Tuple[Type[Exception]], delay: float
62+
) -> None:
63+
self._times = times
64+
self._exceptions = exceptions
65+
self._delay = delay
66+
67+
def __call__(self, func: Callable[..., _T]) -> Callable[..., _T]:
68+
times: int = self._times
69+
exceptions: Tuple[Type[Exception]] = self._exceptions
70+
delay: float = self._delay
5771

58-
def decorator(func):
5972
@functools.wraps(func)
60-
def wrapper(*args, **kwargs):
73+
def wrapper(*args: Any, **kwargs: Any) -> _T:
6174
for attempt in range(times - 1):
6275
try:
6376
return func(*args, **kwargs)
@@ -72,7 +85,17 @@ def wrapper(*args, **kwargs):
7285

7386
return wrapper
7487

75-
return decorator
88+
89+
def retry(
90+
times: int, exceptions: Tuple[Type[Exception]], delay: float
91+
) -> _RetryDecorator:
92+
"""
93+
Retry Decorator
94+
Retries the wrapped function/method `times` times if the exceptions listed
95+
in `exceptions` are thrown
96+
"""
97+
98+
return _RetryDecorator(times, exceptions, delay)
7699

77100

78101
class LeetcodeData:
@@ -230,7 +253,7 @@ def _get_problems_data(
230253
leetcode.models.graphql_question_detail.GraphqlQuestionDetail
231254
] = []
232255

233-
logging.info(f"Fetching {stop - start + 1} problems {page_size} per page")
256+
logging.info("Fetching %s problems %s per page", stop - start + 1, page_size)
234257

235258
for page in tqdm(
236259
range(math.ceil((stop - start + 1) / page_size)),
@@ -261,6 +284,8 @@ def _get_problem_data(
261284
if problem_slug in cache:
262285
return cache[problem_slug]
263286

287+
raise ValueError(f"Problem {problem_slug} is not in cache")
288+
264289
async def _get_description(self, problem_slug: str) -> str:
265290
"""
266291
Problem description

pyproject.toml

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ asyncio_mode = "strict"
33
testpaths = [
44
"test",
55
]
6+
7+
[tool.pylint]
8+
max-line-length = 88
9+
disable = ["line-too-long"]

test/helpers/test_leetcode.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ def dummy_return_question_detail_dict(
7777
@mock.patch("os.environ", mock.MagicMock(return_value={"LEETCODE_SESSION_ID": "test"}))
7878
@mock.patch("leetcode.auth", mock.MagicMock())
7979
class TestLeetcode:
80+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
81+
# `pytest.mark.asyncio`.
8082
@pytest.mark.asyncio
8183
async def test_get_leetcode_api_client(self) -> None:
8284
assert leetcode_anki.helpers.leetcode._get_leetcode_api_client()
8385

86+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
87+
# `pytest.mark.asyncio`.
8488
@pytest.mark.asyncio
8589
async def test_retry(self) -> None:
8690
decorator = leetcode_anki.helpers.leetcode.retry(
@@ -134,6 +138,8 @@ def setup(self) -> None:
134138
0, 10000
135139
)
136140

141+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
142+
# `pytest.mark.asyncio`.
137143
@pytest.mark.asyncio
138144
@mock.patch(
139145
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -142,6 +148,8 @@ def setup(self) -> None:
142148
async def test_init(self) -> None:
143149
self._leetcode_data._cache["test"] = QUESTION_DETAIL
144150

151+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
152+
# `pytest.mark.asyncio`.
145153
@pytest.mark.asyncio
146154
@mock.patch(
147155
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -151,6 +159,8 @@ async def test_get_description(self) -> None:
151159
self._leetcode_data._cache["test"] = QUESTION_DETAIL
152160
assert (await self._leetcode_data.description("test")) == "test content"
153161

162+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
163+
# `pytest.mark.asyncio`.
154164
@pytest.mark.asyncio
155165
@mock.patch(
156166
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -162,6 +172,8 @@ async def test_submissions(self) -> None:
162172
assert (await self._leetcode_data.submissions_total("test")) == 1
163173
assert (await self._leetcode_data.submissions_accepted("test")) == 1
164174

175+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
176+
# `pytest.mark.asyncio`.
165177
@pytest.mark.asyncio
166178
@mock.patch(
167179
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -173,6 +185,8 @@ async def test_difficulty_easy(self) -> None:
173185
QUESTION_DETAIL.difficulty = "Easy"
174186
assert "Easy" in (await self._leetcode_data.difficulty("test"))
175187

188+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
189+
# `pytest.mark.asyncio`.
176190
@pytest.mark.asyncio
177191
@mock.patch(
178192
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -184,6 +198,8 @@ async def test_difficulty_medium(self) -> None:
184198
QUESTION_DETAIL.difficulty = "Medium"
185199
assert "Medium" in (await self._leetcode_data.difficulty("test"))
186200

201+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
202+
# `pytest.mark.asyncio`.
187203
@pytest.mark.asyncio
188204
@mock.patch(
189205
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -195,6 +211,8 @@ async def test_difficulty_hard(self) -> None:
195211
QUESTION_DETAIL.difficulty = "Hard"
196212
assert "Hard" in (await self._leetcode_data.difficulty("test"))
197213

214+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
215+
# `pytest.mark.asyncio`.
198216
@pytest.mark.asyncio
199217
@mock.patch(
200218
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -205,6 +223,8 @@ async def test_paid(self) -> None:
205223

206224
assert (await self._leetcode_data.paid("test")) is False
207225

226+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
227+
# `pytest.mark.asyncio`.
208228
@pytest.mark.asyncio
209229
@mock.patch(
210230
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -215,6 +235,8 @@ async def test_problem_id(self) -> None:
215235

216236
assert (await self._leetcode_data.problem_id("test")) == "1"
217237

238+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
239+
# `pytest.mark.asyncio`.
218240
@pytest.mark.asyncio
219241
@mock.patch(
220242
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -225,6 +247,8 @@ async def test_likes(self) -> None:
225247

226248
assert (await self._leetcode_data.likes("test")) == 1
227249

250+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
251+
# `pytest.mark.asyncio`.
228252
@pytest.mark.asyncio
229253
@mock.patch(
230254
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -235,6 +259,8 @@ async def test_dislikes(self) -> None:
235259

236260
assert (await self._leetcode_data.dislikes("test")) == 1
237261

262+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
263+
# `pytest.mark.asyncio`.
238264
@pytest.mark.asyncio
239265
@mock.patch(
240266
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -248,6 +274,8 @@ async def test_tags(self) -> None:
248274
"difficulty-hard-tag",
249275
]
250276

277+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
278+
# `pytest.mark.asyncio`.
251279
@pytest.mark.asyncio
252280
@mock.patch(
253281
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -258,6 +286,8 @@ async def test_freq_bar(self) -> None:
258286

259287
assert (await self._leetcode_data.freq_bar("test")) == 1.1
260288

289+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
290+
# `pytest.mark.asyncio`.
261291
@pytest.mark.asyncio
262292
@mock.patch(
263293
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data",
@@ -267,6 +297,8 @@ async def test_get_problem_data(self) -> None:
267297
assert self._leetcode_data._cache["test"] == QUESTION_DETAIL
268298

269299
@mock.patch("time.sleep", mock.Mock())
300+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
301+
# `pytest.mark.asyncio`.
270302
@pytest.mark.asyncio
271303
async def test_get_problems_data_page(self) -> None:
272304
data = leetcode.models.graphql_data.GraphqlData(
@@ -281,14 +313,20 @@ async def test_get_problems_data_page(self) -> None:
281313
QUESTION_DETAIL
282314
]
283315

316+
# pyre-fixme[56]: Pyre was not able to infer the type of the decorator
317+
# `pytest.mark.asyncio`.
284318
@pytest.mark.asyncio
285319
@mock.patch(
286320
"leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_count",
287321
mock.Mock(return_value=234),
288322
)
289323
@mock.patch("leetcode_anki.helpers.leetcode.LeetcodeData._get_problems_data_page")
290-
async def test_get_problems_data(self, mock_get_problems_data_page) -> None:
291-
question_list = [QUESTION_DETAIL] * 234
324+
async def test_get_problems_data(
325+
self, mock_get_problems_data_page: mock.Mock
326+
) -> None:
327+
question_list: List[
328+
leetcode.models.graphql_question_detail.GraphqlQuestionDetail
329+
] = [QUESTION_DETAIL] * 234
292330

293331
def dummy(
294332
offset: int, page_size: int, page: int

0 commit comments

Comments
 (0)