Skip to content

Commit 25301cc

Browse files
committed
fix: CI errors
1 parent fe94f01 commit 25301cc

File tree

7 files changed

+100
-73
lines changed

7 files changed

+100
-73
lines changed

ai_eval/backends/base.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Abstract interfaces for code execution backends."""
2+
13
from abc import ABC, abstractmethod
24
from typing import Dict, Any
35

@@ -6,39 +8,32 @@ class CodeExecutionBackend(ABC):
68
"""
79
Abstract base class for code execution backends.
810
"""
9-
1011
@abstractmethod
1112
def submit_code(self, code: str, language_label: str) -> str:
1213
"""
1314
Submit code for execution.
14-
15+
1516
Args:
1617
code: The source code to execute
1718
language_label: Human-readable language label (e.g., "Python").
1819
Implementations map this to their own representation.
19-
20+
2021
Returns:
2122
str: Submission ID for retrieving results
2223
"""
23-
pass
24-
24+
2525
@abstractmethod
2626
def get_result(self, submission_id: str) -> Dict[str, Any]:
2727
"""
2828
Get execution result for a submission.
29-
29+
3030
Args:
3131
submission_id: The submission ID from submit_code()
32-
32+
3333
Returns:
3434
dict: Execution result containing:
3535
- status: dict with 'id' and 'description'
3636
- stdout: str or None
3737
- stderr: str or None
3838
- compile_output: str or None
39-
- time: str or None
40-
- memory: str or None
4139
"""
42-
pass
43-
44-

ai_eval/backends/custom.py

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import requests
1+
"""Custom service code execution backend."""
2+
23
from typing import Dict, Any, Optional
4+
import requests
5+
from ai_eval.utils import SUPPORTED_LANGUAGE_MAP, LanguageLabels, DEFAULT_HTTP_TIMEOUT
36
from .base import CodeExecutionBackend
47

58

69
class CustomServiceBackend(CodeExecutionBackend):
710
"""
811
Generic custom code execution backend.
912
"""
10-
11-
def __init__(
13+
def __init__( # pylint: disable=too-many-positional-arguments
1214
self,
1315
submit_endpoint: str,
1416
results_endpoint: str,
1517
languages_endpoint: str,
1618
api_key: str = "",
17-
timeout: int = 30,
19+
timeout: int = DEFAULT_HTTP_TIMEOUT,
1820
auth_header_name: str = "Authorization",
1921
auth_scheme: Optional[str] = "Bearer",
2022
):
@@ -25,8 +27,8 @@ def __init__(
2527
self.timeout = timeout
2628
self.auth_header_name = auth_header_name
2729
self.auth_scheme = auth_scheme
28-
self._validate_languages()
29-
30+
self._languages_validated = False
31+
3032
def _get_headers(self) -> Dict[str, str]:
3133
"""
3234
Get headers for API requests.
@@ -38,7 +40,7 @@ def _get_headers(self) -> Dict[str, str]:
3840
else:
3941
headers[self.auth_header_name] = self.api_key
4042
return headers
41-
43+
4244
def _validate_languages(self):
4345
"""
4446
Validate that static languages are supported by the custom service.
@@ -50,43 +52,56 @@ def _validate_languages(self):
5052
timeout=self.timeout
5153
)
5254
response.raise_for_status()
53-
55+
5456
service_languages = response.json()
5557
# Expected format: [{"id": "92", "name": "Python"}, ...] or [{"id": "python", "name": "Python"}, ...]
5658
service_language_names = {lang['name'].lower() for lang in service_languages}
57-
58-
from ai_eval.utils import SUPPORTED_LANGUAGE_MAP, LanguageLabels
59-
static_language_names = {name.lower() for name in SUPPORTED_LANGUAGE_MAP.keys()
60-
if name != LanguageLabels.HTML_CSS}
61-
59+
60+
static_language_names = {
61+
name.lower() for name in SUPPORTED_LANGUAGE_MAP
62+
if name != LanguageLabels.HTML_CSS
63+
}
64+
6265
unsupported = static_language_names - service_language_names
6366
if unsupported:
6467
raise ValueError(
6568
f"Custom service does not support languages: {', '.join(unsupported)}. "
6669
)
67-
70+
6871
except (requests.RequestException, KeyError, ValueError) as e:
69-
raise ValueError(f"Failed to validate supported languages: {e}")
70-
72+
raise ValueError(f"Failed to validate supported languages: {e}") from e
73+
74+
def _ensure_languages_validated(self):
75+
"""Validate supported languages lazily once, if endpoint is configured."""
76+
if self._languages_validated:
77+
return
78+
if not self.languages_endpoint:
79+
# No endpoint provided; skip validation.
80+
self._languages_validated = True
81+
return
82+
self._validate_languages()
83+
self._languages_validated = True
84+
7185
def submit_code(self, code: str, language_label: str) -> str:
7286
"""
7387
Submit code to custom service for execution.
7488
"""
89+
self._ensure_languages_validated()
7590
# By default, send the language label; services will need to map as needed
7691
payload = {
7792
'code': code,
7893
'language': language_label
7994
}
80-
95+
8196
try:
8297
response = requests.post(
83-
self.submit_endpoint,
84-
json=payload,
98+
self.submit_endpoint,
99+
json=payload,
85100
headers=self._get_headers(),
86101
timeout=self.timeout
87102
)
88103
response.raise_for_status()
89-
104+
90105
# Handle different response formats
91106
result = response.json()
92107
if 'submission_id' in result:
@@ -95,28 +110,29 @@ def submit_code(self, code: str, language_label: str) -> str:
95110
return str(result['id'])
96111
else:
97112
raise ValueError("Custom service response missing submission ID")
98-
113+
99114
except requests.RequestException as e:
100-
raise ValueError(f"Failed to submit code for execution: {e}")
115+
raise ValueError(f"Failed to submit code for execution: {e}") from e
101116
except (KeyError, ValueError) as e:
102-
raise ValueError(f"Invalid response from custom service: {e}")
103-
117+
raise ValueError(f"Invalid response from custom service: {e}") from e
118+
104119
def get_result(self, submission_id: str) -> Dict[str, Any]:
105120
"""
106121
Get execution result from custom service.
107122
"""
123+
self._ensure_languages_validated()
108124
url = self.results_endpoint.format(submission_id=submission_id)
109-
125+
110126
try:
111127
response = requests.get(
112128
url,
113129
headers=self._get_headers(),
114130
timeout=self.timeout
115131
)
116132
response.raise_for_status()
117-
133+
118134
result = response.json()
119-
135+
120136
# Map custom service response to standard format
121137
return {
122138
'status': {
@@ -127,9 +143,8 @@ def get_result(self, submission_id: str) -> Dict[str, Any]:
127143
'stderr': result.get('stderr'),
128144
'compile_output': result.get('compile_error')
129145
}
130-
146+
131147
except requests.RequestException as e:
132-
raise ValueError(f"Failed to get submission result: {e}")
148+
raise ValueError(f"Failed to get submission result: {e}") from e
133149
except (KeyError, ValueError) as e:
134-
raise ValueError(f"Invalid response from custom service: {e}")
135-
150+
raise ValueError(f"Invalid response from custom service: {e}") from e

ai_eval/backends/factory.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from django.conf import settings
1+
"""Backend selection factory."""
2+
3+
import django.conf as django_conf
24
from .judge0 import Judge0Backend
35
from .custom import CustomServiceBackend
46

@@ -7,20 +9,21 @@ class BackendFactory:
79
"""
810
Factory for creating code execution backends.
911
"""
10-
1112
@classmethod
1213
def get_backend(cls, api_key: str = ""):
1314
"""
1415
Get the appropriate backend based on Django settings.
15-
16+
1617
Args:
1718
api_key: Judge0 API key (only used for judge0 backend)
18-
19+
1920
Returns:
2021
CodeExecutionBackend: Configured backend instance
2122
"""
22-
backend_config = getattr(settings, 'AI_EVAL_CODE_EXECUTION_BACKEND', {})
23-
23+
backend_config = getattr(
24+
django_conf.settings, 'AI_EVAL_CODE_EXECUTION_BACKEND', {}
25+
)
26+
2427
if backend_config.get('backend') == 'custom':
2528
config = backend_config.get('custom_config', {})
2629
return CustomServiceBackend(
@@ -32,7 +35,7 @@ def get_backend(cls, api_key: str = ""):
3235
auth_header_name=config.get('auth_header_name', 'Authorization'),
3336
auth_scheme=config.get('auth_scheme', 'Bearer'),
3437
)
35-
38+
3639
# Default to judge0 backend
3740
judge0_config = backend_config.get('judge0_config', {})
3841
return Judge0Backend(

ai_eval/backends/judge0.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import requests
1+
"""Judge0 code execution backend."""
2+
23
from typing import Dict, Any
4+
import requests
5+
from ai_eval.utils import SUPPORTED_LANGUAGE_MAP, DEFAULT_HTTP_TIMEOUT
36
from .base import CodeExecutionBackend
4-
from ai_eval.utils import SUPPORTED_LANGUAGE_MAP, LanguageLabels
57

68

79
class Judge0Backend(CodeExecutionBackend):
810
"""
911
Judge0 code execution backend.
1012
"""
11-
1213
def __init__(self, api_key: str = "", base_url: str = None):
1314
self.api_key = api_key
1415
self.base_url = base_url or "https://judge0-ce.p.rapidapi.com"
15-
16+
1617
def submit_code(self, code: str, language_label: str) -> str:
1718
"""
1819
Submit code to Judge0 for execution.
1920
"""
2021
if not self.api_key:
2122
raise ValueError("Judge0 API key is required")
22-
23+
2324
# Map the human-readable label to Judge0 numeric id
2425
try:
2526
judge0_id = SUPPORTED_LANGUAGE_MAP[language_label].judge0_id
@@ -35,40 +36,48 @@ def submit_code(self, code: str, language_label: str) -> str:
3536
'source_code': code,
3637
'language_id': int(judge0_id)
3738
}
38-
39+
3940
try:
40-
response = requests.post(url, json=payload, headers=headers)
41+
response = requests.post(
42+
url,
43+
json=payload,
44+
headers=headers,
45+
timeout=DEFAULT_HTTP_TIMEOUT,
46+
)
4147
response.raise_for_status()
42-
48+
4349
result = response.json()
4450
if 'token' in result:
4551
return result['token']
4652
else:
4753
raise ValueError("Judge0 response missing submission token")
48-
54+
4955
except requests.RequestException as e:
50-
raise ValueError(f"Failed to submit code to Judge0: {e}")
56+
raise ValueError(f"Failed to submit code to Judge0: {e}") from e
5157
except (KeyError, ValueError) as e:
52-
raise ValueError(f"Invalid response from Judge0: {e}")
53-
58+
raise ValueError(f"Invalid response from Judge0: {e}") from e
59+
5460
def get_result(self, submission_id: str) -> Dict[str, Any]:
5561
"""
5662
Get execution result from Judge0.
5763
"""
5864
if not self.api_key:
5965
raise ValueError("Judge0 API key is required")
60-
66+
6167
url = f"{self.base_url}/submissions/{submission_id}"
6268
headers = {'x-rapidapi-key': self.api_key}
63-
69+
6470
try:
65-
response = requests.get(url, headers=headers)
71+
response = requests.get(
72+
url,
73+
headers=headers,
74+
timeout=DEFAULT_HTTP_TIMEOUT,
75+
)
6676
response.raise_for_status()
67-
77+
6878
return response.json()
69-
79+
7080
except requests.RequestException as e:
71-
raise ValueError(f"Failed to get submission result from Judge0: {e}")
81+
raise ValueError(f"Failed to get submission result from Judge0: {e}") from e
7282
except (KeyError, ValueError) as e:
73-
raise ValueError(f"Invalid response from Judge0: {e}")
74-
83+
raise ValueError(f"Invalid response from Judge0: {e}") from e

ai_eval/coding_ai_eval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import pkg_resources
55

6+
from django.conf import settings
67
from django.utils.translation import gettext_noop as _
78
from web_fragments.fragment import Fragment
89
from xblock.core import XBlock
@@ -16,7 +17,6 @@
1617
LanguageLabels,
1718
)
1819
from .backends.factory import BackendFactory
19-
from django.conf import settings
2020

2121
logger = logging.getLogger(__name__)
2222

ai_eval/tests/test_ai_eval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def test_custom_backend_language_validation_fails(mock_get):
321321
"""Test CustomServiceBackend raises error for unsupported languages."""
322322
mock_response = Mock()
323323
mock_response.json.return_value = [
324-
{"name": "Python"} # Only Python supported
324+
{"name": "Python"} # Only Python supported
325325
]
326326
mock_response.raise_for_status = Mock()
327327
mock_get.return_value = mock_response

0 commit comments

Comments
 (0)