jupyterlab-leetcode 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. jupyterlab_leetcode/__init__.py +32 -0
  2. jupyterlab_leetcode/_version.py +4 -0
  3. jupyterlab_leetcode/handlers/__init__.py +31 -0
  4. jupyterlab_leetcode/handlers/base_handler.py +10 -0
  5. jupyterlab_leetcode/handlers/cookie_handler.py +87 -0
  6. jupyterlab_leetcode/handlers/leetcode_handler.py +464 -0
  7. jupyterlab_leetcode/labextension/package.json +217 -0
  8. jupyterlab_leetcode/labextension/schemas/jupyterlab-leetcode/package.json.orig +212 -0
  9. jupyterlab_leetcode/labextension/schemas/jupyterlab-leetcode/plugin.json +8 -0
  10. jupyterlab_leetcode/labextension/static/266.1f3531e8ca41f032af95.js +1 -0
  11. jupyterlab_leetcode/labextension/static/728.369f44bbf921c6ebc9ee.js +1 -0
  12. jupyterlab_leetcode/labextension/static/880.2d185bdba596d2d9e8b0.js +1 -0
  13. jupyterlab_leetcode/labextension/static/remoteEntry.6f8397bd75fe51149f55.js +1 -0
  14. jupyterlab_leetcode/labextension/static/style.js +4 -0
  15. jupyterlab_leetcode/labextension/static/third-party-licenses.json +22 -0
  16. jupyterlab_leetcode/tests/__init__.py +1 -0
  17. jupyterlab_leetcode/tests/test_handlers.py +13 -0
  18. jupyterlab_leetcode/utils/notebook.template.json +111 -0
  19. jupyterlab_leetcode/utils/notebook_generator.py +190 -0
  20. jupyterlab_leetcode/utils/utils.py +9 -0
  21. jupyterlab_leetcode-0.1.1.data/data/etc/jupyter/jupyter_server_config.d/jupyterlab_leetcode.json +7 -0
  22. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/install.json +5 -0
  23. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/package.json +217 -0
  24. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/schemas/jupyterlab-leetcode/package.json.orig +212 -0
  25. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/schemas/jupyterlab-leetcode/plugin.json +8 -0
  26. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/266.1f3531e8ca41f032af95.js +1 -0
  27. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/728.369f44bbf921c6ebc9ee.js +1 -0
  28. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/880.2d185bdba596d2d9e8b0.js +1 -0
  29. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/remoteEntry.6f8397bd75fe51149f55.js +1 -0
  30. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/style.js +4 -0
  31. jupyterlab_leetcode-0.1.1.data/data/share/jupyter/labextensions/jupyterlab-leetcode/static/third-party-licenses.json +22 -0
  32. jupyterlab_leetcode-0.1.1.dist-info/METADATA +202 -0
  33. jupyterlab_leetcode-0.1.1.dist-info/RECORD +35 -0
  34. jupyterlab_leetcode-0.1.1.dist-info/WHEEL +4 -0
  35. jupyterlab_leetcode-0.1.1.dist-info/licenses/LICENSE +29 -0
@@ -0,0 +1,32 @@
1
+ try:
2
+ from ._version import __version__
3
+ except ImportError:
4
+ # Fallback when using the package in dev mode without installing
5
+ # in editable mode with pip. It is highly recommended to install
6
+ # the package from a stable release or in editable mode: https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs
7
+ import warnings
8
+
9
+ warnings.warn("Importing 'jupyterlab_leetcode' outside a proper installation.")
10
+ __version__ = "dev"
11
+ from .handlers import setup_handlers
12
+
13
+
14
+ def _jupyter_labextension_paths():
15
+ return [{"src": "labextension", "dest": "jupyterlab-leetcode"}]
16
+
17
+
18
+ def _jupyter_server_extension_points():
19
+ return [{"module": "jupyterlab_leetcode"}]
20
+
21
+
22
+ def _load_jupyter_server_extension(server_app):
23
+ """Registers the API handler to receive HTTP requests from the frontend extension.
24
+
25
+ Parameters
26
+ ----------
27
+ server_app: jupyterlab.labapp.LabApp
28
+ JupyterLab application instance
29
+ """
30
+ setup_handlers(server_app.web_app)
31
+ name = "jupyterlab_leetcode"
32
+ server_app.log.info(f"Registered {name} server extension")
@@ -0,0 +1,4 @@
1
+ # This file is auto-generated by Hatchling. As such, do not:
2
+ # - modify
3
+ # - track in version control e.g. be sure to add to .gitignore
4
+ __version__ = VERSION = '0.1.1'
@@ -0,0 +1,31 @@
1
+ from jupyter_server.utils import url_path_join
2
+
3
+ from .base_handler import BaseHandler
4
+ from .cookie_handler import GetCookieHandler
5
+ from .leetcode_handler import (CreateNotebookHandler, LeetCodeProfileHandler,
6
+ LeetCodeQuestionHandler,
7
+ LeetCodeStatisticsHandler,
8
+ SubmitNotebookHandler)
9
+
10
+
11
+ def setup_handlers(web_app):
12
+ host_pattern = ".*$"
13
+ base_url = web_app.settings["base_url"]
14
+ handlers: list[type[BaseHandler]] = [
15
+ GetCookieHandler,
16
+ LeetCodeProfileHandler,
17
+ LeetCodeStatisticsHandler,
18
+ LeetCodeQuestionHandler,
19
+ CreateNotebookHandler,
20
+ SubmitNotebookHandler,
21
+ ]
22
+
23
+ web_app.add_handlers(
24
+ host_pattern,
25
+ list(
26
+ map(
27
+ lambda h: (url_path_join(base_url, "jupyterlab-leetcode", h.route), h),
28
+ handlers,
29
+ )
30
+ ),
31
+ )
@@ -0,0 +1,10 @@
1
+ from jupyter_server.base.handlers import APIHandler
2
+
3
+
4
+ class BaseHandler(APIHandler):
5
+ """Base handler for JupyterLab LeetCode extension.
6
+ This class extends APIHandler to provide a base for other handlers.
7
+ It can be used to define common functionality or properties for all handlers.
8
+ """
9
+
10
+ route = r""
@@ -0,0 +1,87 @@
1
+ import json
2
+ import time
3
+ from http.cookiejar import Cookie
4
+ from typing import cast
5
+
6
+ import browser_cookie3
7
+ import tornado
8
+ from tornado.httpclient import AsyncHTTPClient
9
+ from tornado.httputil import HTTPHeaders
10
+
11
+ from ..utils.utils import first
12
+ from .base_handler import BaseHandler
13
+
14
+ BROWSER_COOKIE_METHOD_MAP = {
15
+ "chrome": browser_cookie3.chrome,
16
+ "chromium": browser_cookie3.chromium,
17
+ "opera": browser_cookie3.opera,
18
+ "opera_gx": browser_cookie3.opera_gx,
19
+ "brave": browser_cookie3.brave,
20
+ "edge": browser_cookie3.edge,
21
+ "vivaldi": browser_cookie3.vivaldi,
22
+ "firefox": browser_cookie3.firefox,
23
+ "librewolf": browser_cookie3.librewolf,
24
+ "safari": browser_cookie3.safari,
25
+ "arc": browser_cookie3.arc,
26
+ }
27
+
28
+
29
+ class GetCookieHandler(BaseHandler):
30
+ route = r"cookies"
31
+
32
+ @tornado.web.authenticated
33
+ def get(self):
34
+ self.log.debug("Loading all cookies for LeetCode...")
35
+ browser = self.get_query_argument("browser", "", strip=True)
36
+ if not browser:
37
+ self.set_status(400)
38
+ self.finish(json.dumps({"message": "Browser parameter is required"}))
39
+ return
40
+
41
+ if browser not in BROWSER_COOKIE_METHOD_MAP:
42
+ self.set_status(400)
43
+ self.finish(json.dumps({"message": f"Unsupported browser: {browser}"}))
44
+ return
45
+
46
+ cj = BROWSER_COOKIE_METHOD_MAP[browser](domain_name="leetcode.com")
47
+ cookie_session = first(cj, lambda c: c.name == "LEETCODE_SESSION")
48
+ cookie_csrf = first(cj, lambda c: c.name == "csrftoken")
49
+ exist = bool(cookie_session and cookie_csrf)
50
+ expired = exist and (
51
+ cast(Cookie, cookie_session).is_expired()
52
+ or cast(Cookie, cookie_csrf).is_expired()
53
+ )
54
+ checked = exist and not expired
55
+
56
+ resp = {"exist": exist, "expired": expired, "checked": checked}
57
+
58
+ if checked:
59
+ cookie_session_expires = cast(Cookie, cookie_session).expires
60
+ max_age = (
61
+ cookie_session_expires - int(time.time())
62
+ if cookie_session_expires is not None
63
+ else 3600 * 24 * 14
64
+ )
65
+ self.set_cookie("leetcode_browser", browser, max_age=max_age)
66
+ self.settings.update(
67
+ leetcode_browser=browser,
68
+ leetcode_cookiejar=cj,
69
+ leetcode_headers=HTTPHeaders(
70
+ {
71
+ "Cookie": "; ".join(f"{c.name}={c.value}" for c in cj),
72
+ "Content-Type": "application/json",
73
+ "Origin": "https://leetcode.com",
74
+ "Referer": "https://leetcode.com/",
75
+ "X-CsrfToken": (
76
+ cookie_csrf.value
77
+ if cookie_csrf and cookie_csrf.value
78
+ else ""
79
+ ),
80
+ }
81
+ ),
82
+ )
83
+ AsyncHTTPClient.configure(
84
+ None, defaults=dict(user_agent=self.request.headers.get("user-agent"))
85
+ )
86
+
87
+ self.finish(json.dumps(resp))
@@ -0,0 +1,464 @@
1
+ import json
2
+ import os
3
+ from typing import Any, Mapping, cast, overload
4
+
5
+ import tornado
6
+ from tornado.gen import multi
7
+ from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPResponse
8
+ from tornado.httputil import HTTPHeaders
9
+
10
+ from ..utils.notebook_generator import NotebookGenerator
11
+ from ..utils.utils import first
12
+ from .base_handler import BaseHandler
13
+
14
+ LEETCODE_URL = "https://leetcode.com"
15
+ LEETCODE_GRAPHQL_URL = f"{LEETCODE_URL}/graphql"
16
+
17
+ type QueryType = dict[str, str | Mapping[str, Any]]
18
+
19
+
20
+ class LeetCodeHandler(BaseHandler):
21
+ """Base handler for LeetCode-related requests."""
22
+
23
+ @overload
24
+ async def graphql(self, name: str, query: QueryType) -> None: ...
25
+
26
+ @overload
27
+ async def graphql(
28
+ self, name: str, query: QueryType, returnJson=True
29
+ ) -> dict[str, Any]: ...
30
+
31
+ async def graphql(self, name: str, query: QueryType, returnJson=False):
32
+ self.log.debug(f"Fetching LeetCode {name} data...")
33
+ client = AsyncHTTPClient()
34
+ req = HTTPRequest(
35
+ url=LEETCODE_GRAPHQL_URL,
36
+ method="POST",
37
+ headers=HTTPHeaders(self.settings.get("leetcode_headers", {})),
38
+ body=json.dumps(query),
39
+ )
40
+
41
+ try:
42
+ resp = await client.fetch(req)
43
+ except Exception as e:
44
+ self.log.error(f"Error fetching LeetCode {name}: {e}")
45
+ self.set_status(500)
46
+ self.finish(json.dumps({"message": f"Failed to fetch LeetCode {name}"}))
47
+ return
48
+ else:
49
+ if returnJson:
50
+ return json.loads(resp.body)
51
+ self.finish(resp.body)
52
+
53
+ async def graphql_multi(
54
+ self, name: str, queries: dict[str, QueryType]
55
+ ) -> dict[str, HTTPResponse]:
56
+ self.log.debug(f"Fetching LeetCode {name} data...")
57
+ client = AsyncHTTPClient()
58
+ request_futures = dict(
59
+ map(
60
+ lambda kv: (
61
+ kv[0],
62
+ client.fetch(
63
+ HTTPRequest(
64
+ url=LEETCODE_GRAPHQL_URL,
65
+ method="POST",
66
+ headers=HTTPHeaders(
67
+ self.settings.get("leetcode_headers", {})
68
+ ),
69
+ body=json.dumps(kv[1]),
70
+ ),
71
+ ),
72
+ ),
73
+ queries.items(),
74
+ )
75
+ )
76
+
77
+ try:
78
+ responses = await multi(request_futures)
79
+ except Exception as e:
80
+ self.log.error(f"Error fetching LeetCode {name}: {e}")
81
+ self.set_status(500)
82
+ self.finish(json.dumps({"message": f"Failed to fetch LeetCode {name}"}))
83
+ return {}
84
+ else:
85
+ return cast("dict[str, HTTPResponse]", responses)
86
+
87
+ async def request_api(self, url: str, method: str, body: Mapping[str, Any]):
88
+ self.log.debug(f"Requesting LeetCode API: {url} with method {method}")
89
+ client = AsyncHTTPClient()
90
+ req = HTTPRequest(
91
+ url=f"{LEETCODE_URL}{url}",
92
+ method=method,
93
+ headers=HTTPHeaders(self.settings.get("leetcode_headers", {})),
94
+ body=json.dumps(body),
95
+ )
96
+ try:
97
+ resp = await client.fetch(req)
98
+ except Exception as e:
99
+ self.log.error(f"Error requesting LeetCode API: {e}")
100
+ self.set_status(500)
101
+ self.finish(json.dumps({"message": "Failed to request LeetCode API"}))
102
+ return None
103
+ else:
104
+ return json.loads(resp.body) if resp.body else {}
105
+
106
+ async def get_question_detail(self, title_slug: str) -> dict[str, Any]:
107
+ resp = await self.graphql(
108
+ name="question_detail",
109
+ query={
110
+ "query": """query questionData($titleSlug: String!) {
111
+ question(titleSlug: $titleSlug) {
112
+ questionId
113
+ questionFrontendId
114
+ submitUrl
115
+ questionDetailUrl
116
+ title
117
+ titleSlug
118
+ content
119
+ isPaidOnly
120
+ difficulty
121
+ likes
122
+ dislikes
123
+ isLiked
124
+ similarQuestions
125
+ exampleTestcaseList
126
+ topicTags {
127
+ name
128
+ slug
129
+ translatedName
130
+ }
131
+ codeSnippets {
132
+ lang
133
+ langSlug
134
+ code
135
+ }
136
+ stats
137
+ hints
138
+ solution {
139
+ id
140
+ canSeeDetail
141
+ paidOnly
142
+ hasVideoSolution
143
+ paidOnlyVideo
144
+ }
145
+ status
146
+ sampleTestCase
147
+ }
148
+ }""",
149
+ "variables": {"titleSlug": title_slug},
150
+ },
151
+ returnJson=True,
152
+ )
153
+ return resp
154
+
155
+
156
+ class LeetCodeProfileHandler(LeetCodeHandler):
157
+ route = r"leetcode/profile"
158
+
159
+ @tornado.web.authenticated
160
+ async def get(self):
161
+ await self.graphql(
162
+ name="profile",
163
+ query={
164
+ "query": """query globalData {
165
+ userStatus {
166
+ isSignedIn
167
+ username
168
+ realName
169
+ avatar
170
+ }
171
+ }"""
172
+ },
173
+ )
174
+
175
+
176
+ class LeetCodeStatisticsHandler(LeetCodeHandler):
177
+ route = r"leetcode/statistics"
178
+
179
+ @tornado.web.authenticated
180
+ async def get(self):
181
+ username = self.get_query_argument("username", "", strip=True)
182
+ if not username:
183
+ self.set_status(400)
184
+ self.finish(json.dumps({"message": "Username parameter is required"}))
185
+ return
186
+
187
+ responses = await self.graphql_multi(
188
+ name="statistics",
189
+ queries={
190
+ "userSessionProgress": {
191
+ "query": """query userSessionProgress($username: String!) {
192
+ allQuestionsCount {
193
+ difficulty
194
+ count
195
+ }
196
+ matchedUser(username: $username) {
197
+ submitStats {
198
+ acSubmissionNum {
199
+ difficulty
200
+ count
201
+ }
202
+ totalSubmissionNum {
203
+ difficulty
204
+ count
205
+ }
206
+ }
207
+ }
208
+ }""",
209
+ "variables": {"username": username},
210
+ },
211
+ "userProfileUserQuestionProgressV2": {
212
+ "query": """query userProfileUserQuestionProgressV2($userSlug: String!) {
213
+ userProfileUserQuestionProgressV2(userSlug: $userSlug) {
214
+ numAcceptedQuestions {
215
+ count
216
+ difficulty
217
+ }
218
+ numFailedQuestions {
219
+ count
220
+ difficulty
221
+ }
222
+ numUntouchedQuestions {
223
+ count
224
+ difficulty
225
+ }
226
+ userSessionBeatsPercentage {
227
+ difficulty
228
+ percentage
229
+ }
230
+ totalQuestionBeatsPercentage
231
+ }
232
+ }""",
233
+ "variables": {"userSlug": username},
234
+ },
235
+ "userPublicProfile": {
236
+ "query": """query userPublicProfile($username: String!) {
237
+ matchedUser(username: $username) {
238
+ username
239
+ profile {
240
+ ranking
241
+ }
242
+ }
243
+ }""",
244
+ "variables": {"username": username},
245
+ },
246
+ },
247
+ )
248
+
249
+ if not responses:
250
+ return
251
+
252
+ res = dict(
253
+ map(
254
+ lambda kv: (kv[0], json.loads(kv[1].body).get("data", {})),
255
+ responses.items(),
256
+ )
257
+ )
258
+ self.finish(res)
259
+
260
+
261
+ class LeetCodeQuestionHandler(LeetCodeHandler):
262
+ route = r"leetcode/questions"
263
+
264
+ @tornado.web.authenticated
265
+ async def post(self):
266
+ body = self.get_json_body()
267
+ if not body:
268
+ self.set_status(400)
269
+ self.finish(json.dumps({"message": "Request body is required"}))
270
+ return
271
+
272
+ body = cast("dict[str, str|int]", body)
273
+ skip = cast(int, body.get("skip", 0))
274
+ limit = cast(int, body.get("limit", 0))
275
+ keyword = cast(str, body.get("keyword", ""))
276
+ sortField = cast(str, body.get("sortField", "CUSTOM"))
277
+ sortOrder = cast(str, body.get("sortOrder", "ASCENDING"))
278
+
279
+ await self.graphql(
280
+ name="question_list",
281
+ query={
282
+ "query": """query problemsetQuestionListV2($filters: QuestionFilterInput,
283
+ $limit: Int,
284
+ $searchKeyword: String,
285
+ $skip: Int,
286
+ $sortBy: QuestionSortByInput,
287
+ $categorySlug: String) {
288
+ problemsetQuestionListV2(
289
+ filters: $filters
290
+ limit: $limit
291
+ searchKeyword: $searchKeyword
292
+ skip: $skip
293
+ sortBy: $sortBy
294
+ categorySlug: $categorySlug
295
+ ) {
296
+ questions {
297
+ id
298
+ titleSlug
299
+ title
300
+ translatedTitle
301
+ questionFrontendId
302
+ paidOnly
303
+ difficulty
304
+ topicTags {
305
+ name
306
+ slug
307
+ nameTranslated
308
+ }
309
+ status
310
+ isInMyFavorites
311
+ frequency
312
+ acRate
313
+ }
314
+ totalLength
315
+ finishedLength
316
+ hasMore
317
+ }
318
+ }""",
319
+ "variables": {
320
+ "skip": skip,
321
+ "limit": limit,
322
+ "searchKeyword": keyword,
323
+ "categorySlug": "algorithms",
324
+ "filters": {
325
+ "filterCombineType": "ALL",
326
+ "statusFilter": {
327
+ "questionStatuses": ["TO_DO"],
328
+ "operator": "IS",
329
+ },
330
+ "difficultyFilter": {
331
+ "difficulties": ["MEDIUM", "HARD"],
332
+ "operator": "IS",
333
+ },
334
+ "languageFilter": {"languageSlugs": [], "operator": "IS"},
335
+ "topicFilter": {"topicSlugs": [], "operator": "IS"},
336
+ "acceptanceFilter": {},
337
+ "frequencyFilter": {},
338
+ "frontendIdFilter": {},
339
+ "lastSubmittedFilter": {},
340
+ "publishedFilter": {},
341
+ "companyFilter": {"companySlugs": [], "operator": "IS"},
342
+ "positionFilter": {"positionSlugs": [], "operator": "IS"},
343
+ "premiumFilter": {"premiumStatus": [], "operator": "IS"},
344
+ },
345
+ "sortBy": {"sortField": sortField, "sortOrder": sortOrder},
346
+ },
347
+ },
348
+ )
349
+
350
+
351
+ class CreateNotebookHandler(LeetCodeHandler):
352
+ route = r"notebook/create"
353
+
354
+ @tornado.web.authenticated
355
+ async def post(self):
356
+ body = self.get_json_body()
357
+ if not body:
358
+ self.set_status(400)
359
+ self.finish({"message": "Request body is required"})
360
+ return
361
+
362
+ body = cast("dict[str, str]", body)
363
+ title_slug = cast(str, body.get("titleSlug", ""))
364
+ if not title_slug:
365
+ self.set_status(400)
366
+ self.finish({"message": "titleSlug is required"})
367
+ return
368
+
369
+ question = await self.get_question_detail(title_slug)
370
+ question = question.get("data", {}).get("question")
371
+ if not question:
372
+ self.set_status(404)
373
+ self.finish({"message": "Question not found"})
374
+ return
375
+
376
+ notebook_generator = self.settings.get("notebook_generator")
377
+ if not notebook_generator:
378
+ notebook_generator = NotebookGenerator()
379
+ self.settings.update(notebook_generator=notebook_generator)
380
+
381
+ file_path = notebook_generator.generate(question)
382
+ self.finish({"filePath": file_path})
383
+
384
+
385
+ class SubmitNotebookHandler(LeetCodeHandler):
386
+ route = r"notebook/submit"
387
+
388
+ def get_solution(self, notebook):
389
+ solution_cell = first(
390
+ notebook["cells"],
391
+ lambda c: c["cell_type"] == "code" and c["metadata"].get("isSolutionCode"),
392
+ )
393
+ if not solution_cell:
394
+ return
395
+
396
+ code = "".join(solution_cell["source"]).strip()
397
+ return code if not code.endswith("pass") else None
398
+
399
+ async def submit(self, file_path: str):
400
+ if not os.path.exists(file_path):
401
+ self.set_status(404)
402
+ self.finish({"message": "Notebook file not found"})
403
+ return
404
+
405
+ with open(file_path, "r", encoding="utf-8") as f:
406
+ notebook = json.load(f)
407
+
408
+ question_info = notebook["metadata"]["leetcode_question_info"]
409
+ if not question_info:
410
+ self.set_status(400)
411
+ self.finish({"message": "Notebook does not contain LeetCode question info"})
412
+ return
413
+
414
+ question_frontend_id = question_info["questionFrontendId"]
415
+ question_submit_id = question_info["questionId"]
416
+ submit_url = question_info["submitUrl"]
417
+ sample_testcase = question_info["sampleTestCase"]
418
+ if (
419
+ not question_frontend_id
420
+ or not question_submit_id
421
+ or not submit_url
422
+ or not sample_testcase
423
+ ):
424
+ self.set_status(400)
425
+ self.finish({"message": "Invalid question info in notebook"})
426
+ return
427
+
428
+ solution_code = self.get_solution(notebook)
429
+ if not solution_code:
430
+ self.set_status(400)
431
+ self.finish({"message": "No solution code found in notebook"})
432
+ return
433
+
434
+ resp = await self.request_api(
435
+ submit_url,
436
+ "POST",
437
+ {
438
+ "question_id": str(question_submit_id),
439
+ "data_input": sample_testcase,
440
+ "lang": "python3",
441
+ "typed_code": solution_code,
442
+ "test_mode": False,
443
+ "judge_type": "large",
444
+ },
445
+ )
446
+
447
+ self.finish(resp)
448
+
449
+ @tornado.web.authenticated
450
+ async def post(self):
451
+ body = self.get_json_body()
452
+ if not body:
453
+ self.set_status(400)
454
+ self.finish({"message": "Request body is required"})
455
+ return
456
+
457
+ body = cast("dict[str, str]", body)
458
+ file_path = cast(str, body.get("filePath", ""))
459
+ if not file_path:
460
+ self.set_status(400)
461
+ self.finish({"message": "filePath is required"})
462
+ return
463
+
464
+ await self.submit(file_path)