Skip to content

Commit 0e4fd8b

Browse files
committed
Add client resolution based on the submissions and the languages. Add Language enumeration.
1 parent 5151027 commit 0e4fd8b

File tree

9 files changed

+263
-28
lines changed

9 files changed

+263
-28
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ufmt = "==2.7.3"
1111
pre-commit = "==3.8.0"
1212
pytest = "==8.3.3"
1313
python-dotenv = "==1.0.1"
14+
pytest-cov = "6.0.0"
1415

1516
[requires]
1617
python_version = "3.9"

examples/0003_hello_world.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import judge0
2+
3+
submission = judge0.Submission(
4+
source_code="print('Hello Judge0')",
5+
language_id=judge0.PYTHON,
6+
)
7+
8+
judge0.run(submissions=submission)
9+
10+
print(submission.stdout)

src/judge0/__init__.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
SuluJudge0CE,
1616
SuluJudge0ExtraCE,
1717
)
18-
from .common import Flavor, Status
18+
from .common import Flavor, Language, Status
1919
from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry
2020
from .submission import Submission
2121

@@ -24,22 +24,23 @@
2424
"ATDJudge0CE",
2525
"ATDJudge0ExtraCE",
2626
"Client",
27+
"Language",
28+
"MaxRetries",
29+
"MaxWaitTime",
2730
"Rapid",
2831
"RapidJudge0CE",
2932
"RapidJudge0ExtraCE",
33+
"RegularPeriodRetry",
34+
"Status",
35+
"Submission",
3036
"Sulu",
3137
"SuluJudge0CE",
3238
"SuluJudge0ExtraCE",
33-
"Submission",
34-
"RegularPeriodRetry",
35-
"MaxRetries",
36-
"MaxWaitTime",
3739
"async_execute",
38-
"sync_execute",
3940
"execute",
40-
"wait",
4141
"run",
42-
"Status",
42+
"sync_execute",
43+
"wait",
4344
]
4445

4546

@@ -88,3 +89,10 @@ def _create_default_client(
8889

8990
CE = Flavor.CE
9091
EXTRA_CE = Flavor.EXTRA_CE
92+
93+
PYTHON = Language.PYTHON
94+
CPP = Language.CPP
95+
JAVA = Language.JAVA
96+
CPP_GCC = Language.CPP_GCC
97+
CPP_CLANG = Language.CPP_CLANG
98+
PYTHON_FOR_ML = Language.PYTHON_FOR_ML

src/judge0/api.py

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,67 @@
22

33
from .clients import Client
44
from .common import Flavor
5+
from .data import LANGUAGE_TO_LANGUAGE_ID
56
from .retry import RegularPeriodRetry, RetryMechanism
67
from .submission import Submission
78

89

9-
def _resolve_client(client: Union[Client, Flavor]) -> Client:
10-
if not isinstance(client, Flavor):
11-
return client
12-
13-
if client == Flavor.CE:
14-
from . import JUDGE0_IMPLICIT_CE_CLIENT
15-
16-
client = JUDGE0_IMPLICIT_CE_CLIENT
17-
else:
18-
from . import JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
10+
def resolve_client(
11+
client: Optional[Union[Client, Flavor]] = None,
12+
submissions: Optional[Union[Submission, list[Submission]]] = None,
13+
) -> Union[Client, None]:
14+
from . import JUDGE0_IMPLICIT_CE_CLIENT, JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
1915

20-
client = JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
16+
# User explicitly passed a client.
17+
if isinstance(client, Client):
18+
return client
2119

22-
return client
20+
# User explicitly choose the flavor of the client.
21+
if isinstance(client, Flavor):
22+
if client == Flavor.CE:
23+
return JUDGE0_IMPLICIT_CE_CLIENT
24+
else:
25+
return JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
26+
27+
# client is None and we have to determine a flavor of the client from the
28+
# submissions and the languages.
29+
if isinstance(submissions, Submission):
30+
submissions = [submissions]
31+
32+
if submissions is not None and len(submissions) == 0:
33+
raise ValueError("Client cannot be determined from empty submissions argument.")
34+
35+
if submissions is None:
36+
raise ValueError(
37+
"Client cannot be determined from unprovided submissions argument."
38+
)
39+
40+
# Check which client supports all languages from the provided submissions.
41+
languages = [submission.language_id for submission in submissions]
42+
43+
if JUDGE0_IMPLICIT_CE_CLIENT is not None:
44+
if all(
45+
[
46+
JUDGE0_IMPLICIT_CE_CLIENT.is_language_supported(lang)
47+
for lang in languages
48+
]
49+
):
50+
return JUDGE0_IMPLICIT_CE_CLIENT
51+
52+
if JUDGE0_IMPLICIT_EXTRA_CE_CLIENT is not None:
53+
if all(
54+
[
55+
JUDGE0_IMPLICIT_EXTRA_CE_CLIENT.is_language_supported(lang)
56+
for lang in languages
57+
]
58+
):
59+
return JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
60+
61+
raise RuntimeError(
62+
"Failed to resolve the client from submissions argument."
63+
"None of the implicit clients supports all languages from the submissions."
64+
"Please explicitly provide the client argument."
65+
)
2366

2467

2568
def wait(
@@ -59,10 +102,10 @@ def wait(
59102

60103
def async_execute(
61104
*,
62-
client: Union[Client, Flavor] = Flavor.CE,
63-
submissions: Union[Submission, list[Submission], None] = None,
105+
client: Optional[Union[Client, Flavor]] = None,
106+
submissions: Optional[Union[Submission, list[Submission]]] = None,
64107
) -> Union[Submission, list[Submission]]:
65-
client = _resolve_client(client)
108+
client = resolve_client(client)
66109

67110
if isinstance(submissions, (list, tuple)):
68111
return client.create_submissions(submissions)
@@ -72,10 +115,10 @@ def async_execute(
72115

73116
def sync_execute(
74117
*,
75-
client: Union[Client, Flavor] = Flavor.CE,
118+
client: Optional[Union[Client, Flavor]] = None,
76119
submissions: Union[Submission, list[Submission], None] = None,
77120
) -> Union[Submission, list[Submission]]:
78-
client = _resolve_client(client)
121+
client = resolve_client(client, submissions=submissions)
79122
submissions = async_execute(client=client, submissions=submissions)
80123
return wait(client, submissions)
81124

src/judge0/clients.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import requests
44

5+
from .common import Language
6+
from .data import LANGUAGE_TO_LANGUAGE_ID
57
from .submission import Submission
68

79

@@ -55,7 +57,21 @@ def get_statuses(self) -> list[dict]:
5557
r.raise_for_status()
5658
return r.json()
5759

58-
def is_language_supported(self, language_id: int) -> bool:
60+
@property
61+
def version(self):
62+
if not hasattr(self, "_version"):
63+
_version = self.get_about()["version"]
64+
setattr(self, "_version", _version)
65+
return self._version
66+
67+
def resolve_language_id(self, language_id: Union[Language, int]) -> int:
68+
if isinstance(language_id, Language):
69+
languages = LANGUAGE_TO_LANGUAGE_ID[self.version]
70+
language_id = languages.get(language_id, -1)
71+
return language_id
72+
73+
def is_language_supported(self, language_id: Union[Language, int]) -> bool:
74+
language_id = self.resolve_language_id(language_id)
5975
return language_id in self.languages
6076

6177
def create_submission(self, submission: Submission) -> Submission:
@@ -72,6 +88,9 @@ def create_submission(self, submission: Submission) -> Submission:
7288
}
7389

7490
body = submission.to_dict()
91+
# We have to resolve language_id because language_id can be Language
92+
# enumeration.
93+
body["language_id"] = self.resolve_language_id(submission.language_id)
7594

7695
resp = requests.post(
7796
f"{self.endpoint}/submissions",
@@ -126,6 +145,12 @@ def create_submissions(self, submissions: list[Submission]) -> list[Submission]:
126145
)
127146

128147
submissions_body = [submission.to_dict() for submission in submissions]
148+
# We have to resolve language_id because language_id can be Language
149+
# enumeration.
150+
for submission_body in submissions_body:
151+
submission_body["language_id"] = self.resolve_language_id(
152+
submission_body["language_id"]
153+
)
129154

130155
resp = requests.post(
131156
f"{self.endpoint}/submissions/batch",

src/judge0/common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
from enum import IntEnum
22

33

4+
class Language(IntEnum):
5+
PYTHON = 0
6+
CPP = 1
7+
JAVA = 2
8+
CPP_GCC = 3
9+
CPP_CLANG = 4
10+
PYTHON_FOR_ML = 5
11+
12+
413
class Flavor(IntEnum):
514
CE = 0
615
EXTRA_CE = 1

src/judge0/data.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from .common import Language
2+
3+
LANGUAGE_TO_LANGUAGE_ID = {
4+
"1.13.1": {
5+
Language.PYTHON: 71,
6+
Language.CPP: 54,
7+
Language.JAVA: 62,
8+
Language.CPP_GCC: 54,
9+
Language.CPP_CLANG: 76,
10+
},
11+
"1.13.1-extra": {
12+
Language.PYTHON: 10,
13+
Language.CPP: 2,
14+
Language.JAVA: 4,
15+
Language.CPP_CLANG: 2,
16+
Language.PYTHON_FOR_ML: 10,
17+
},
18+
"1.14.0": {
19+
Language.PYTHON: 100,
20+
Language.CPP: 105,
21+
Language.JAVA: 91,
22+
Language.CPP_GCC: 105,
23+
Language.CPP_CLANG: 76,
24+
},
25+
"1.14.0-extra": {
26+
Language.PYTHON: 25,
27+
Language.CPP: 2,
28+
Language.JAVA: 4,
29+
Language.CPP_CLANG: 2,
30+
Language.PYTHON_FOR_ML: 25,
31+
},
32+
}

tests/test_api.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import pytest
2+
3+
from judge0 import (
4+
Flavor,
5+
JUDGE0_IMPLICIT_CE_CLIENT,
6+
JUDGE0_IMPLICIT_EXTRA_CE_CLIENT,
7+
Language,
8+
Submission,
9+
submission,
10+
)
11+
from judge0.api import resolve_client
12+
13+
DEFAULT_CLIENTS = (
14+
"atd_ce_client",
15+
"atd_extra_ce_client",
16+
"rapid_ce_client",
17+
"rapid_extra_ce_client",
18+
"sulu_ce_client",
19+
"sulu_extra_ce_client",
20+
)
21+
22+
23+
@pytest.mark.parametrize("client", DEFAULT_CLIENTS)
24+
def test_resolve_client_with_explicit_client(client, request):
25+
client = request.getfixturevalue(client)
26+
assert resolve_client(client) is client
27+
28+
29+
@pytest.mark.parametrize(
30+
"flavor,expected_client",
31+
[
32+
[
33+
Flavor.CE,
34+
JUDGE0_IMPLICIT_CE_CLIENT,
35+
],
36+
[
37+
Flavor.EXTRA_CE,
38+
JUDGE0_IMPLICIT_EXTRA_CE_CLIENT,
39+
],
40+
],
41+
)
42+
def test_resolve_client_with_flavor(
43+
flavor,
44+
expected_client,
45+
):
46+
assert resolve_client(client=flavor) is expected_client
47+
48+
49+
@pytest.mark.parametrize(
50+
"submissions",
51+
[
52+
[],
53+
None,
54+
],
55+
)
56+
def test_resolve_client_empty_submissions_argument(submissions):
57+
with pytest.raises(ValueError):
58+
resolve_client(submissions=submissions)
59+
60+
61+
def test_resolve_client_no_common_client_for_submissions():
62+
cpp_submission = Submission(
63+
source_code="", # source code is not important because in this test
64+
language_id=Language.CPP_GCC,
65+
)
66+
67+
py_submission = Submission(
68+
source_code="", # source code is not important because in this test
69+
language_id=Language.PYTHON_FOR_ML,
70+
)
71+
72+
submissions = [cpp_submission, py_submission]
73+
74+
with pytest.raises(RuntimeError):
75+
resolve_client(submissions=submissions)
76+
77+
78+
def test_resolve_client_common_ce_client():
79+
cpp_submission = Submission(
80+
source_code="", # source code is not important because in this test
81+
language_id=Language.CPP_GCC,
82+
)
83+
84+
py_submission = Submission(
85+
source_code="", # source code is not important because in this test
86+
language_id=Language.PYTHON,
87+
)
88+
89+
submissions = [cpp_submission, py_submission]
90+
91+
assert resolve_client(submissions=submissions) is JUDGE0_IMPLICIT_CE_CLIENT
92+
93+
94+
def test_resolve_client_common_extra_ce_client():
95+
cpp_submission = Submission(
96+
source_code="", # source code is not important because in this test
97+
language_id=Language.CPP_CLANG,
98+
)
99+
100+
py_submission = Submission(
101+
source_code="", # source code is not important because in this test
102+
language_id=Language.PYTHON_FOR_ML,
103+
)
104+
105+
submissions = [cpp_submission, py_submission]
106+
107+
assert resolve_client(submissions=submissions) is JUDGE0_IMPLICIT_EXTRA_CE_CLIENT

0 commit comments

Comments
 (0)