Skip to content

fix: add timeout and error handling to Codeforces and LeetCode API calls#219

Open
francisofficial01 wants to merge 2 commits into
OpenLake:mainfrom
francisofficial01:fix/api-timeout-and-null-check
Open

fix: add timeout and error handling to Codeforces and LeetCode API calls#219
francisofficial01 wants to merge 2 commits into
OpenLake:mainfrom
francisofficial01:fix/api-timeout-and-null-check

Conversation

@francisofficial01

@francisofficial01 francisofficial01 commented Apr 15, 2026

Copy link
Copy Markdown

Fixes #208

Changes

  • Added timeout=10 to all requests.get() calls in get_codeforces_submission_stats and get_leetcode_data
  • Wrapped calls in try/except for Timeout and RequestException
  • Added null check in the LeetCode caller to prevent KeyError on user_data["ranking"]

Why

Without a timeout, the server hangs indefinitely if Codeforces/LeetCode is slow or down.
The LeetCode proxy (onrender.com free tier) frequently returns non-200 responses,
causing an unhandled KeyError crash in the caller.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of leaderboard data fetching with timeout protection on all external API calls.
    • Enhanced error handling with appropriate fallback values when external services are unavailable or experiencing delays.
    • Strengthened data parsing and validation logic for external user profiles.
    • Better differentiation between permanent and temporary API errors.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@francisofficial01 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 11 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 11 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d36ab82-cd7b-4bcf-ad36-9d49ef611c8f

📥 Commits

Reviewing files that changed from the base of the PR and between dedef75 and 2902dfc.

📒 Files selected for processing (1)
  • api/leaderboard/views.py

Walkthrough

The PR adds timeout parameters (10 seconds) and exception handling to all external API calls in the leaderboard views, replacing simple status-code checks with try/except blocks that return sensible fallback values on network failures or malformed responses.

Changes

Cohort / File(s) Summary
Timeout and Exception Handling
api/leaderboard/views.pyget_github_contributions(), get_github_stars(), get_github_user_data(), get_github_profile_data(), get_codeforces_submission_stats(), get_codeforces_data(), get_codechef_data(), get_leetcode_data()
All external API requests now include timeout=10 and are wrapped in try/except blocks with requests.exceptions.RequestException handling. Methods return fallback values (0 for counts, None for user data) on network failures instead of crashing or returning None implicitly.
CodeChef Data Refactoring
api/leaderboard/views.pyget_codechef_data()
Removed conditional status_code == 200 block structure and replaced with explicit 404 vs non-200 handling. Added parsing validation for rating conversion and changed avatar extraction to use first matched profile image. Return object now includes computed fields with calendar_data defaulting to "[]".
LeetCode Status Code Handling
api/leaderboard/views.pyget_leetcode_data()
Added explicit error handling distinguishing 404"NOT_FOUND" from other non-200 responses → "TRANSIENT_ERROR". Added exception handling for Timeout and general Exception types to return "TRANSIENT_ERROR".
ViewSet Scope Change
api/leaderboard/views.pyCodeforcesUserViewSet, get_codeforces_data()
Moved CodeforcesUserViewSet(viewsets.ModelViewSet) class definition inside get_codeforces_data() method scope with queryset and serializer_class assignments moved into inner class scope (previously at module/class scope).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A timeout here, exception there,
No more crashes without a care!
Fallback values light the way,
When APIs sleep the night away—
Robust and steady, come what may! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR partially addresses issue #208 but has critical discrepancies: the code adds timeouts only to some functions, doesn't use the suggested timeout=30 value, and introduces unexpected scope changes to CodeforcesUserViewSet. Ensure timeout=30 is used consistently across all affected API calls [#208], verify null checks are properly implemented for LeetCode data [#208], and revert unrelated scope changes to CodeforcesUserViewSet.
Out of Scope Changes check ⚠️ Warning The PR introduces unexpected refactoring: moving CodeforcesUserViewSet inside a method and restructuring get_codechef_data with parsing validation changes not mentioned in the PR objectives or issue #208. Remove unrelated changes to CodeforcesUserViewSet scope and codechef_data refactoring; focus only on timeout and error handling for Codeforces and LeetCode as specified in #208.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes in the PR: adding timeout and error handling to Codeforces and LeetCode API calls.
Description check ✅ Passed The description covers the key changes and rationale but lacks related issue link formatting and some checklist items are missing from completion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/leaderboard/views.py (2)

487-513: ⚠️ Potential issue | 🔴 Critical

Don't change get_leetcode_data to return truthy sentinels without updating callers.

This method now returns "NOT_FOUND" / "TRANSIENT_ERROR", but Line 524 still does a truthiness check and Lines 525-530 index user_data as a dict. For any outdated user on a 404 or transient upstream error, get() will now crash with a TypeError.

One safe option: keep the old falsy contract
         try:
             response = requests.get(url, timeout=10)

             if response.status_code == 404:
-                return "NOT_FOUND"
+                return None

             if response.status_code != 200:
                 logger.error(f"LeetCode server error for {username}: Status {response.status_code}")
-                return "TRANSIENT_ERROR"
+                return None
@@
         except requests.exceptions.Timeout:
             logger.error(f"LeetCode timeout for {username}")
-            return "TRANSIENT_ERROR"
+            return None
         except Exception as e:
             logger.error(f"Error fetching LeetCode data for {username}: {e}")
-            return "TRANSIENT_ERROR"
+            return None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 487 - 513, get_leetcode_data now
returns string sentinels ("NOT_FOUND"/"TRANSIENT_ERROR") which breaks existing
callers that expect a falsy value (e.g., None/False) and then index user_data as
a dict; revert the sentinel returns to the previous falsy contract (return None)
for 404 and transient errors, keep logging the specific condition (use
logger.error with the status or exception) and ensure the success path still
returns the dict with keys like "ranking", "easy_solved", etc.; update only
get_leetcode_data (the function) so callers that do a truthiness check and then
index user_data continue to work without changes.

348-409: ⚠️ Potential issue | 🔴 Critical

Import BeautifulSoup before using it at line 358.

The code uses BeautifulSoup(response.text, "html.parser") but the file does not import it. This will cause a NameError on any successful CodeChef fetch. While beautifulsoup4 is declared as a project dependency, the import statement is missing from this file.

Required import
+from bs4 import BeautifulSoup
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 348 - 409, The file uses BeautifulSoup
in the CodeChef fetch logic (BeautifulSoup(response.text, "html.parser") inside
the function handling CodeChef responses) but never imports it; add the missing
import "from bs4 import BeautifulSoup" near the top import block (alongside
other third-party imports) so BeautifulSoup is defined when the code reaches
that line, then run linters/tests to confirm no unused-import warnings or
import-order issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/leaderboard/views.py`:
- Around line 292-314: The get_codeforces_data function has misindented blocks:
align the except requests.RequestException as e: block with its corresponding
try in get_codeforces_data so the except, response.json parsing, status checks,
and return statements are inside the function body correctly (ensure
response.raise_for_status() is inside try and any early returns remain correct).
After fixing indentation, move the CodeforcesUserViewSet class out of the
get_codeforces_data function (dedent it to module level) so it is defined at
top-level and available to DRF routing; reference the get_codeforces_data
function name and CodeforcesUserViewSet class name when locating the blocks to
fix. Ensure no leftover nested indentation or unreachable code remains.

---

Outside diff comments:
In `@api/leaderboard/views.py`:
- Around line 487-513: get_leetcode_data now returns string sentinels
("NOT_FOUND"/"TRANSIENT_ERROR") which breaks existing callers that expect a
falsy value (e.g., None/False) and then index user_data as a dict; revert the
sentinel returns to the previous falsy contract (return None) for 404 and
transient errors, keep logging the specific condition (use logger.error with the
status or exception) and ensure the success path still returns the dict with
keys like "ranking", "easy_solved", etc.; update only get_leetcode_data (the
function) so callers that do a truthiness check and then index user_data
continue to work without changes.
- Around line 348-409: The file uses BeautifulSoup in the CodeChef fetch logic
(BeautifulSoup(response.text, "html.parser") inside the function handling
CodeChef responses) but never imports it; add the missing import "from bs4
import BeautifulSoup" near the top import block (alongside other third-party
imports) so BeautifulSoup is defined when the code reaches that line, then run
linters/tests to confirm no unused-import warnings or import-order issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3cedef02-7399-4212-a8ce-bc89cc976d38

📥 Commits

Reviewing files that changed from the base of the PR and between 7e75c73 and dedef75.

📒 Files selected for processing (1)
  • api/leaderboard/views.py

Comment thread api/leaderboard/views.py
Comment on lines +292 to +314
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
print(f"Request failed: {e}")
return None

if response.status_code == 200:
data = response.json()
if data.get("status") == "OK" and data.get("result"):
user_data = data["result"][0]
return {
"rating": user_data.get("rating", 0),
"max_rating": user_data.get("maxRating", 0),
"last_activity": user_data.get(
"lastOnlineTimeSeconds", MAX_DATE_TIMESTAMP
),
"last_activity": user_data.get("lastOnlineTimeSeconds", MAX_DATE_TIMESTAMP),
"avatar": user_data.get("titlePhoto", ""),
}

queryset = codeforcesUser.objects.all()
serializer_class = CF_Serializer
return None # Explicit fallback


class CodeforcesUserViewSet(viewsets.ModelViewSet): # or wherever these belong
queryset = codeforcesUser.objects.all()
serializer_class = CF_Serializer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '290,314p' api/leaderboard/views.py | nl -ba

python - <<'PY'
from pathlib import Path
import ast

path = Path("api/leaderboard/views.py")
source = path.read_text()

try:
    tree = ast.parse(source)
    print("AST parse: OK")
except SyntaxError as e:
    print(f"SyntaxError: line={e.lineno}, offset={e.offset}, msg={e.msg}")
PY

Repository: OpenLake/Leaderboard-Pro

Length of output: 110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant section with line numbers
echo "=== Lines 290-320 of api/leaderboard/views.py ==="
head -320 api/leaderboard/views.py | tail -31 | cat -n

# Check for syntax errors
echo ""
echo "=== AST Parse Check ==="
python3 <<'PY'
from pathlib import Path
import ast

path = Path("api/leaderboard/views.py")
source = path.read_text()

try:
    tree = ast.parse(source)
    print("AST parse: OK - No syntax errors")
except SyntaxError as e:
    print(f"SyntaxError found:")
    print(f"  Line: {e.lineno}")
    print(f"  Offset: {e.offset}")
    print(f"  Message: {e.msg}")
    print(f"  Text: {e.text}")
PY

Repository: OpenLake/Leaderboard-Pro

Length of output: 1762


Fix indentation and class scope in get_codeforces_data.

The code has critical syntax errors: the except block (line 295 onwards) is misindented and does not match the try block, causing an AST parse failure. Additionally, CodeforcesUserViewSet (line 312) is incorrectly nested inside the get_codeforces_data method rather than defined at the module level, making it unreachable to DRF routing even after fixing the indentation.

Proposed fix
     def get_codeforces_data(self, username):
         url = f"https://codeforces.com/api/user.info?handles={username}"
-        try:
-                response = requests.get(url, timeout=10)
-                response.raise_for_status()
-            except requests.RequestException as e:
-                print(f"Request failed: {e}")
-                return None
-
-            data = response.json()
-            if data.get("status") == "OK" and data.get("result"):
-                user_data = data["result"][0]
-                return {
-                    "rating": user_data.get("rating", 0),
-                    "max_rating": user_data.get("maxRating", 0),
-                    "last_activity": user_data.get("lastOnlineTimeSeconds", MAX_DATE_TIMESTAMP),
-                    "avatar": user_data.get("titlePhoto", ""),
-                }
-
-            return None  # Explicit fallback
-
-        class CodeforcesUserViewSet(viewsets.ModelViewSet):  # or wherever these belong
-            queryset = codeforcesUser.objects.all()
-            serializer_class = CF_Serializer
+        try:
+            response = requests.get(url, timeout=10)
+            response.raise_for_status()
+        except requests.RequestException as e:
+            logger.warning("Codeforces request failed for %s: %s", username, e)
+            return None
+
+        data = response.json()
+        if data.get("status") == "OK" and data.get("result"):
+            user_data = data["result"][0]
+            return {
+                "rating": user_data.get("rating", 0),
+                "max_rating": user_data.get("maxRating", 0),
+                "last_activity": user_data.get("lastOnlineTimeSeconds", MAX_DATE_TIMESTAMP),
+                "avatar": user_data.get("titlePhoto", ""),
+            }
+
+        return None

Move CodeforcesUserViewSet to module level after fixing indentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/leaderboard/views.py` around lines 292 - 314, The get_codeforces_data
function has misindented blocks: align the except requests.RequestException as
e: block with its corresponding try in get_codeforces_data so the except,
response.json parsing, status checks, and return statements are inside the
function body correctly (ensure response.raise_for_status() is inside try and
any early returns remain correct). After fixing indentation, move the
CodeforcesUserViewSet class out of the get_codeforces_data function (dedent it
to module level) so it is defined at top-level and available to DRF routing;
reference the get_codeforces_data function name and CodeforcesUserViewSet class
name when locating the blocks to fix. Ensure no leftover nested indentation or
unreachable code remains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_codeforces_submission_stats has no timeout and crashes on malformed API responses

1 participant