Skip to content

Commit b9c39ce

Browse files
committed
Add to_dict and from_dict to Submission and Filesystem objects.
1 parent e9fc9da commit b9c39ce

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

src/judge0/base_types.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from abc import ABC, abstractmethod
21
from dataclasses import dataclass
32
from enum import IntEnum
4-
from typing import Optional, Sequence, Union
3+
from typing import Optional, Protocol, runtime_checkable, Sequence, Union
54

65
Iterable = Sequence
76

@@ -33,10 +32,11 @@ def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase":
3332
)
3433

3534

36-
class Encodeable(ABC):
37-
@abstractmethod
35+
@runtime_checkable
36+
class Encodeable(Protocol):
3837
def encode(self) -> bytes:
39-
pass
38+
"""Serialize the object to bytes."""
39+
...
4040

4141

4242
@dataclass(frozen=True)
@@ -46,6 +46,8 @@ class Language:
4646

4747

4848
class LanguageAlias(IntEnum):
49+
"""Language enumeration."""
50+
4951
PYTHON = 0
5052
CPP = 1
5153
JAVA = 2
@@ -55,11 +57,15 @@ class LanguageAlias(IntEnum):
5557

5658

5759
class Flavor(IntEnum):
60+
"""Judge0 flavor enumeration."""
61+
5862
CE = 0
5963
EXTRA_CE = 1
6064

6165

6266
class Status(IntEnum):
67+
"""Status enumeration."""
68+
6369
IN_QUEUE = 1
6470
PROCESSING = 2
6571
ACCEPTED = 3

src/judge0/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from itertools import islice
33
from typing import Union
44

5-
from .base_types import Encodeable
5+
from judge0.base_types import Encodeable
66

77

88
def encode(content: Union[bytes, str, Encodeable]) -> str:
@@ -26,7 +26,7 @@ def decode(content: Union[bytes, str]) -> str:
2626

2727

2828
def batched(iterable, n):
29-
"""Utility function for batching submissions.
29+
"""Iterate over an iterable in batches of a specified size.
3030
3131
Adapted from https://docs.python.org/3/library/itertools.html#itertools.batched.
3232
"""

src/judge0/filesystem.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from base64 import b64decode, b64encode
66
from typing import Optional, Union
77

8-
from .base_types import Encodeable, Iterable
8+
from .base_types import Iterable
99

1010

1111
class File:
@@ -24,7 +24,7 @@ def __str__(self):
2424
return self.content.decode(errors="backslashreplace")
2525

2626

27-
class Filesystem(Encodeable):
27+
class Filesystem:
2828
def __init__(
2929
self,
3030
content: Optional[Union[str, bytes, File, Iterable[File], "Filesystem"]] = None,
@@ -47,6 +47,14 @@ def __init__(
4747
self.files = [content]
4848
elif isinstance(content, Filesystem):
4949
self.files = copy.deepcopy(content.files)
50+
elif content is None:
51+
pass
52+
else:
53+
raise ValueError(
54+
"Unsupported type for content argument. Expected "
55+
"one of str, bytes, File, Iterable[File], or Filesystem, "
56+
f"got {type(content)}."
57+
)
5058

5159
def __repr__(self) -> str:
5260
content_encoded = b64encode(self.encode()).decode()
@@ -59,7 +67,17 @@ def encode(self) -> bytes:
5967
zip_file.writestr(file.name, file.content)
6068
return zip_buffer.getvalue()
6169

70+
def to_dict(self) -> dict:
71+
"""Pack the Filesystem object to a dictionary."""
72+
return {"filesystem": str(self)}
73+
74+
@staticmethod
75+
def from_dict(filesystem_dict: dict) -> "Filesystem":
76+
"""Create a Filesystem object from dictionary."""
77+
return Filesystem(filesystem_dict.get("filesystem"))
78+
6279
def __str__(self) -> str:
80+
"""Create string representation of Filesystem object."""
6381
return b64encode(self.encode()).decode()
6482

6583
def __iter__(self):

src/judge0/submission.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"stdout",
1818
"stderr",
1919
"compile_output",
20-
# "post_execution_filesystem",
20+
"post_execution_filesystem",
2121
}
2222
ENCODED_FIELDS = ENCODED_REQUEST_FIELDS | ENCODED_RESPONSE_FIELDS
2323
EXTRA_REQUEST_FIELDS = {
@@ -48,7 +48,6 @@
4848
"time",
4949
"wall_time",
5050
"memory",
51-
"post_execution_filesystem",
5251
}
5352
REQUEST_FIELDS = ENCODED_REQUEST_FIELDS | EXTRA_REQUEST_FIELDS
5453
RESPONSE_FIELDS = ENCODED_RESPONSE_FIELDS | EXTRA_RESPONSE_FIELDS
@@ -207,7 +206,7 @@ def set_attributes(self, attributes: dict[str, Any]) -> None:
207206
if attr in SKIP_FIELDS:
208207
continue
209208

210-
if attr in ENCODED_FIELDS:
209+
if attr in ENCODED_FIELDS and attr not in ("post_execution_filesystem",):
211210
value = decode(value) if value else None
212211
elif attr == "status":
213212
value = Status(value["id"])
@@ -241,6 +240,43 @@ def as_body(self, client: "Client") -> dict:
241240

242241
return body
243242

243+
def to_dict(self) -> dict:
244+
encoded_request_fields = {
245+
field_name: encode(getattr(self, field_name))
246+
for field_name in ENCODED_REQUEST_FIELDS
247+
if getattr(self, field_name) is not None
248+
}
249+
extra_request_fields = {
250+
field_name: getattr(self, field_name)
251+
for field_name in EXTRA_REQUEST_FIELDS
252+
if getattr(self, field_name) is not None
253+
}
254+
encoded_response_fields = {
255+
field_name: encode(getattr(self, field_name))
256+
for field_name in ENCODED_RESPONSE_FIELDS
257+
if getattr(self, field_name) is not None
258+
}
259+
extra_response_fields = {
260+
field_name: getattr(self, field_name)
261+
for field_name in EXTRA_RESPONSE_FIELDS
262+
if getattr(self, field_name) is not None
263+
}
264+
265+
submission_dict = (
266+
encoded_request_fields
267+
| extra_request_fields
268+
| encoded_response_fields
269+
| extra_response_fields
270+
)
271+
272+
return submission_dict
273+
274+
@staticmethod
275+
def from_dict(submission_dict) -> "Submission":
276+
submission = Submission()
277+
submission.set_attributes(submission_dict)
278+
return submission
279+
244280
def is_done(self) -> bool:
245281
"""Check if submission is finished processing.
246282

0 commit comments

Comments
 (0)