rbx.cp 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbx/box/cd.py +2 -2
- rbx/box/cli.py +8 -2
- rbx/box/code.py +2 -2
- rbx/box/contest/build_contest_statements.py +2 -2
- rbx/box/contest/contest_package.py +1 -1
- rbx/box/contest/main.py +29 -2
- rbx/box/environment.py +140 -80
- rbx/box/formatting.py +2 -1
- rbx/box/package.py +5 -5
- rbx/box/packaging/__init__.py +0 -0
- rbx/box/packaging/boca/__init__.py +0 -0
- rbx/box/packaging/polygon/packager.py +3 -3
- rbx/box/presets/__init__.py +369 -53
- rbx/box/presets/lock_schema.py +42 -2
- rbx/box/presets/schema.py +4 -0
- rbx/box/remote.py +3 -3
- rbx/box/retries.py +3 -2
- rbx/box/sanitizers/warning_stack.py +2 -2
- rbx/box/solutions.py +24 -18
- rbx/box/statements/build_statements.py +6 -6
- rbx/box/statements/builders.py +1 -1
- rbx/box/stresses.py +2 -2
- rbx/box/testcase_utils.py +3 -3
- rbx/grading/judge/sandbox.py +2 -1
- rbx/grading/judge/sandboxes/isolate.py +3 -2
- rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
- rbx/grading/judge/storage.py +2 -1
- rbx/grading/steps.py +2 -1
- rbx/resources/envs/default.rbx.yml +2 -3
- rbx/resources/envs/isolate.rbx.yml +2 -3
- rbx/resources/presets/default/contest/.gitignore +5 -1
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -1
- rbx/resources/presets/default/env.rbx.yml +67 -0
- rbx/resources/presets/default/preset.rbx.yml +6 -2
- rbx/resources/presets/default/problem/.gitignore +1 -1
- rbx/resources/presets/default/{contest/statement/template.rbx.tex → shared/problem_template.rbx.tex} +13 -7
- rbx/submitors/codeforces.py +3 -2
- rbx/test.py +1 -1
- rbx/utils.py +6 -1
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.0.dist-info}/METADATA +2 -1
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.0.dist-info}/RECORD +46 -44
- rbx/resources/presets/default/problem/statement/icpc.sty +0 -322
- /rbx/resources/presets/default/{problem/statement/template.rbx.tex → shared/contest_template.rbx.tex} +0 -0
- /rbx/resources/presets/default/{contest/statement → shared}/icpc.sty +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.0.dist-info}/LICENSE +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.0.dist-info}/WHEEL +0 -0
- {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.0.dist-info}/entry_points.txt +0 -0
rbx/box/cd.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|
5
5
|
|
6
6
|
import typer
|
7
7
|
|
8
|
-
from rbx import console
|
8
|
+
from rbx import console, utils
|
9
9
|
from rbx.box.sanitizers import warning_stack
|
10
10
|
from rbx.utils import new_cd
|
11
11
|
|
@@ -13,7 +13,7 @@ from rbx.utils import new_cd
|
|
13
13
|
def find_package(
|
14
14
|
root: pathlib.Path = pathlib.Path(), consider_presets: bool = False
|
15
15
|
) -> Optional[pathlib.Path]:
|
16
|
-
root =
|
16
|
+
root = utils.abspath(root)
|
17
17
|
|
18
18
|
def has_file():
|
19
19
|
problem_yaml_path = root / 'problem.rbx.yml'
|
rbx/box/cli.py
CHANGED
@@ -577,7 +577,13 @@ async def irun(
|
|
577
577
|
help='Create a new problem package.',
|
578
578
|
)
|
579
579
|
def create(
|
580
|
-
name:
|
580
|
+
name: Annotated[
|
581
|
+
str,
|
582
|
+
typer.Option(
|
583
|
+
help='Name of the problem to create, which will be used as the name of the new folder.',
|
584
|
+
prompt='What should the name of the problem be?',
|
585
|
+
),
|
586
|
+
],
|
581
587
|
preset: Annotated[
|
582
588
|
Optional[str], typer.Option(help='Preset to use when creating the problem.')
|
583
589
|
] = None,
|
@@ -914,7 +920,7 @@ def languages():
|
|
914
920
|
|
915
921
|
for language in env.languages:
|
916
922
|
console.console.print(
|
917
|
-
f'[item]{language.name}[/item], aka [item]{language.
|
923
|
+
f'[item]{language.name}[/item], aka [item]{language.readableName or language.name}[/item]:'
|
918
924
|
)
|
919
925
|
console.console.print(language)
|
920
926
|
console.console.print()
|
rbx/box/code.py
CHANGED
@@ -12,7 +12,7 @@ import rich.text
|
|
12
12
|
import typer
|
13
13
|
from pydantic import BaseModel
|
14
14
|
|
15
|
-
from rbx import console
|
15
|
+
from rbx import console, utils
|
16
16
|
from rbx.box import download, global_package, package, setter_config, state
|
17
17
|
from rbx.box.environment import (
|
18
18
|
CompilationConfig,
|
@@ -296,7 +296,7 @@ def _prepare_for_communication(
|
|
296
296
|
)
|
297
297
|
|
298
298
|
if capture.merged_capture is not None:
|
299
|
-
merged_output_path = package.get_merged_capture_path()
|
299
|
+
merged_output_path = utils.abspath(package.get_merged_capture_path())
|
300
300
|
run.sandbox_params.timeit_dups['Do'].append(merged_output_path)
|
301
301
|
|
302
302
|
run.artifacts.outputs.append(
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
6
6
|
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from rbx import console, testing_utils
|
9
|
+
from rbx import console, testing_utils, utils
|
10
10
|
from rbx.box import cd, package
|
11
11
|
from rbx.box.contest.contest_package import get_problems
|
12
12
|
from rbx.box.contest.schema import Contest, ContestProblem, ContestStatement
|
@@ -156,7 +156,7 @@ def _build_problem_statements(
|
|
156
156
|
console.console.print('Building problem-level statements...')
|
157
157
|
extracted_problems = get_problems_for_statement(contest, statement)
|
158
158
|
res = []
|
159
|
-
contest_cwd_absolute = pathlib.Path()
|
159
|
+
contest_cwd_absolute = utils.abspath(pathlib.Path())
|
160
160
|
contest_assets = get_relative_assets(statement.path, statement.assets)
|
161
161
|
|
162
162
|
extra_vars = dict(statement.override.vars if statement.override is not None else {})
|
@@ -17,7 +17,7 @@ YAML_NAME = 'contest.rbx.yml'
|
|
17
17
|
|
18
18
|
@functools.cache
|
19
19
|
def find_contest_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
20
|
-
root =
|
20
|
+
root = utils.abspath(root)
|
21
21
|
contest_yaml_path = root / YAML_NAME
|
22
22
|
while root != pathlib.PosixPath('/') and not contest_yaml_path.is_file():
|
23
23
|
root = root.parent
|
rbx/box/contest/main.py
CHANGED
@@ -38,7 +38,13 @@ app.add_typer(
|
|
38
38
|
|
39
39
|
@app.command('create, c', help='Create a new contest package.')
|
40
40
|
def create(
|
41
|
-
path:
|
41
|
+
path: Annotated[
|
42
|
+
str,
|
43
|
+
typer.Option(
|
44
|
+
help='Path where to create the contest.',
|
45
|
+
prompt='Where should the contest be created, relative to the current directory? (e.g. "contests/ioi2024")',
|
46
|
+
),
|
47
|
+
],
|
42
48
|
preset: Annotated[
|
43
49
|
Optional[str],
|
44
50
|
typer.Option(
|
@@ -108,7 +114,28 @@ def edit():
|
|
108
114
|
|
109
115
|
@app.command('add, a', help='Add new problem to contest.')
|
110
116
|
@within_contest
|
111
|
-
def add(
|
117
|
+
def add(
|
118
|
+
path: Annotated[
|
119
|
+
str,
|
120
|
+
typer.Option(
|
121
|
+
help='Path where to create the problem. Name part of the path will be used as the problem name.',
|
122
|
+
prompt='Where should the problem be created, relative to the contest root? (e.g. problems/choco will create a problem named "choco" in this directory)',
|
123
|
+
),
|
124
|
+
],
|
125
|
+
short_name: Annotated[
|
126
|
+
str,
|
127
|
+
typer.Option(
|
128
|
+
help='Short name of the problem. Will be used as the identifier in the contest.',
|
129
|
+
prompt='What should the problem be named? (e.g. "A", "B1", "B2", "Z")',
|
130
|
+
),
|
131
|
+
],
|
132
|
+
preset: Annotated[
|
133
|
+
Optional[str],
|
134
|
+
typer.Option(
|
135
|
+
help='Preset to use when creating the problem. If not specified, the active preset will be used.',
|
136
|
+
),
|
137
|
+
] = None,
|
138
|
+
):
|
112
139
|
problem_path = pathlib.Path(path)
|
113
140
|
name = problem_path.stem
|
114
141
|
utils.validate_field(ContestProblem, 'short_name', short_name)
|
rbx/box/environment.py
CHANGED
@@ -40,102 +40,149 @@ VerificationParam = Annotated[
|
|
40
40
|
class FileMapping(BaseModel):
|
41
41
|
model_config = ConfigDict(extra='forbid')
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
input: str = Field(
|
44
|
+
default='stdin',
|
45
|
+
description="""Path where to copy the stdin file to before running the program,
|
46
|
+
relative to the sandbox root.""",
|
47
|
+
)
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
output: str = Field(
|
50
|
+
default='stdout',
|
51
|
+
description="""Path where to output the stdout file after running the program,
|
52
|
+
relative to the sandbox root.""",
|
53
|
+
)
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
error: str = Field(
|
56
|
+
default='stderr',
|
57
|
+
description="""Path where to output the stderr file after running the program,
|
58
|
+
relative to the sandbox root.""",
|
59
|
+
)
|
54
60
|
|
55
|
-
|
56
|
-
|
57
|
-
|
61
|
+
compilable: str = Field(
|
62
|
+
default='compilable',
|
63
|
+
description="""Path where to copy the compilable file to before compiling the program,
|
64
|
+
relative to the sandbox root.""",
|
65
|
+
)
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
executable: str = Field(
|
68
|
+
default='executable',
|
69
|
+
description="""Path to where to output the executable file after compiling the program,
|
70
|
+
relative to the sandbox root.""",
|
71
|
+
)
|
62
72
|
|
63
73
|
|
64
74
|
class EnvironmentSandbox(BaseModel):
|
65
75
|
model_config = ConfigDict(extra='forbid')
|
66
76
|
|
67
|
-
|
68
|
-
|
77
|
+
maxProcesses: Optional[int] = Field(
|
78
|
+
default=1,
|
79
|
+
description="""Max. number of process to allow to run concurrently for the program.""",
|
80
|
+
)
|
69
81
|
|
70
|
-
|
71
|
-
|
82
|
+
timeLimit: Optional[int] = Field(
|
83
|
+
default=None,
|
84
|
+
description="""Time limit in milliseconds to allow the program to run.""",
|
85
|
+
)
|
72
86
|
|
73
|
-
|
74
|
-
|
87
|
+
wallTimeLimit: Optional[int] = Field(
|
88
|
+
default=None,
|
89
|
+
description="""Wall time limit in milliseconds to allow the program to run.""",
|
90
|
+
)
|
75
91
|
|
76
|
-
|
77
|
-
|
92
|
+
memoryLimit: Optional[int] = Field(
|
93
|
+
default=None,
|
94
|
+
description="""Memory limit in MiB.""",
|
95
|
+
)
|
78
96
|
|
79
|
-
|
80
|
-
|
97
|
+
fileSizeLimit: Optional[int] = Field(
|
98
|
+
default=None,
|
99
|
+
description="""File size limit in KiB""",
|
100
|
+
)
|
81
101
|
|
82
|
-
|
83
|
-
|
102
|
+
stackLimit: Optional[int] = Field(
|
103
|
+
default=None,
|
104
|
+
description="""Stack limit in MiB.""",
|
105
|
+
)
|
84
106
|
|
85
|
-
|
86
|
-
|
107
|
+
preserveEnv: Optional[bool] = Field(
|
108
|
+
default=False,
|
109
|
+
description="""Whether to preserve env. variables coming from the host.""",
|
110
|
+
)
|
87
111
|
|
88
|
-
|
89
|
-
|
112
|
+
mirrorDirs: Optional[List[str]] = Field(
|
113
|
+
default=[],
|
114
|
+
description="""Directories in the host that should be read-only exposed to the sandbox.""",
|
115
|
+
)
|
90
116
|
|
91
117
|
|
92
118
|
class CompilationConfig(BaseModel):
|
93
|
-
|
94
|
-
|
119
|
+
commands: Optional[List[str]] = Field(
|
120
|
+
default=[],
|
121
|
+
description="""Commands to compile the program.""",
|
122
|
+
)
|
95
123
|
|
96
|
-
|
97
|
-
|
124
|
+
sandbox: Optional[EnvironmentSandbox] = Field(
|
125
|
+
default=None,
|
126
|
+
description="""Sandbox configuration to use when compiling for this language.""",
|
127
|
+
)
|
98
128
|
|
99
129
|
|
100
130
|
class ExecutionConfig(BaseModel):
|
101
131
|
model_config = ConfigDict(extra='forbid')
|
102
132
|
|
103
|
-
|
104
|
-
|
133
|
+
command: Optional[str] = Field(
|
134
|
+
default=None,
|
135
|
+
description="""Command to run the program.""",
|
136
|
+
)
|
105
137
|
|
106
|
-
|
107
|
-
|
138
|
+
sandbox: Optional[EnvironmentSandbox] = Field(
|
139
|
+
default=None,
|
140
|
+
description="""Sandbox configuration to use when executing for this language.""",
|
141
|
+
)
|
108
142
|
|
109
|
-
|
110
|
-
|
143
|
+
problemLimits: Limits = Field(
|
144
|
+
default_factory=Limits,
|
145
|
+
description="""Original limits of the problem.""",
|
146
|
+
)
|
111
147
|
|
112
148
|
|
113
149
|
class EnvironmentLanguage(BaseModel):
|
114
150
|
model_config = ConfigDict(extra='forbid')
|
115
151
|
|
116
|
-
|
117
|
-
|
152
|
+
name: str = Field(
|
153
|
+
description="""Identifier of this language within this environment."""
|
154
|
+
)
|
118
155
|
|
119
|
-
|
120
|
-
|
156
|
+
readableName: Optional[str] = Field(
|
157
|
+
default=None,
|
158
|
+
description="""Readable name for this language.""",
|
159
|
+
)
|
121
160
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
161
|
+
extension: str = Field(
|
162
|
+
description="""File extension supported by this language. If there's only one language
|
163
|
+
that supports a certain file extension in the environment, the tool
|
164
|
+
will automatically identify the language based on such extension."""
|
165
|
+
)
|
126
166
|
|
127
|
-
|
128
|
-
|
167
|
+
compilation: Optional[CompilationConfig] = Field(
|
168
|
+
default=None,
|
169
|
+
description="""Compilation config to use when compiling programs for this language.""",
|
170
|
+
)
|
129
171
|
|
130
|
-
|
131
|
-
|
172
|
+
execution: ExecutionConfig = Field(
|
173
|
+
description="""Execution config to use when running programs for this language."""
|
174
|
+
)
|
132
175
|
|
133
|
-
|
134
|
-
|
135
|
-
|
176
|
+
fileMapping: Optional[FileMapping] = Field(
|
177
|
+
default=None,
|
178
|
+
description="""Mapping for files within the sandbox. If not specified, the default mapping
|
179
|
+
for the environment will be used.""",
|
180
|
+
)
|
136
181
|
|
137
|
-
|
138
|
-
|
182
|
+
extensions: Optional[LanguageExtensions] = Field(
|
183
|
+
default=None,
|
184
|
+
description="""Extensions to apply for this language.""",
|
185
|
+
)
|
139
186
|
|
140
187
|
def get_extension(self, name: str, _: Type[T]) -> Optional[T]:
|
141
188
|
if self.extensions is None:
|
@@ -151,39 +198,52 @@ class EnvironmentLanguage(BaseModel):
|
|
151
198
|
class TimingConfig(BaseModel):
|
152
199
|
model_config = ConfigDict(extra='forbid')
|
153
200
|
|
154
|
-
|
155
|
-
|
201
|
+
formula: str = Field(
|
202
|
+
default='step_up(max(fastest * 3, slowest * 1.5), 100)',
|
203
|
+
description="""Formula to use to calculate the time limit for the environment.""",
|
204
|
+
)
|
156
205
|
|
157
206
|
|
158
207
|
class Environment(BaseModel):
|
159
208
|
model_config = ConfigDict(extra='forbid')
|
160
209
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# the compilation config can be individually overridden in the language configuration.
|
167
|
-
defaultCompilation: Optional[CompilationConfig] = None
|
210
|
+
defaultFileMapping: Optional[FileMapping] = Field(
|
211
|
+
default=None,
|
212
|
+
description="""Default mapping for files within the sandbox. Fields in the mapping can be
|
213
|
+
individually overridden in the language configuration.""",
|
214
|
+
)
|
168
215
|
|
169
|
-
|
170
|
-
|
171
|
-
|
216
|
+
defaultCompilation: Optional[CompilationConfig] = Field(
|
217
|
+
default=None,
|
218
|
+
description="""Default compilation configuration to use when compiling programs. Fields in
|
219
|
+
the compilation config can be individually overridden in the language configuration.""",
|
220
|
+
)
|
172
221
|
|
173
|
-
|
174
|
-
|
222
|
+
defaultExecution: Optional[ExecutionConfig] = Field(
|
223
|
+
default=None,
|
224
|
+
description="""Default execution configuration to use when running programs. Fields in the
|
225
|
+
execution config can be individually overridden in the language configuration.""",
|
226
|
+
)
|
175
227
|
|
176
|
-
|
177
|
-
|
228
|
+
languages: List[EnvironmentLanguage] = Field(
|
229
|
+
default=[],
|
230
|
+
description="""Configuration for each language supported in this environment.""",
|
231
|
+
)
|
178
232
|
|
179
|
-
|
180
|
-
|
233
|
+
sandbox: str = Field(
|
234
|
+
default='stupid',
|
235
|
+
description="""Identifier of the sandbox used by this environment (e.g. "stupid", "isolate")""",
|
236
|
+
)
|
181
237
|
|
182
|
-
|
183
|
-
|
238
|
+
timing: TimingConfig = Field(
|
239
|
+
default_factory=TimingConfig,
|
240
|
+
description="""Timing configuration for the environment.""",
|
241
|
+
)
|
184
242
|
|
185
|
-
|
186
|
-
|
243
|
+
extensions: Optional[Extensions] = Field(
|
244
|
+
default=None,
|
245
|
+
description="""Extensions to be added to the environment.""",
|
246
|
+
)
|
187
247
|
|
188
248
|
|
189
249
|
def get_app_environment_path(env: str) -> pathlib.Path:
|
rbx/box/formatting.py
CHANGED
@@ -2,6 +2,7 @@ import os
|
|
2
2
|
import pathlib
|
3
3
|
from typing import Any, Optional
|
4
4
|
|
5
|
+
from rbx import utils
|
5
6
|
from rbx.box import setter_config
|
6
7
|
|
7
8
|
|
@@ -23,7 +24,7 @@ def href(url: os.PathLike[str], text: Optional[str] = None, style: str = 'item')
|
|
23
24
|
return f'[{style}]{text}[/{style}]'
|
24
25
|
|
25
26
|
if isinstance(url, pathlib.Path):
|
26
|
-
url =
|
27
|
+
url = utils.abspath(url)
|
27
28
|
|
28
29
|
url_str = str(url)
|
29
30
|
if pathlib.Path(url_str).exists():
|
rbx/box/package.py
CHANGED
@@ -39,7 +39,7 @@ TEMP_DIR = None
|
|
39
39
|
|
40
40
|
@functools.cache
|
41
41
|
def find_problem_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
42
|
-
root =
|
42
|
+
root = utils.abspath(root)
|
43
43
|
problem_yaml_path = root / YAML_NAME
|
44
44
|
while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
|
45
45
|
root = root.parent
|
@@ -152,8 +152,8 @@ def get_problem_iruns_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
|
152
152
|
def get_problem_preprocessed_path(
|
153
153
|
item: pathlib.Path, root: pathlib.Path = pathlib.Path()
|
154
154
|
) -> pathlib.Path:
|
155
|
-
root_resolved =
|
156
|
-
item_resolved =
|
155
|
+
root_resolved = utils.abspath(root)
|
156
|
+
item_resolved = utils.abspath(item)
|
157
157
|
|
158
158
|
if not item_resolved.is_relative_to(root_resolved):
|
159
159
|
final_path = pathlib.Path('remote') / item_resolved.name
|
@@ -360,11 +360,11 @@ def get_test_groups_by_name(
|
|
360
360
|
# Return each compilation file and to where it should be moved inside
|
361
361
|
# the sandbox.
|
362
362
|
def get_compilation_files(code: CodeItem) -> List[Tuple[pathlib.Path, pathlib.Path]]:
|
363
|
-
code_dir = code.path.parent
|
363
|
+
code_dir = utils.abspath(code.path.parent)
|
364
364
|
|
365
365
|
res = []
|
366
366
|
for compilation_file in code.compilationFiles or []:
|
367
|
-
compilation_file_path = pathlib.Path(compilation_file)
|
367
|
+
compilation_file_path = utils.abspath(pathlib.Path(compilation_file))
|
368
368
|
if not compilation_file_path.is_file():
|
369
369
|
console.console.print(
|
370
370
|
f'[error]Compilation file [item]{compilation_file}[/item] for '
|
File without changes
|
File without changes
|
@@ -4,7 +4,7 @@ from typing import List, Optional
|
|
4
4
|
|
5
5
|
import typer
|
6
6
|
|
7
|
-
from rbx import console
|
7
|
+
from rbx import console, utils
|
8
8
|
from rbx.box import header, package
|
9
9
|
from rbx.box.lang import code_to_langs, is_valid_lang_code
|
10
10
|
from rbx.box.packaging.packager import (
|
@@ -129,7 +129,7 @@ class PolygonPackager(BasePackager):
|
|
129
129
|
shutil.copyfile(built_statement.path, final_path)
|
130
130
|
|
131
131
|
return polygon_schema.Statement(
|
132
|
-
path=str(
|
132
|
+
path=str(utils.abspath(final_path).relative_to(utils.abspath(into_path))),
|
133
133
|
language=language,
|
134
134
|
type=self._statement_application_type(built_statement), # type: ignore
|
135
135
|
)
|
@@ -238,7 +238,7 @@ class PolygonContestPackager(BaseContestPackager):
|
|
238
238
|
shutil.copyfile(built_statement.path, final_path)
|
239
239
|
|
240
240
|
return polygon_schema.Statement(
|
241
|
-
path=str(
|
241
|
+
path=str(utils.abspath(final_path).relative_to(utils.abspath(into_path))),
|
242
242
|
language=language,
|
243
243
|
type='application/pdf', # type: ignore
|
244
244
|
)
|