Skip to content

Commit 0080014

Browse files
Modularize practice modes into separate files
Separated individual practice modes (Random, Daily, Custom, Study Plan) into distinct files under a new `modes/` directory. This improves maintainability and reduces complexity in `PracticeHandler.py`. Common utilities, such as `difficulty_map`, were extracted into reusable modules.
1 parent 23c0e62 commit 0080014

File tree

8 files changed

+243
-240
lines changed

8 files changed

+243
-240
lines changed

handlers/PracticeHandler.py

Lines changed: 5 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -1,247 +1,12 @@
1-
import random
2-
import html2text
3-
import markdown
4-
5-
from typing import List, Dict, Any, Optional
6-
7-
from utils.logger import log, LogLevel
8-
9-
from handlers.SolutionHandler import SolutionHandler
10-
from handlers.CacheHandler import cached_api
11-
12-
difficulty_map = {
13-
"easy": "Easy",
14-
"medium": "Medium",
15-
"hard": "Hard",
16-
}
17-
18-
19-
class PracticeMode:
20-
def __init__(self):
21-
pass
22-
23-
def handle(self, args):
24-
raise NotImplementedError("This method should be implemented by subclasses.")
25-
26-
def log_problem_details(self, problem, difficulty_label, url):
27-
log(f"🎯 Problem Selected: {problem.get('title', 'Unknown')}", LogLevel.INFO)
28-
log(f"✨ Difficulty: {difficulty_label}", LogLevel.INFO)
29-
log(f"🔗 URL: {url}", LogLevel.INFO)
30-
31-
topic_tags = problem.get("topicTags", [])
32-
if topic_tags:
33-
tags = ", ".join(tag.get("name", "Unknown") for tag in topic_tags)
34-
log(f"🏷️ Tags: {tags}", LogLevel.INFO)
35-
36-
ac_rate = problem.get("acRate")
37-
if ac_rate is not None:
38-
log(f"📈 Acceptance Rate: {ac_rate:.2f}%", LogLevel.INFO)
39-
40-
# Assuming `content` is the Markdown content
41-
content = problem.get("content")
42-
if content:
43-
try:
44-
html_content = markdown.markdown(content)
45-
46-
text_maker = html2text.HTML2Text()
47-
text_maker.ignore_links = True
48-
plain_text = text_maker.handle(html_content)
49-
50-
log(plain_text, LogLevel.INFO)
51-
except Exception as e:
52-
log(f"Failed to convert problem content: {str(e)}", LogLevel.ERROR)
53-
54-
def open_in_browser(self, url, open_flag):
55-
if open_flag:
56-
import webbrowser
57-
58-
webbrowser.open(url)
59-
60-
def create_and_solve_handler(
61-
self, problem_slug, code_snippets, difficulty_label, args
62-
):
63-
# Determine the starter code based on the chosen language
64-
65-
code = ""
66-
for item in code_snippets:
67-
if item.get("lang").lower() == args["language"].lower():
68-
code = item.get("code")
69-
break
70-
71-
if not code:
72-
log(
73-
f"Starter code not found for language: {args['language']}",
74-
LogLevel.ERROR,
75-
)
76-
return
77-
78-
handler = SolutionHandler(
79-
problem=problem_slug,
80-
code=code,
81-
difficulty=difficulty_label,
82-
editor=args["editor"],
83-
language=args["language"],
84-
time_limit=args["time_limit"],
85-
)
86-
handler.solve()
87-
88-
def get_random_problem(
89-
self,
90-
difficulties: Optional[List[str]] = None,
91-
) -> Optional[Dict[str, Any]]:
92-
"""
93-
Get a random problem from LeetCode.
94-
:param difficulties: Difficulty levels of the problems (e.g., "Easy", "Medium", "Hard").
95-
:return: A random problem dictionary or None if no problems are found.
96-
"""
97-
problems = cached_api.fetch_problems(limit=1000, difficulties=difficulties)
98-
99-
if not problems:
100-
return None
101-
102-
random_index = random.randint(0, len(problems) - 1)
103-
return problems[random_index]
104-
105-
def get_study_plan_problems(self, slug: str) -> List[Dict[str, Any]]:
106-
"""
107-
Fetch the list of problems for a specific study plan.
108-
:param slug: The slug of the study plan (e.g., "leetcode-75").
109-
:return: A list of dictionaries, each containing details of a problem.
110-
"""
111-
study_plan = cached_api.get_study_plan(slug)
112-
113-
if not study_plan:
114-
raise Exception(f"❌ Study plan not found for slug: {slug}")
115-
116-
# Gather all questions from the study plan subgroups
117-
problems = []
118-
for subgroup in study_plan.get("planSubGroups", []):
119-
questions = subgroup.get("questions", [])
120-
problems.extend(questions) # Add all questions to the list
121-
122-
if not problems:
123-
raise Exception(f"❌ No problems found for study plan: {slug}")
124-
125-
return problems
126-
127-
def get_random_study_plan_problem(self, slug: str) -> Optional[Dict[str, Any]]:
128-
"""
129-
Get a random problem from a specific study plan.
130-
:param slug: The slug of the study plan (e.g., "leetcode-75").
131-
:return: A random problem dictionary or None if no problems are found.
132-
"""
133-
problems = self.get_study_plan_problems(slug)
134-
135-
if not problems:
136-
return None
137-
138-
random_index = random.randint(0, len(problems) - 1)
139-
problem = problems[random_index]
140-
141-
# Get the problem in the right format
142-
return cached_api.fetch_problem(problem["titleSlug"])
143-
144-
145-
class RandomProblemMode(PracticeMode):
146-
def handle(self, args):
147-
log("Selected 🎲 Random Problem Mode", LogLevel.INFO)
148-
149-
try:
150-
# Use the LeetCodeAPI's fetch_problems method
151-
problem = self.get_random_problem(difficulties=args["difficulties"])
152-
153-
if not problem:
154-
log("No problems found for the selected difficulties.", LogLevel.ERROR)
155-
return
156-
157-
difficulty_label = difficulty_map[problem["difficulty"].lower()]
158-
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
159-
160-
self.log_problem_details(problem, difficulty_label, url)
161-
self.open_in_browser(url, args["open_in_browser"])
162-
self.create_and_solve_handler(
163-
problem["titleSlug"], problem["codeSnippets"], difficulty_label, args
164-
)
165-
except Exception as e:
166-
log(f"Failed to fetch random problem: {str(e)}", LogLevel.ERROR)
167-
168-
169-
class DailyChallengeMode(PracticeMode):
170-
def handle(self, args):
171-
log("Selected 📅 Daily Challenge Mode", LogLevel.INFO)
172-
173-
try:
174-
# Use the LeetCodeAPI's fetch_daily_challenge method
175-
daily_challenge = cached_api.fetch_daily_challenge()
176-
difficulty_label = difficulty_map[
177-
daily_challenge["question"]["difficulty"].lower()
178-
]
179-
url = f"https://leetcode.com{daily_challenge['link'].removesuffix('/')}"
180-
181-
log("🎯 Daily Coding Challenge:", LogLevel.INFO)
182-
log(f"📅 Date: {daily_challenge['date']}", LogLevel.INFO)
183-
self.log_problem_details(daily_challenge["question"], difficulty_label, url)
184-
self.open_in_browser(url, args["open_in_browser"])
185-
self.create_and_solve_handler(
186-
daily_challenge["question"]["titleSlug"],
187-
daily_challenge["codeSnippets"],
188-
difficulty_label,
189-
args,
190-
)
191-
except Exception as e:
192-
log(f"Failed to fetch daily challenge: {str(e)}", LogLevel.ERROR)
193-
194-
195-
class CustomPracticeMode(PracticeMode):
196-
def handle(self, args):
197-
log("Selected 🧩 Custom Practice Mode", LogLevel.INFO)
198-
199-
for slug in args["problems"]:
200-
try:
201-
problem = cached_api.fetch_problem(slug.strip())
202-
203-
if not problem:
204-
log(f"Problem with slug '{slug}' not found.", LogLevel.ERROR)
205-
continue
206-
207-
difficulty_label = difficulty_map[problem["difficulty"].lower()]
208-
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
209-
210-
self.log_problem_details(problem, difficulty_label, url)
211-
self.open_in_browser(url, args["open_in_browser"])
212-
self.create_and_solve_handler(
213-
slug, problem["codeSnippets"], difficulty_label, args
214-
)
215-
except Exception as e:
216-
log(f"Failed to process problem '{slug}': {str(e)}", LogLevel.ERROR)
217-
218-
219-
class StudyPlanMode(PracticeMode):
220-
def handle(self, args):
221-
try:
222-
log(f"Selected 🎯 Study Plan Mode: {args['plan_name']}", LogLevel.INFO)
223-
224-
problem = self.get_random_study_plan_problem(args["plan_name"])
225-
226-
if not problem:
227-
log("No problems found for the selected study plan.", LogLevel.ERROR)
228-
return
229-
230-
difficulty_label = difficulty_map[problem["difficulty"].lower()]
231-
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
232-
233-
self.log_problem_details(problem, difficulty_label, url)
234-
self.open_in_browser(url, args["open_in_browser"])
235-
self.create_and_solve_handler(
236-
problem["titleSlug"], problem["codeSnippets"], difficulty_label, args
237-
)
238-
except Exception as e:
239-
log(f"Failed to fetch random problem: {str(e)}", LogLevel.ERROR)
1+
from modes.RandomProblemMode import RandomProblemMode
2+
from modes.DailyChallengeMode import DailyChallengeMode
3+
from modes.CustomPracticeMode import CustomPracticeMode
4+
from modes.StudyPlanMode import StudyPlanMode
2405

2416

2427
class PracticeModeHandler:
2438
@staticmethod
244-
def get_mode(selection_mode: str) -> PracticeMode:
9+
def get_mode(selection_mode: str):
24510
if selection_mode == "random":
24611
return RandomProblemMode()
24712
elif selection_mode == "daily":

modes/CustomPracticeMode.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from modes.PracticeMode import PracticeMode
2+
from utils.constants import difficulty_map
3+
from utils.logger import log, LogLevel
4+
from handlers.CacheHandler import cached_api
5+
6+
7+
class CustomPracticeMode(PracticeMode):
8+
def handle(self, args):
9+
log("Selected 🧩 Custom Practice Mode", LogLevel.INFO)
10+
for slug in args["problems"]:
11+
try:
12+
problem = cached_api.fetch_problem(slug.strip())
13+
if not problem:
14+
log(f"Problem with slug '{slug}' not found.", LogLevel.ERROR)
15+
continue
16+
difficulty_label = difficulty_map[problem["difficulty"].lower()]
17+
url = f"https://leetcode.com/problems/{problem['titleSlug']}"
18+
self.log_problem_details(problem, difficulty_label, url)
19+
self.open_in_browser(url, args["open_in_browser"])
20+
self.create_and_solve_handler(
21+
slug, problem["codeSnippets"], difficulty_label, args
22+
)
23+
except Exception as e:
24+
log(f"Failed to process problem '{slug}': {str(e)}", LogLevel.ERROR)

modes/DailyChallengeMode.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from modes.PracticeMode import PracticeMode
2+
from utils.constants import difficulty_map
3+
from utils.logger import log, LogLevel
4+
from handlers.CacheHandler import cached_api
5+
6+
7+
class DailyChallengeMode(PracticeMode):
8+
def handle(self, args):
9+
log("Selected 📅 Daily Challenge Mode", LogLevel.INFO)
10+
try:
11+
daily_challenge = cached_api.fetch_daily_challenge()
12+
difficulty_label = difficulty_map[
13+
daily_challenge["question"]["difficulty"].lower()
14+
]
15+
url = f"https://leetcode.com{daily_challenge['link'].removesuffix('/')}"
16+
log("🎯 Daily Coding Challenge:", LogLevel.INFO)
17+
log(f"📅 Date: {daily_challenge['date']}", LogLevel.INFO)
18+
self.log_problem_details(daily_challenge["question"], difficulty_label, url)
19+
self.open_in_browser(url, args["open_in_browser"])
20+
self.create_and_solve_handler(
21+
daily_challenge["question"]["titleSlug"],
22+
daily_challenge["codeSnippets"],
23+
difficulty_label,
24+
args,
25+
)
26+
except Exception as e:
27+
log(f"Failed to fetch daily challenge: {str(e)}", LogLevel.ERROR)

0 commit comments

Comments
 (0)