Skip to content

Commit 1336459

Browse files
Refactor LeetCode interaction into centralized API class
Replaced individual modules with a single `LeetCodeAPI` class to handle problem fetching, daily challenges, and solutions. Simplified PracticeFactory logic by integrating API calls. Extended CLI and input handling for study plans and logging improvements.
1 parent 8883b5c commit 1336459

File tree

12 files changed

+511
-329
lines changed

12 files changed

+511
-329
lines changed

PracticeFactory.py

Lines changed: 135 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from lib.fetch_problems import get_random_problem
2-
from lib.fetch_daily_challenge import fetch_daily_challenge
3-
from lib.fetch_problem import fetch_problem
1+
import random
2+
from typing import List, Dict, Any, Optional
3+
44
from utils.logger import log, LogLevel
55
from handlers.SolutionHandler import SolutionHandler
6+
from api.LeetCodeAPI import LeetCodeAPI
67

78
difficulty_map = {
89
"easy": "Easy",
@@ -12,104 +13,181 @@
1213

1314

1415
class PracticeMode:
15-
def handle(self, args):
16-
raise NotImplementedError("This method should be implemented by subclasses.")
16+
def __init__(self):
17+
self.leetcode_api = LeetCodeAPI()
1718

18-
19-
class RandomProblemMode(PracticeMode):
2019
def handle(self, args):
21-
problem = get_random_problem(args["difficulties"])
20+
raise NotImplementedError("This method should be implemented by subclasses.")
2221

23-
# Log problem details
22+
def log_problem_details(self, problem, difficulty_label, url):
2423
log(f"🎯 Problem Selected: {problem['title']}", LogLevel.INFO)
25-
log(f"✨ Difficulty: {difficulty_map[problem['difficulty']]}", LogLevel.INFO)
26-
log(f"🔗 URL: https://leetcode.com/problems/{problem['slug']}", LogLevel.INFO)
24+
log(f"✨ Difficulty: {difficulty_label}", LogLevel.INFO)
25+
log(f"🔗 URL: {url}", LogLevel.INFO)
2726

28-
# Open in browser if flag is set
29-
if args["open_in_browser"]:
27+
def open_in_browser(self, url, open_flag):
28+
if open_flag:
3029
import webbrowser
3130

32-
webbrowser.open(f"https://leetcode.com/problems/{problem['slug']}")
31+
webbrowser.open(url)
3332

33+
def create_and_solve_handler(self, problem_slug, difficulty_label, args):
3434
handler = SolutionHandler(
35-
problem=problem["slug"],
36-
difficulty=difficulty_map[problem["difficulty"]],
35+
problem=problem_slug,
36+
difficulty=difficulty_label,
3737
editor=args["editor"],
3838
language=args["language"],
3939
time_limit=args["time_limit"],
4040
)
4141
handler.solve()
4242

43+
def get_random_problem(
44+
self,
45+
category_slug: Optional[str] = None,
46+
difficulties: Optional[List[str]] = None,
47+
) -> Optional[Dict[str, Any]]:
48+
"""
49+
Get a random problem from LeetCode.
50+
:param category_slug: Slug of the category to fetch problems from.
51+
:param difficulties: Difficulty levels of the problems (e.g., "Easy", "Medium", "Hard").
52+
:return: A random problem dictionary or None if no problems are found.
53+
"""
54+
problems = self.leetcode_api.fetch_problems(
55+
category_slug=category_slug, limit=1000, difficulties=difficulties
56+
)
4357

44-
class DailyChallengeMode(PracticeMode):
58+
if not problems:
59+
return None
60+
61+
random_index = random.randint(0, len(problems) - 1)
62+
return problems[random_index]
63+
64+
def get_study_plan_problems(self, slug: str) -> List[Dict[str, Any]]:
65+
"""
66+
Fetch the list of problems for a specific study plan.
67+
:param slug: The slug of the study plan (e.g., "leetcode-75").
68+
:return: A list of dictionaries, each containing details of a problem.
69+
"""
70+
study_plan = self.leetcode_api.get_study_plan(slug)
71+
72+
if not study_plan:
73+
raise Exception(f"❌ Study plan not found for slug: {slug}")
74+
75+
# Gather all questions from the study plan subgroups
76+
problems = []
77+
for subgroup in study_plan.get("planSubGroups", []):
78+
questions = subgroup.get("questions", [])
79+
problems.extend(questions) # Add all questions to the list
80+
81+
if not problems:
82+
raise Exception(f"❌ No problems found for study plan: {slug}")
83+
84+
return problems
85+
86+
def get_random_study_plan_problem(self, slug: str) -> Optional[Dict[str, Any]]:
87+
"""
88+
Get a random problem from a specific study plan.
89+
:param slug: The slug of the study plan (e.g., "leetcode-75").
90+
:return: A random problem dictionary or None if no problems are found.
91+
"""
92+
problems = self.get_study_plan_problems(slug)
93+
94+
if not problems:
95+
return None
96+
97+
random_index = random.randint(0, len(problems) - 1)
98+
return problems[random_index]
99+
100+
101+
class RandomProblemMode(PracticeMode):
45102
def handle(self, args):
103+
log("Selected 🎲 Random Problem Mode", LogLevel.INFO)
104+
46105
try:
47-
daily_challenge = fetch_daily_challenge()
106+
# Use the LeetCodeAPI's fetch_problems method
107+
problem = self.get_random_problem(args["difficulties"])
108+
109+
if not problem:
110+
log("No problems found for the selected difficulties.", LogLevel.ERROR)
111+
return
112+
113+
difficulty_label = difficulty_map[problem["difficulty"].lower()]
114+
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
48115

116+
self.log_problem_details(problem, difficulty_label, url)
117+
self.open_in_browser(url, args["open_in_browser"])
118+
self.create_and_solve_handler(problem["titleSlug"], difficulty_label, args)
119+
except Exception as e:
120+
log(f"Failed to fetch random problem: {str(e)}", LogLevel.ERROR)
121+
122+
123+
class DailyChallengeMode(PracticeMode):
124+
def handle(self, args):
125+
log("Selected 📅 Daily Challenge Mode", LogLevel.INFO)
126+
127+
try:
128+
# Use the LeetCodeAPI's fetch_daily_challenge method
129+
daily_challenge = self.leetcode_api.fetch_daily_challenge()
49130
difficulty_label = difficulty_map[
50131
daily_challenge["question"]["difficulty"].lower()
51132
]
133+
url = f"https://leetcode.com{daily_challenge['link'].removesuffix('/')}"
52134

53-
# Log daily challenge details
54135
log("🎯 Daily Coding Challenge:", LogLevel.INFO)
55136
log(f"📅 Date: {daily_challenge['date']}", LogLevel.INFO)
56-
log(f"📖 Title: {daily_challenge['question']['title']}", LogLevel.INFO)
57-
log(f"✨ Difficulty: {difficulty_label}", LogLevel.INFO)
58-
log(
59-
f"🔗 Link: https://leetcode.com{daily_challenge['link']}", LogLevel.INFO
60-
)
61-
62-
if args["open_in_browser"]:
63-
import webbrowser
64-
65-
webbrowser.open(f"https://leetcode.com{daily_challenge['link']}")
66-
67-
handler = SolutionHandler(
68-
problem=daily_challenge["question"]["titleSlug"],
69-
difficulty=difficulty_label,
70-
editor=args["editor"],
71-
language=args["language"],
72-
time_limit=args["time_limit"],
137+
self.log_problem_details(daily_challenge["question"], difficulty_label, url)
138+
self.open_in_browser(url, args["open_in_browser"])
139+
self.create_and_solve_handler(
140+
daily_challenge["question"]["titleSlug"], difficulty_label, args
73141
)
74-
handler.solve()
75142
except Exception as e:
76143
log(f"Failed to fetch daily challenge: {str(e)}", LogLevel.ERROR)
77144

78145

79146
class CustomPracticeMode(PracticeMode):
80147
def handle(self, args):
81-
problems = args["problems"]
148+
log("Selected 🧩 Custom Practice Mode", LogLevel.INFO)
82149

83-
for slug in problems:
150+
for slug in args["problems"]:
84151
log(f"🔍 Fetching details for problem: {slug}", LogLevel.INFO)
85152
try:
86-
problem = fetch_problem(slug.strip())
153+
problem = self.leetcode_api.fetch_problem(slug.strip())
87154

88155
if not problem:
89156
log(f"Problem with slug '{slug}' not found.", LogLevel.ERROR)
90157
continue
91158

92-
log(f"🎯 Problem Selected: {problem['title']}", LogLevel.INFO)
93-
log(f"✨ Difficulty: {problem['difficulty']}", LogLevel.INFO)
94-
log(f"🔗 URL: https://leetcode.com/problems/{slug}", LogLevel.INFO)
159+
difficulty_label = difficulty_map[problem["difficulty"].lower()]
160+
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
95161

96-
if args["open_in_browser"]:
97-
import webbrowser
98-
99-
webbrowser.open(f"https://leetcode.com/problems/{problem['slug']}")
100-
101-
handler = SolutionHandler(
102-
problem=slug,
103-
difficulty=problem["difficulty"],
104-
editor=args["editor"],
105-
language=args["language"],
106-
time_limit=args["time_limit"],
107-
)
108-
handler.solve()
162+
self.log_problem_details(problem, difficulty_label, url)
163+
self.open_in_browser(url, args["open_in_browser"])
164+
self.create_and_solve_handler(slug, difficulty_label, args)
109165
except Exception as e:
110166
log(f"Failed to process problem '{slug}': {str(e)}", LogLevel.ERROR)
111167

112168

169+
class StudyPlanMode(PracticeMode):
170+
def handle(self, args):
171+
try:
172+
log(f"Selected 🎯 Study Plan Mode: {args['study_plan']}", LogLevel.INFO)
173+
174+
# Use the LeetCodeAPI's fetch_problems method
175+
problem = self.get_random_study_plan_problem(args["study_plan"])
176+
177+
if not problem:
178+
log("No problems found for the selected study plan.", LogLevel.ERROR)
179+
return
180+
181+
difficulty_label = difficulty_map[problem["difficulty"].lower()]
182+
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
183+
184+
self.log_problem_details(problem, difficulty_label, url)
185+
self.open_in_browser(url, args["open_in_browser"])
186+
self.create_and_solve_handler(problem["titleSlug"], difficulty_label, args)
187+
except Exception as e:
188+
log(f"Failed to fetch random problem: {str(e)}", LogLevel.ERROR)
189+
190+
113191
class PracticeModeFactory:
114192
@staticmethod
115193
def get_mode(selection_mode: str) -> PracticeMode:
@@ -120,6 +198,6 @@ def get_mode(selection_mode: str) -> PracticeMode:
120198
elif selection_mode == "custom":
121199
return CustomPracticeMode()
122200
elif selection_mode == "study-plan":
123-
raise ValueError("Study Plan mode is not yet supported.")
201+
return StudyPlanMode()
124202
else:
125203
raise ValueError(f"Unsupported mode: {selection_mode}")

0 commit comments

Comments
 (0)