Skip to content

Commit 5601aa6

Browse files
authored
New config filed extra_files (#31)
* Add handling for extra files * Add tests * Fix tests, run formatters * Add information to the documentation
1 parent 8bee447 commit 5601aa6

File tree

27 files changed

+798
-26
lines changed

27 files changed

+798
-26
lines changed

docs/sinolpack.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ fields:
157157
present when the program is executed. The files are relative
158158
to the `prog/` directory.
159159

160+
- `extra_files` -- a new field, introduced by SIO3Pack. It
161+
is an array of paths (relative to the root of the package)
162+
that should be saved along with the package. These files
163+
can be later used in custom workflows.
164+
160165
The `config.yml` can also contain other fields used by various
161166
tools, like `sinol-make` which uses fields starting with
162167
`sinol_`. These fields are ignored by SIO2 and are described
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
{
2+
"compile_extra": {
3+
"name": "Workflow for compiling the interactor",
4+
"external_objects": [
5+
"<EXTRA_FILE:prog/abcsoc.cpp>"
6+
],
7+
"observable_objects": [],
8+
"registers": 0,
9+
"observable_registers": 0,
10+
"tasks": [
11+
{
12+
"name": "Compile interactor",
13+
"type": "execution",
14+
"channels": [],
15+
"exclusive" : false,
16+
"hard_time_limit": 60000,
17+
"output_register": "obsreg:soc_compilation_result",
18+
"pid_namespaces": 1,
19+
"filesystems": [
20+
{
21+
"type": "object",
22+
"handle": "<EXTRA_FILE:prog/abcsoc.cpp>"
23+
},
24+
{
25+
"type": "object",
26+
"handle": "<EXTRA_EXE:prog/abcsoc.cpp>"
27+
}
28+
],
29+
"mount_namespaces": [
30+
{
31+
"mountpoints": [
32+
{
33+
"source": 0,
34+
"target": "/soc.cpp",
35+
"writable": false
36+
},
37+
{
38+
"source": 1,
39+
"target": "/soc",
40+
"writable": true
41+
}
42+
],
43+
"root": 0
44+
}
45+
],
46+
"pipes": 0,
47+
"resource_groups": [
48+
{
49+
"cpu_usage_limit": 100.0,
50+
"instruction_limit": 1000000000.0,
51+
"memory_limit": 2147483648,
52+
"oom_terminate_all_tasks": false,
53+
"pid_limit": 2,
54+
"swap_limit": 0,
55+
"time_limit": 1000000000.0
56+
}
57+
],
58+
"processes": [
59+
{
60+
"arguments": [
61+
"g++",
62+
"/soc.cpp",
63+
"-o",
64+
"/soc",
65+
"-std=c++20",
66+
"-O3"
67+
],
68+
"environment": [],
69+
"image": "compiler:g++",
70+
"mount_namespace": 0,
71+
"resource_group": 0,
72+
"pid_namespace": 0,
73+
"working_directory": "/",
74+
"descriptors": {}
75+
}
76+
]
77+
}
78+
]
79+
},
80+
"run_test": {
81+
"name": "Workflow for running the solution with interactor",
82+
"external_objects": [
83+
"<EXTRA_EXE:prog/abcsoc.cpp>",
84+
"<IN_TEST_PATH>",
85+
"<SOL_PATH>"
86+
],
87+
"observable_objects": [],
88+
"registers": 0,
89+
"observable_registers": 0,
90+
"tasks": [
91+
{
92+
"name": "Run solution with interactor on test <TEST_ID>",
93+
"type": "execution",
94+
"pipes": 2,
95+
"channels": [
96+
{
97+
"buffer_size": 1048576,
98+
"source_pipe": 0,
99+
"target_pipe": 1
100+
},
101+
{
102+
"buffer_size": 1048576,
103+
"source_pipe": 1,
104+
"target_pipe": 0
105+
}
106+
],
107+
"exclusive": false,
108+
"hard_time_limit": 60000,
109+
"output_register": "r:run_res_<TEST_ID>",
110+
"pid_namespaces": 2,
111+
"filesystems": [
112+
{
113+
"type": "object",
114+
"handle": "<EXTRA_EXE:prog/abcsoc.cpp>"
115+
},
116+
{
117+
"type": "object",
118+
"handle": "<SOL_PATH>"
119+
}
120+
],
121+
"mount_namespaces": [
122+
{
123+
"mountpoints": [
124+
{
125+
"source": 0,
126+
"target": "/soc",
127+
"writable": false
128+
}
129+
],
130+
"root": 0
131+
},
132+
{
133+
"mountpoints": [
134+
{
135+
"source": 0,
136+
"target": "/exe",
137+
"writable": false
138+
}
139+
],
140+
"root": 0
141+
}
142+
],
143+
"resource_groups": [
144+
{
145+
"cpu_usage_limit": 100.0,
146+
"instruction_limit": 1000000000.0,
147+
"memory_limit": 2147483648,
148+
"oom_terminate_all_tasks": false,
149+
"pid_limit": 2,
150+
"swap_limit": 0,
151+
"time_limit": 1000000000.0
152+
}
153+
],
154+
"processes": [
155+
{
156+
"arguments": [
157+
"/soc", "1", "2", "3"
158+
],
159+
"environment": [],
160+
"image": "",
161+
"mount_namespace": 0,
162+
"resource_group": 0,
163+
"pid_namespace": 0,
164+
"working_directory": "/",
165+
"descriptors": {
166+
"0": {
167+
"type": "object_read",
168+
"handle": "<IN_TEST_PATH>"
169+
},
170+
"1": {
171+
"type": "object_write",
172+
"handle": "soc_res_<TEST_ID>"
173+
},
174+
"2": {
175+
"type": "pipe_write",
176+
"pipe": 0
177+
},
178+
"3": {
179+
"type": "pipe_read",
180+
"pipe": 1
181+
}
182+
}
183+
},
184+
{
185+
"arguments": [
186+
"/exe"
187+
],
188+
"environment": [],
189+
"image": "",
190+
"mount_namespace": 1,
191+
"resource_group": 0,
192+
"pid_namespace": 1,
193+
"working_directory": "/",
194+
"descriptors": {
195+
"0": {
196+
"type": "pipe_read",
197+
"pipe": 0
198+
},
199+
"1": {
200+
"type": "pipe_write",
201+
"pipe": 1
202+
}
203+
}
204+
}
205+
]
206+
},
207+
{
208+
"name": "Grade test <TEST_ID>",
209+
"type": "script",
210+
"reactive": false,
211+
"input_registers": [
212+
"r:run_res_<TEST_ID>"
213+
],
214+
"output_registers": [
215+
"r:grade_res_<TEST_ID>"
216+
],
217+
"objects": [
218+
"soc_res_<TEST_ID>"
219+
],
220+
"script": "grade here"
221+
}
222+
]
223+
}
224+
}

src/sio3pack/django/sinolpack/handler.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
SinolpackAdditionalFile,
1010
SinolpackAttachment,
1111
SinolpackConfig,
12+
SinolpackExtraFile,
1213
SinolpackModelSolution,
1314
SinolpackSpecialFile,
1415
)
@@ -34,6 +35,7 @@ def save_to_db(self):
3435
self._save_config()
3536
self._save_additional_files()
3637
self._save_special_files()
38+
self._save_extra_files()
3739
self._save_attachments()
3840

3941
def _save_config(self):
@@ -80,6 +82,14 @@ def _save_special_files(self):
8082
)
8183
instance.save()
8284

85+
def _save_extra_files(self):
86+
for path, file in self.package.extra_files.items():
87+
instance = SinolpackExtraFile(
88+
package=self.db_package,
89+
package_path=path,
90+
)
91+
instance.file.save(file.filename, File(open(file.path, "rb")))
92+
8393
def _save_attachments(self):
8494
for attachment in self.package.attachments:
8595
instance = SinolpackAttachment(
@@ -148,3 +158,25 @@ def attachments(self) -> list[RemoteFile]:
148158
A list of attachments (as :class:`sio3pack.RemoteFile`) related to the problem.
149159
"""
150160
return [RemoteFile(f.content) for f in self.db_package.attachments.all()]
161+
162+
@property
163+
def extra_files(self) -> dict[str, RemoteFile]:
164+
"""
165+
A dictionary of extra files (as :class:`sio3pack.RemoteFile`) for the problem, as
166+
specified in the config file. The keys are the paths of the files in the package.
167+
"""
168+
files = self.db_package.extra_files.all()
169+
return {f.package_path: RemoteFile(f.file) for f in files}
170+
171+
def get_extra_file(self, package_path: str) -> RemoteFile | None:
172+
"""
173+
Get an extra file (as :class:`sio3pack.RemoteFile`) for the problem.
174+
175+
:param package_path: The path of the file in the package.
176+
:return: The extra file (as :class:`sio3pack.RemoteFile`) or None if it does not exist.
177+
"""
178+
try:
179+
extra_file = self.db_package.extra_files.get(package_path=package_path)
180+
return RemoteFile(extra_file.file)
181+
except SinolpackExtraFile.DoesNotExist:
182+
return None
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 4.2.21 on 2025-05-18 11:13
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import sio3pack.django.common.models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("common", "0006_alter_sio3packmainmodelsolution_package_and_more"),
12+
("sinolpack", "0003_sinolpackspecialfile"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="SinolpackExtraFile",
18+
fields=[
19+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
20+
("package_path", models.CharField(max_length=255, verbose_name="package path")),
21+
(
22+
"file",
23+
models.FileField(
24+
upload_to=sio3pack.django.common.models.make_problem_filename, verbose_name="file"
25+
),
26+
),
27+
(
28+
"package",
29+
models.ForeignKey(
30+
on_delete=django.db.models.deletion.CASCADE, related_name="extra_files", to="common.sio3package"
31+
),
32+
),
33+
],
34+
),
35+
]

src/sio3pack/django/sinolpack/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,12 @@ def __str__(self):
9393
class Meta(object):
9494
verbose_name = _("attachment")
9595
verbose_name_plural = _("attachments")
96+
97+
98+
class SinolpackExtraFile(models.Model):
99+
package = models.ForeignKey(SIO3Package, on_delete=models.CASCADE, related_name="extra_files")
100+
package_path = models.CharField(max_length=255, verbose_name=_("package path"))
101+
file = FileField(upload_to=make_problem_filename, verbose_name=_("file"))
102+
103+
def __str__(self):
104+
return f"<SinolpackExtraFile {self.package_path}>"

src/sio3pack/packages/sinolpack/model.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class Sinolpack(Package):
3535
a model solution kind and a file.
3636
:param list[File] additional_files: A list of additional files for
3737
the problem.
38+
:param dict[str, File] extra_files: A dictionary of extra files as specified
39+
in the config.yml file, where keys are file paths and values are
40+
:class:`sio3pack.files.File` objects.
3841
:param list[File] attachments: A list of attachments related to the problem.
3942
:param WorkflowManager workflow_manager: A workflow manager for the problem.
4043
:param File | None main_model_solution: The main model solution file.
@@ -178,6 +181,7 @@ def _process_package(self):
178181
self._detect_full_name()
179182
self._detect_full_name_translations()
180183
self._process_prog_files()
184+
self._process_extra_files()
181185
self._process_statements()
182186
self._process_attachments()
183187
self._process_existing_tests()
@@ -325,6 +329,34 @@ def _process_prog_files(self):
325329
except FileNotFoundError:
326330
self.special_files[file] = None
327331

332+
def _process_extra_files(self):
333+
"""
334+
Process extra files from the config.yml file. The files are
335+
stored in the `extra_files` attribute.
336+
"""
337+
self.extra_files = {}
338+
conf_extra_files = self.config.get("extra_files", [])
339+
if isinstance(conf_extra_files, str):
340+
conf_extra_files = [conf_extra_files]
341+
for file in conf_extra_files:
342+
try:
343+
lf = LocalFile(os.path.join(self.rootdir, file))
344+
self.extra_files[file] = lf
345+
except FileNotFoundError:
346+
pass
347+
348+
def get_extra_file(self, package_path: str) -> File | None:
349+
"""
350+
Returns the extra file with the given package path.
351+
352+
:param package_path: The path to the extra file in the package.
353+
:return: The extra file if it exists, otherwise None.
354+
"""
355+
if self.is_from_db:
356+
return self.django.get_extra_file(package_path)
357+
else:
358+
return self.extra_files.get(package_path, None)
359+
328360
def get_statement(self, lang: str | None = None) -> File | None:
329361
"""
330362
Returns the problem statement for a given language code.

0 commit comments

Comments
 (0)