rbx.cp 0.5.29__py3-none-any.whl → 0.5.31__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/code.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import pathlib
2
2
  import re
3
+ import resource
3
4
  import shlex
4
5
  from enum import Enum
5
6
  from pathlib import PosixPath
@@ -10,7 +11,7 @@ import rich.text
10
11
  import typer
11
12
 
12
13
  from rbx import console
13
- from rbx.box import download, package, setter_config
14
+ from rbx.box import download, package, setter_config, state
14
15
  from rbx.box.environment import (
15
16
  ExecutionConfig,
16
17
  get_compilation_config,
@@ -22,6 +23,7 @@ from rbx.box.environment import (
22
23
  get_sandbox_params_from_config,
23
24
  merge_execution_configs,
24
25
  )
26
+ from rbx.box.formatting import get_formatted_memory
25
27
  from rbx.box.sanitizers import warning_stack
26
28
  from rbx.box.schema import CodeItem
27
29
  from rbx.grading import steps_with_caching
@@ -150,6 +152,57 @@ def _ignore_warning_in_cxx_input(input: GradingFileInput):
150
152
  input.src = preprocessed_path
151
153
 
152
154
 
155
+ def _format_stack_limit(limit: int) -> str:
156
+ if limit == resource.RLIM_INFINITY:
157
+ return 'unlimited'
158
+ return get_formatted_memory(limit)
159
+
160
+
161
+ def _check_stack_limit():
162
+ if not state.STATE.run_through_cli:
163
+ return
164
+ soft, hard = resource.RLIM_INFINITY, resource.RLIM_INFINITY
165
+
166
+ TARGET = 256 * 1024 * 1024 # 256 MiB
167
+ try:
168
+ soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
169
+ except Exception:
170
+ pass
171
+
172
+ if soft != hard or (soft != resource.RLIM_INFINITY and soft < TARGET):
173
+ soft_fmt = _format_stack_limit(soft)
174
+ hard_fmt = _format_stack_limit(hard)
175
+ console.console.print(
176
+ f'[error]Stack limit is too low (limit is set as [item]{soft_fmt}[/item], but configured user capacity is [item]{hard_fmt}[/item]).[/error]'
177
+ )
178
+ console.console.print(
179
+ '[error]It is not safe to develop problems in [item]rbx[/item] with this configuration.[/error]'
180
+ )
181
+ console.console.print(
182
+ 'To solve this, add the following lines to the end of your [item]~/.bashrc[/item] or [item]~/.zshrc[/item] file (or equivalent shell configuration file):'
183
+ )
184
+
185
+ target_text = TARGET
186
+ if hard != resource.RLIM_INFINITY:
187
+ target_text = min(hard, TARGET)
188
+ console.console.print(
189
+ """
190
+ ```
191
+ export RBX_BIN_PATH=`which rbx`
192
+ function rbx() {
193
+ ulimit -s %s && $RBX_BIN_PATH $@
194
+ }
195
+ ```
196
+ """
197
+ % target_text
198
+ )
199
+ console.console.print()
200
+ console.console.print(
201
+ 'You can read more about this in [item]https://rsalesc.github.io/rbx/stack-limit/[/item].'
202
+ )
203
+ raise typer.Exit(1)
204
+
205
+
153
206
  # Compile code item and return its digest in the storage.
154
207
  def compile_item(
155
208
  code: CodeItem,
@@ -157,6 +210,8 @@ def compile_item(
157
210
  force_warnings: bool = False,
158
211
  verbose: bool = False,
159
212
  ) -> str:
213
+ _check_stack_limit()
214
+
160
215
  generator_path = PosixPath(code.path)
161
216
 
162
217
  if not generator_path.is_file():
@@ -268,6 +323,8 @@ def run_item(
268
323
  extra_config: Optional[ExecutionConfig] = None,
269
324
  retry_index: Optional[int] = None,
270
325
  ) -> Optional[RunLog]:
326
+ _check_stack_limit()
327
+
271
328
  language = find_language_name(code)
272
329
  execution_options = get_execution_config(language)
273
330
  if extra_config is not None:
rbx/box/contest/schema.py CHANGED
@@ -19,7 +19,7 @@ class ProblemStatementOverride(BaseModel):
19
19
  model_config = ConfigDict(extra='forbid')
20
20
 
21
21
  configure: List[ConversionStep] = Field(
22
- [],
22
+ default=[],
23
23
  discriminator='type',
24
24
  description="""
25
25
  Configure how certain conversion steps should happen when applied to the statement file.
@@ -33,7 +33,7 @@ configure them in case they are applied.
33
33
  class ContestStatement(BaseModel):
34
34
  model_config = ConfigDict(extra='forbid')
35
35
 
36
- language: str = Field('en', description='Language code for this statement.')
36
+ language: str = Field(default='en', description='Language code for this statement.')
37
37
 
38
38
  title: str = Field(description='Title of the contest in this language.')
39
39
 
@@ -50,7 +50,7 @@ class ContestStatement(BaseModel):
50
50
  type: StatementType = Field(description='Type of the input statement file.')
51
51
 
52
52
  joiner: Optional[Joiner] = Field(
53
- None,
53
+ default=None,
54
54
  description="""
55
55
  Joiner to be used to build the statement.
56
56
 
@@ -98,7 +98,7 @@ Can be glob pattern as well, such as `imgs/*.png`.
98
98
  # Vars to be re-used in the statement.
99
99
  # - It will be available as \VAR{vars} variable in the contest-level box statement.
100
100
  vars: Dict[str, Primitive] = Field(
101
- {}, description='Variables to be re-used across the package.'
101
+ default={}, description='Variables to be re-used across the package.'
102
102
  )
103
103
 
104
104
  @property
@@ -147,7 +147,7 @@ class Contest(BaseModel):
147
147
  # Vars to be re-used in the statements.
148
148
  # - It will be available as \VAR{vars} variable in the contest-level box statement.
149
149
  vars: Dict[str, Primitive] = Field(
150
- {}, description='Variables to be re-used across the package.'
150
+ default={}, description='Variables to be re-used across the package.'
151
151
  )
152
152
 
153
153
  @property
rbx/box/creation.py CHANGED
@@ -6,6 +6,7 @@ import typer
6
6
 
7
7
  from rbx import console
8
8
  from rbx.box import presets
9
+ from rbx.box.contest.contest_package import find_contest_yaml
9
10
  from rbx.box.presets.fetch import get_preset_fetch_info
10
11
 
11
12
 
@@ -26,6 +27,15 @@ def create(
26
27
  ] = None,
27
28
  path: Optional[pathlib.Path] = None,
28
29
  ):
30
+ if find_contest_yaml() is not None:
31
+ console.console.print(
32
+ '[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
33
+ )
34
+ console.console.print(
35
+ '[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
36
+ )
37
+ raise typer.Exit(1)
38
+
29
39
  preset = preset or 'default'
30
40
  console.console.print(f'Creating new problem [item]{name}[/item]...')
31
41
 
rbx/box/extensions.py CHANGED
@@ -8,11 +8,11 @@ from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguageExtensio
8
8
  # Extension abstractions.
9
9
  class Extensions(BaseModel):
10
10
  boca: Optional[BocaExtension] = Field(
11
- None, description='Environment-level extensions for BOCA packaging.'
11
+ default=None, description='Environment-level extensions for BOCA packaging.'
12
12
  )
13
13
 
14
14
 
15
15
  class LanguageExtensions(BaseModel):
16
16
  boca: Optional[BocaLanguageExtension] = Field(
17
- None, description='Language-level extensions for BOCA packaging.'
17
+ default=None, description='Language-level extensions for BOCA packaging.'
18
18
  )
rbx/box/formatting.py ADDED
@@ -0,0 +1,10 @@
1
+ def get_formatted_memory(memory_in_bytes: int, mib_decimal_places: int = 0) -> str:
2
+ if memory_in_bytes < 1024 * 1024:
3
+ if memory_in_bytes < 1024:
4
+ return f'{memory_in_bytes} B'
5
+ return f'{memory_in_bytes / 1024:.0f} KiB'
6
+ return f'{memory_in_bytes / (1024 * 1024):.{mib_decimal_places}f} MiB'
7
+
8
+
9
+ def get_formatted_time(time_in_ms: int) -> str:
10
+ return f'{time_in_ms} ms'
rbx/box/generators.py CHANGED
@@ -47,11 +47,11 @@ def _get_group_output(
47
47
  return group_path / f'{subgroup_prefix}{i:03d}.out'
48
48
 
49
49
 
50
- def _fill_output_for_testcase(testcase: Testcase) -> Testcase:
50
+ def _fill_output_for_defined_testcase(testcase: Testcase) -> Testcase:
51
51
  res = testcase.model_copy()
52
52
  if res.outputPath is not None:
53
53
  return res
54
- output_path = res.inputPath.with_suffix('.out')
54
+ output_path = res.inputPath.with_suffix('.ans')
55
55
  if output_path.is_file():
56
56
  res.outputPath = output_path
57
57
  return res
@@ -61,7 +61,7 @@ def _copy_testcase_over(
61
61
  testcase: Testcase,
62
62
  dest: Testcase,
63
63
  ):
64
- testcase = _fill_output_for_testcase(testcase)
64
+ testcase = _fill_output_for_defined_testcase(testcase)
65
65
  dest.inputPath.parent.mkdir(parents=True, exist_ok=True)
66
66
  shutil.copy(
67
67
  str(testcase.inputPath),
@@ -195,7 +195,7 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
195
195
  pkg = package.find_problem_package_or_die()
196
196
 
197
197
  def _explore_subgroup(
198
- subgroup: TestcaseSubgroup, subgroup_index: int, prefix: List[str]
198
+ subgroup: TestcaseSubgroup, subgroup_index: Optional[int], prefix: List[str]
199
199
  ):
200
200
  assert prefix and len(prefix) >= 1 and len(prefix) <= 2
201
201
  group_path = prefix[0]
@@ -212,8 +212,10 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
212
212
  def _copied_to(i: int) -> Testcase:
213
213
  group_fs_path = package.get_build_testgroup_path(group_path)
214
214
  group_prefix = ''
215
+ if subgroup_index is not None:
216
+ group_prefix = f'{subgroup_index}-'
215
217
  if len(prefix) == 2:
216
- group_prefix = f'{subgroup_index}-{prefix[1]}-'
218
+ group_prefix += f'{prefix[1]}-'
217
219
  return Testcase(
218
220
  inputPath=_get_group_input(group_fs_path, group_prefix, i),
219
221
  outputPath=_get_group_output(group_fs_path, group_prefix, i),
@@ -228,7 +230,7 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
228
230
  group_entry=_entry(i),
229
231
  subgroup_entry=_sub_entry(i),
230
232
  metadata=GenerationMetadata(
231
- copied_from=_fill_output_for_testcase(tc),
233
+ copied_from=_fill_output_for_defined_testcase(tc),
232
234
  copied_to=_copied_to(i),
233
235
  ),
234
236
  )
@@ -249,7 +251,7 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
249
251
  group_entry=_entry(i),
250
252
  subgroup_entry=_sub_entry(i),
251
253
  metadata=GenerationMetadata(
252
- copied_from=_fill_output_for_testcase(tc),
254
+ copied_from=_fill_output_for_defined_testcase(tc),
253
255
  copied_to=_copied_to(i),
254
256
  ),
255
257
  )
@@ -296,10 +298,10 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
296
298
  if not visitor.should_visit_group(group.name):
297
299
  continue
298
300
 
299
- _explore_subgroup(group, 0, [group.name])
301
+ _explore_subgroup(group, 0 if group.subgroups else None, [group.name])
300
302
 
301
303
  for i, subgroup in enumerate(group.subgroups):
302
- _explore_subgroup(subgroup, i, [group.name, subgroup.name])
304
+ _explore_subgroup(subgroup, i + 1, [group.name, subgroup.name])
303
305
 
304
306
 
305
307
  def _get_necessary_generators_for_groups(
@@ -433,7 +435,7 @@ def generate_standalone(
433
435
  validator_digest,
434
436
  )
435
437
  if not ok:
436
- _print_error_header('Failed validating testcase.')
438
+ _print_error_header('failed validating testcase.')
437
439
  console.console.print(f'[error]Message:[/error] {message}')
438
440
  console.console.print(
439
441
  f'Testcase written at [item]{spec.copied_to.inputPath}[/item]'
@@ -19,7 +19,7 @@ def test_generator_works(pkg_from_testdata: pathlib.Path):
19
19
  print_directory_tree(pkg_from_testdata)
20
20
 
21
21
  assert (
22
- package.get_build_testgroup_path('gen1') / '0-main-000.in'
22
+ package.get_build_testgroup_path('gen1') / '0-000.in'
23
23
  ).read_text() == '777\n'
24
24
  assert (
25
25
  package.get_build_testgroup_path('gen1') / '1-gen-000.in'
rbx/box/main.py CHANGED
@@ -25,6 +25,7 @@ from rbx.box import (
25
25
  builder,
26
26
  cd,
27
27
  setter_config,
28
+ state,
28
29
  creation,
29
30
  download,
30
31
  environment,
@@ -83,6 +84,11 @@ app.add_typer(
83
84
  )
84
85
 
85
86
 
87
+ @app.callback()
88
+ def main():
89
+ state.STATE.run_through_cli = True
90
+
91
+
86
92
  # @app.command('ui', hidden=True)
87
93
  # @package.within_problem
88
94
  # def ui():
@@ -255,6 +261,7 @@ def _time_impl(check: bool, detailed: bool) -> Optional[int]:
255
261
  tracked_solutions=tracked_solutions,
256
262
  check=check,
257
263
  verification=VerificationLevel(verification),
264
+ timelimit_override=600, # 10 minute time limit for estimation
258
265
  )
259
266
 
260
267
  console.console.print()
@@ -352,6 +359,12 @@ def irun(
352
359
  '-t',
353
360
  help='Testcase to run, in the format "[group]/[index]". If not specified, will run interactively.',
354
361
  ),
362
+ output: bool = typer.Option(
363
+ False,
364
+ '--output',
365
+ '-o',
366
+ help='Whether to ask user for custom output.',
367
+ ),
355
368
  print: bool = typer.Option(
356
369
  False, '--print', '-p', help='Whether to print outputs to terminal.'
357
370
  ),
@@ -414,6 +427,7 @@ def irun(
414
427
  if generator is not None
415
428
  else None,
416
429
  testcase_entry=TestcaseEntry.parse(testcase) if testcase else None,
430
+ custom_output=output,
417
431
  print=print,
418
432
  sanitized=sanitized,
419
433
  )
rbx/box/schema.py CHANGED
@@ -156,11 +156,11 @@ class CodeItem(BaseModel):
156
156
  )
157
157
 
158
158
  language: Optional[str] = Field(
159
- None, description="""The language of the code file."""
159
+ default=None, description="""The language of the code file."""
160
160
  )
161
161
 
162
162
  compilationFiles: Optional[List[str]] = Field(
163
- [],
163
+ default=[],
164
164
  description="""
165
165
  Extra files that should be placed alongside the code file during its compilation,
166
166
  such as testlib.h, jngen.h, etc.
@@ -189,7 +189,7 @@ class GeneratorCall(BaseModel):
189
189
  name: str = FNameField(description='The name of the generator to call.')
190
190
 
191
191
  args: Optional[str] = Field(
192
- None, description='The arguments to pass to the generator.'
192
+ default=None, description='The arguments to pass to the generator.'
193
193
  )
194
194
 
195
195
 
@@ -199,31 +199,31 @@ class TestcaseSubgroup(BaseModel):
199
199
  name: str = NameField(description='The name of the test group.')
200
200
 
201
201
  testcases: List[Testcase] = Field(
202
- [],
202
+ default=[],
203
203
  description="""
204
204
  The path of testcases to add to this group,
205
205
  in the order they're defined.""",
206
206
  )
207
207
 
208
208
  testcaseGlob: Optional[str] = Field(
209
- None,
209
+ default=None,
210
210
  description="""
211
211
  A Python glob that matches input file paths relative to the
212
212
  package directory. The globbed files should end with the extension
213
213
  ".in", and their corresponding outputs, if defined, should have the same file name,
214
- but ending with ".out".
214
+ but ending with ".ans".
215
215
  """,
216
216
  )
217
217
 
218
218
  generators: List[GeneratorCall] = Field(
219
- [],
219
+ default=[],
220
220
  description="""
221
221
  A list of generators to call to generate testcases for this group.
222
222
  """,
223
223
  )
224
224
 
225
225
  generatorScript: Optional[CodeItem] = Field(
226
- None,
226
+ default=None,
227
227
  description="""
228
228
  A generator script to call to generate testcases for this group.
229
229
  """,
@@ -247,14 +247,14 @@ class TestcaseGroup(TestcaseSubgroup):
247
247
  model_config = ConfigDict(extra='forbid')
248
248
 
249
249
  subgroups: List[TestcaseSubgroup] = Field(
250
- [],
250
+ default=[],
251
251
  description="""
252
252
  A list of test subgroups to define for this group.
253
253
  """,
254
254
  )
255
255
 
256
256
  validator: Optional[CodeItem] = Field(
257
- None,
257
+ default=None,
258
258
  description="""
259
259
  A validator to use to validate the testcases of this group.
260
260
  If not specified, will use the package-level validator.
@@ -263,7 +263,7 @@ Useful in cases where the constraints vary across test groups.
263
263
  )
264
264
 
265
265
  weight: Optional[float] = Field(
266
- 1.0,
266
+ default=1.0,
267
267
  description="""
268
268
  The weight of this group in the final score. Useful for
269
269
  problems that have points.
@@ -301,29 +301,29 @@ class Stress(BaseModel):
301
301
 
302
302
  class Limits(BaseModel):
303
303
  time: Optional[int] = Field(
304
- None, description='Value to override time limit with, in milliseconds.'
304
+ default=None, description='Value to override time limit with, in milliseconds.'
305
305
  )
306
306
  memory: Optional[int] = Field(
307
- None, description='Value to override memory limit with, in MB.'
307
+ default=None, description='Value to override memory limit with, in MB.'
308
308
  )
309
309
  output: Optional[int] = Field(
310
- None, description='Value to override output limit with, in KB.'
310
+ default=None, description='Value to override output limit with, in KB.'
311
311
  )
312
312
 
313
313
  isDoubleTL: bool = Field(
314
- False, description='Whether to use double TL for this language.'
314
+ default=False, description='Whether to use double TL for this language.'
315
315
  )
316
316
 
317
317
 
318
318
  class LimitModifiers(BaseModel):
319
319
  timeMultiplier: Optional[float] = Field(
320
- None, description='Multiplier for time limit.'
320
+ default=None, description='Multiplier for time limit.'
321
321
  )
322
322
  time: Optional[int] = Field(
323
- None, description='Value to override time limit with, in milliseconds.'
323
+ default=None, description='Value to override time limit with, in milliseconds.'
324
324
  )
325
325
  memory: Optional[int] = Field(
326
- None, description='Value to override memory limit with, in MB.'
326
+ default=None, description='Value to override memory limit with, in MB.'
327
327
  )
328
328
 
329
329
 
@@ -338,28 +338,30 @@ class Package(BaseModel):
338
338
  memoryLimit: int = Field(description='Memory limit of the problem, in MB.')
339
339
 
340
340
  outputLimit: int = Field(
341
- 4 * 1024, description='Output limit of the problem, in KB.'
341
+ default=4 * 1024, description='Output limit of the problem, in KB.'
342
342
  )
343
343
 
344
344
  modifiers: Dict[str, LimitModifiers] = Field(
345
- {},
345
+ default={},
346
346
  description="""
347
347
  Limit modifiers that can be specified per language.
348
348
  """,
349
349
  )
350
350
 
351
351
  checker: Optional[CodeItem] = Field(
352
- None, description='The checker for this problem.'
352
+ default=None, description='The checker for this problem.'
353
353
  )
354
354
 
355
355
  validator: Optional[CodeItem] = Field(
356
- None, description='The validator for this problem.'
356
+ default=None, description='The validator for this problem.'
357
357
  )
358
358
 
359
- generators: List[Generator] = Field([], description='Generators for this problem.')
359
+ generators: List[Generator] = Field(
360
+ default=[], description='Generators for this problem.'
361
+ )
360
362
 
361
363
  solutions: List[Solution] = Field(
362
- [],
364
+ default=[],
363
365
  description="""
364
366
  All tested solutions for this problem.
365
367
 
@@ -368,17 +370,23 @@ that is correct and used as reference -- and should have the `accepted` outcome.
368
370
  """,
369
371
  )
370
372
 
371
- testcases: List[TestcaseGroup] = Field([], description='Testcases for the problem.')
373
+ testcases: List[TestcaseGroup] = Field(
374
+ default=[], description='Testcases for the problem.'
375
+ )
372
376
 
373
- stresses: List[Stress] = Field([], description='Stress tests for the problem.')
377
+ stresses: List[Stress] = Field(
378
+ default=[], description='Stress tests for the problem.'
379
+ )
374
380
 
375
- statements: List[Statement] = Field([], description='Statements for the problem.')
381
+ statements: List[Statement] = Field(
382
+ default=[], description='Statements for the problem.'
383
+ )
376
384
 
377
385
  # Vars to be re-used across the package.
378
386
  # - It will be passed as --key=value arguments to the validator.
379
387
  # - It will be available as \VAR{key} variables in the rbx statement.
380
388
  vars: Dict[str, Primitive] = Field(
381
- {}, description='Variables to be re-used across the package.'
389
+ default={}, description='Variables to be re-used across the package.'
382
390
  )
383
391
 
384
392
  @property
rbx/box/setter_config.py CHANGED
@@ -19,36 +19,36 @@ _CONFIG_FILE_NAME_MAC = 'default_setter_config.mac.yml'
19
19
 
20
20
  class SanitizersConfig(BaseModel):
21
21
  enabled: bool = Field(
22
- False,
22
+ default=False,
23
23
  description='Whether to use sanitizers when running solutions.',
24
24
  )
25
25
 
26
26
  command_substitutions: Dict[str, str] = Field(
27
- {},
27
+ default={},
28
28
  description='Substitutions to apply to commands before running them with sanitizers.',
29
29
  )
30
30
 
31
31
 
32
32
  class WarningsConfig(BaseModel):
33
33
  enabled: bool = Field(
34
- False,
34
+ default=False,
35
35
  description='Whether to use warning flags when running solutions.',
36
36
  )
37
37
 
38
38
 
39
39
  class RepeatsConfig(BaseModel):
40
40
  reps: int = Field(
41
- 1,
41
+ default=1,
42
42
  description='Number of times to repeat the solution.',
43
43
  )
44
44
 
45
45
  retries: int = Field(
46
- 0,
46
+ default=0,
47
47
  description='Number of times to retry if the solution TLs.',
48
48
  )
49
49
 
50
50
  retries_for_stress: int = Field(
51
- 0,
51
+ default=0,
52
52
  description='Number of times to retry in stress mode if the solution TLs.',
53
53
  )
54
54
 
@@ -69,7 +69,7 @@ class SetterConfig(BaseModel):
69
69
  )
70
70
 
71
71
  command_substitutions: Dict[str, str] = Field(
72
- {},
72
+ default={},
73
73
  description='Substitutions to apply to commands before running them.',
74
74
  )
75
75
 
rbx/box/solutions.py CHANGED
@@ -25,6 +25,7 @@ from rbx.box.environment import (
25
25
  ExecutionConfig,
26
26
  VerificationLevel,
27
27
  )
28
+ from rbx.box.formatting import get_formatted_memory, get_formatted_time
28
29
  from rbx.box.generators import (
29
30
  GenerationMetadata,
30
31
  expand_generator_call,
@@ -430,6 +431,7 @@ async def _generate_testcase_interactively(
430
431
  generator: Optional[GeneratorCall] = None,
431
432
  testcase_entry: Optional[TestcaseEntry] = None,
432
433
  check: bool = True,
434
+ custom_output: bool = False,
433
435
  sanitized: bool = False,
434
436
  print: bool = False,
435
437
  ) -> Testcase:
@@ -468,7 +470,7 @@ async def _generate_testcase_interactively(
468
470
  if (
469
471
  testcase.outputPath is not None
470
472
  and not testcase.outputPath.is_file()
471
- and main_solution is None
473
+ and (main_solution is None or custom_output)
472
474
  ):
473
475
  with utils.no_progress(progress):
474
476
  output = console.multiline_prompt('Testcase output')
@@ -604,6 +606,7 @@ async def run_and_print_interactive_solutions(
604
606
  generator: Optional[GeneratorCall] = None,
605
607
  testcase_entry: Optional[TestcaseEntry] = None,
606
608
  check: bool = True,
609
+ custom_output: bool = False,
607
610
  print: bool = False,
608
611
  sanitized: bool = False,
609
612
  ):
@@ -618,6 +621,7 @@ async def run_and_print_interactive_solutions(
618
621
  generator=generator,
619
622
  testcase_entry=testcase_entry,
620
623
  check=check,
624
+ custom_output=custom_output,
621
625
  sanitized=sanitized,
622
626
  print=print,
623
627
  )
@@ -729,10 +733,6 @@ def _get_evals_memory_in_bytes(evals: List[Evaluation]) -> int:
729
733
  return max(int(eval.log.memory or 0) for eval in evals)
730
734
 
731
735
 
732
- def get_formatted_time(time_in_ms: int) -> str:
733
- return f'{time_in_ms} ms'
734
-
735
-
736
736
  def get_evals_formatted_time(evals: List[Evaluation]) -> str:
737
737
  max_time = _get_evals_time_in_ms(evals)
738
738
  return get_formatted_time(max_time)
@@ -765,14 +765,6 @@ def get_capped_evals_formatted_time(
765
765
  return f'{max_time} ms'
766
766
 
767
767
 
768
- def get_formatted_memory(memory_in_bytes: int) -> str:
769
- if memory_in_bytes < 1024 * 1024:
770
- if memory_in_bytes < 1024:
771
- return f'{memory_in_bytes} B'
772
- return f'{memory_in_bytes // 1024} KiB'
773
- return f'{memory_in_bytes // (1024 * 1024)} MiB'
774
-
775
-
776
768
  def get_evals_formatted_memory(evals: List[Evaluation]) -> str:
777
769
  max_memory = _get_evals_memory_in_bytes(evals)
778
770
  return get_formatted_memory(max_memory)
@@ -906,33 +898,51 @@ def _print_solution_header(
906
898
  console.print(f'({solution_testdir})')
907
899
 
908
900
 
901
+ @dataclasses.dataclass
902
+ class TimingSummary:
903
+ slowest_good: Optional[int] = None
904
+ fastest_slow: Optional[int] = None
905
+
906
+ def add_good(self, time: int):
907
+ if self.slowest_good is None or time > self.slowest_good:
908
+ self.slowest_good = time
909
+
910
+ def add_slow(self, time: int):
911
+ if self.fastest_slow is None or time < self.fastest_slow:
912
+ self.fastest_slow = time
913
+
914
+
909
915
  async def _print_timing(
910
916
  console: rich.console.Console,
911
917
  skeleton: SolutionReportSkeleton,
912
918
  evaluations: StructuredEvaluation,
913
919
  ):
914
- slowest_good = None
915
- fastest_slow = None
920
+ summary = TimingSummary()
921
+ sumamry_per_language = collections.defaultdict(TimingSummary)
916
922
  for solution in skeleton.solutions:
917
923
  all_evals = []
918
924
  for evals in evaluations[str(solution.path)].values():
919
925
  all_evals.extend([await eval() for eval in evals if eval is not None])
920
926
  solution_time = _get_evals_time_in_ms(all_evals)
921
927
  if solution.outcome.match(Outcome.ACCEPTED):
922
- if slowest_good is None or solution_time > slowest_good:
923
- slowest_good = solution_time
928
+ summary.add_good(solution_time)
929
+ sumamry_per_language[solution.language].add_good(solution_time)
924
930
  if solution.outcome.is_slow():
925
- if fastest_slow is None or solution_time < fastest_slow:
926
- fastest_slow = solution_time
931
+ summary.add_slow(solution_time)
932
+ sumamry_per_language[solution.language].add_slow(solution_time)
927
933
 
928
- if slowest_good is None and fastest_slow is None:
934
+ if summary.slowest_good is None and summary.fastest_slow is None:
929
935
  return
930
936
 
931
937
  console.print('[status]Timing summary:[/status]')
932
- if slowest_good is not None:
933
- console.print(f'Slowest [success]OK[/success] solution: {slowest_good} ms')
934
- if fastest_slow is not None:
935
- console.print(f'Fastest [error]slow[/error] solution: {fastest_slow} ms')
938
+ if summary.slowest_good is not None:
939
+ console.print(
940
+ f'Slowest [success]OK[/success] solution: {summary.slowest_good} ms'
941
+ )
942
+ if summary.fastest_slow is not None:
943
+ console.print(
944
+ f'Fastest [error]slow[/error] solution: {summary.fastest_slow} ms'
945
+ )
936
946
 
937
947
 
938
948
  def _length_markup(markup: str) -> int:
rbx/box/state.py ADDED
@@ -0,0 +1,9 @@
1
+ import dataclasses
2
+
3
+
4
+ @dataclasses.dataclass
5
+ class State:
6
+ run_through_cli: bool = False
7
+
8
+
9
+ STATE = State()
@@ -104,7 +104,7 @@ class Statement(BaseModel):
104
104
  type: StatementType = Field(description='Type of the input statement file.')
105
105
 
106
106
  steps: List[ConversionStep] = Field(
107
- [],
107
+ default=[],
108
108
  discriminator='type',
109
109
  description="""
110
110
  Describes a sequence of conversion steps that should be applied to the statement file.
@@ -116,7 +116,7 @@ certain conversion steps to happen.
116
116
  )
117
117
 
118
118
  configure: List[ConversionStep] = Field(
119
- [],
119
+ default=[],
120
120
  discriminator='type',
121
121
  description="""
122
122
  Configure how certain conversion steps should happen when applied to the statement file.
@@ -127,7 +127,7 @@ configure them in case they are applied.
127
127
  )
128
128
 
129
129
  assets: List[str] = Field(
130
- [],
130
+ default=[],
131
131
  description="""
132
132
  Assets relative to the package directory that should be included while building
133
133
  the statement. Files will be included in the same folder as the statement file, preserving
@@ -135,4 +135,6 @@ their relativeness. Can be glob pattern as well, such as `imgs/*.png`.
135
135
  """,
136
136
  )
137
137
 
138
- language: str = Field('en', description='Language this is statement is written in.')
138
+ language: str = Field(
139
+ default='en', description='Language this is statement is written in.'
140
+ )
@@ -267,7 +267,6 @@ class StupidSandbox(SandboxBase):
267
267
  (caused by the sandbox itself), False otherwise
268
268
 
269
269
  """
270
-
271
270
  self.exec_num += 1
272
271
 
273
272
  logger.debug(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.29
3
+ Version: 0.5.31
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -5,7 +5,7 @@ rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  rbx/box/builder.py,sha256=eynVPyRdpYtSNmr8MP7-8jspNH74lj-DclwtiIcJrvM,3280
6
6
  rbx/box/cd.py,sha256=9a_SOnzoJBXxxffp4Wbf3UKXIwKuN3Hvj7K6SocALwE,1194
7
7
  rbx/box/checkers.py,sha256=VpgDzevOK7hrffG2zJGxquNiu-a9Fl3wquLn7xadcK0,6285
8
- rbx/box/code.py,sha256=sD3kpQaPU8_E5TJFjRL-tgGxz-qyC0oKsyykgQArbJA,11606
8
+ rbx/box/code.py,sha256=_tJExjZxByi_pnxOJkmyr2008lLH8-6rvGE0m85j3L0,13420
9
9
  rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
10
10
  rbx/box/conftest.py,sha256=sEmciXSeDC-wmrZ1JSxbsUenKNP_VWW32mrCun2pY3I,1070
11
11
  rbx/box/contest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -13,16 +13,17 @@ rbx/box/contest/build_contest_statements.py,sha256=OvKTE2O1UchiBRPzbU94KdIn0whT0
13
13
  rbx/box/contest/contest_package.py,sha256=OaUbpBtkhkgOPzJ1ccI_Vq4FMSaJvZm3gMOKfVY8oy4,3032
14
14
  rbx/box/contest/contest_utils.py,sha256=TDE7I6YQJlu4dQd68wzOp019bNgqiT0RlM-LMQMjL9w,301
15
15
  rbx/box/contest/main.py,sha256=fFYeZn8KTLt9j-OFFkcBeQWw8RsTLUauFV6drM2hT48,7404
16
- rbx/box/contest/schema.py,sha256=rxzjJasMWPKKhvSJs4eW4A2oCiA4gXgfF-MzqsbPslQ,4914
16
+ rbx/box/contest/schema.py,sha256=JMAig5WpaOahNgAHxA9vX4zYeVYDxpjKP_PFGvmmkE0,4954
17
17
  rbx/box/contest/statements.py,sha256=Pe4uo1hxvEON8O11VAzsOP3DxUel0vmwiAmolh4ltEs,2910
18
- rbx/box/creation.py,sha256=mVHVVj8ozX9D5qpkLewE5WSjF6HtTm74Pkwubk-bATg,2259
18
+ rbx/box/creation.py,sha256=YOWn0zUFvt3ABGIYdc-bn_49UIhBzcDMarL7yjI1hqc,2662
19
19
  rbx/box/deferred.py,sha256=II3X9e87JCOZtmspnHh-n4PFqh-FsH_oc0XJHZ9ZYVQ,691
20
20
  rbx/box/download.py,sha256=MFP-R26JiYGAP89I0TK-0fYc69Fsd20tsBqgtRCy5AE,2234
21
21
  rbx/box/environment.py,sha256=47NtyuVC6zSQKAtQaXPEXvqcD-KJiuWRpWF8pYvcG4c,11158
22
- rbx/box/extensions.py,sha256=gIC73VbF1897er3iIMhaIw6GE8o1t43M7q97Iz7-_lg,503
23
- rbx/box/generators.py,sha256=CVTFHlLton7puNxlfH1tTyOvpIQCb4H-KtmhUyl_si8,20440
24
- rbx/box/generators_test.py,sha256=mQqHepAMYa6zV_PseQALI0nIX6AdQktt6lh94muFhNw,1758
25
- rbx/box/main.py,sha256=ErfFSaMzXs12dmc2PazHYZlhGHjkAwdO1sLtsHz5taI,23428
22
+ rbx/box/extensions.py,sha256=Von8kIeXvNFTkGlMRMTvL2HIHPwlkuiMswr-ydbGV1w,519
23
+ rbx/box/formatting.py,sha256=3phFRHzqVXj4Ok1yDhCq6Clbw6KlqwJNpMhs--oTWFI,405
24
+ rbx/box/generators.py,sha256=XJsWXNjtQXUDX2huAUsl-q7OWFyIPc42CyS557jLYxk,20594
25
+ rbx/box/generators_test.py,sha256=BqWf-A0u0ifZJZlvrMuOHOHQPfMox2Jy6540GmnTnkc,1753
26
+ rbx/box/main.py,sha256=qSHoXBsG7nW9cXmnOm9afnGavIOZqvknu691YFACTxQ,23765
26
27
  rbx/box/package.py,sha256=SSckCXo7Zh412_qjYhSNwConjhR0Sk8911qGJU34Hac,11851
27
28
  rbx/box/packaging/boca/extension.py,sha256=hQhcbocNfW2ESv5RalS1wf6uvOoOfOnR_gHvbXUbSzY,852
28
29
  rbx/box/packaging/boca/packager.py,sha256=FOhSRg5K5Y4qNB0WyTR3DKgrpObf9I0JbyGpJHOtxpo,10673
@@ -38,17 +39,18 @@ rbx/box/presets/lock_schema.py,sha256=6sRPnyePOC8yy-5WcD5JRZdDJHf8loqbvpQ1IPiOU9
38
39
  rbx/box/presets/schema.py,sha256=mZmSPkQsw7eQM0lQN6er1MO_LiW1ObwwAZFDK0F5fxE,1962
39
40
  rbx/box/retries.py,sha256=z7cIh1QmLVUsTr3Attt_28dbwNg6KWTwpulcWCFwMPo,4667
40
41
  rbx/box/sanitizers/warning_stack.py,sha256=RI97_GJgdjTKIXY_r0EKp5h0qQQSDSdNDh5K7zINrqs,2861
41
- rbx/box/schema.py,sha256=1A-NqOKkXY4QWIwwkWuKQgTR2nJVV_JYEfXmVE9d470,14035
42
- rbx/box/setter_config.py,sha256=sg38rzTuT0RNANNV558cSCORvovOK6EIz_ioVzaTkGI,4223
43
- rbx/box/solutions.py,sha256=AkvAJ2msVj-FHVfT3XwTFrvMXwJEGV_SF3imnvARogg,41871
42
+ rbx/box/schema.py,sha256=iu6S2A2d_7SAQNFd34galQHxFAI2CQrIgXnSBlKgBCk,14307
43
+ rbx/box/setter_config.py,sha256=ZM7_G2tbaixaFr0NvRaXkowwfxSWF2Gb4XHBsr2Prpc,4279
44
+ rbx/box/solutions.py,sha256=A-VVxjTrQ2OuBk_2WynoK0kS5BtsEjkRWzBcx92N6Mk,42266
44
45
  rbx/box/solutions_test.py,sha256=Cx7Goon_0sz_PaUcD8qa8gmjgzOVub6VHss3CB0GaA0,1524
46
+ rbx/box/state.py,sha256=yTpjfASpnSXkRB3JiDNvCg5b9JNnNxuYT4uMcbdr59s,109
45
47
  rbx/box/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
48
  rbx/box/statements/build_statements.py,sha256=upsMT-cAnSvbmKgtijdFc0OxPcyeBxRG92hY6dN-ZOk,11920
47
49
  rbx/box/statements/builders.py,sha256=W3VkmtjfzrT5MkIVXgfR9fM-OWK007ihm5hfzvp9cfc,10474
48
50
  rbx/box/statements/joiners.py,sha256=ZbxomnMjEFT8yf5WSWUB4tBa3DL3AhjGEuh8uqHyDdg,2837
49
51
  rbx/box/statements/latex.py,sha256=LkcHwXjMFxbw--Gj9T1VkFKQFsXhY9dN7xZHpZycNW8,1346
50
52
  rbx/box/statements/latex_jinja.py,sha256=7WBfn1h8DpqCAmSE6Av64HfURMnJ2AO4QX1CD72sz5E,7096
51
- rbx/box/statements/schema.py,sha256=g3KgBn4nIqx-0utH8R2FCqPmJP969chhYfn96chQgd4,3851
53
+ rbx/box/statements/schema.py,sha256=ES8EUE9JE_uJlDwQx1kZd_5nQJyABtlnjP5IjbWaJ-0,3897
52
54
  rbx/box/stresses.py,sha256=ceFpkZVKBfKKVrKFjeARdub5VGKmU9JPZwj-FxcqYjQ,11771
53
55
  rbx/box/stressing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
56
  rbx/box/stressing/finder_parser.py,sha256=jXpYNa4FyugzmHi3r96Uv4rU1krRQJc5Ihr9jf1cvNo,11918
@@ -77,7 +79,7 @@ rbx/grading/judge/digester.py,sha256=m6o-kjwyFOXKdImUXtVbdMHhwrgrXk8FDnJFVefnTIw
77
79
  rbx/grading/judge/sandbox.py,sha256=0h3YCmGabf9OfORJgx6v2Bed4kE-i8FyuZkPux-sDVk,23569
78
80
  rbx/grading/judge/sandboxes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
81
  rbx/grading/judge/sandboxes/isolate.py,sha256=9xgBuNfAvGtO2zME1FXRah2rcPvzDShsPG0TTuX_UDU,25649
80
- rbx/grading/judge/sandboxes/stupid_sandbox.py,sha256=2rFinUSafape4ad_r0TXBX0ZwnAfuVgEnDviozXbYlo,10155
82
+ rbx/grading/judge/sandboxes/stupid_sandbox.py,sha256=NKSJWw_1HALjwGVIYQr_R1-s1tb6I25lzDLnPPsYANg,10154
81
83
  rbx/grading/judge/sandboxes/timeit.py,sha256=xScfasI2lsSQGZVpIZ7qBZfi0IaKC-1k8wO5qFp7UoM,6634
82
84
  rbx/grading/judge/storage.py,sha256=FirqjwDqb0m0h2OTFyWrZL7CQ4XjZNxhqB4JpnDIhZY,9485
83
85
  rbx/grading/judge/test.py,sha256=ll0Iw7zyOpGdKPD_PGH7dvUkb4stQLu-ikbQnqJvuAc,944
@@ -163,8 +165,8 @@ rbx/testdata/caching/executable.py,sha256=WKRHNf_fprFJd1Fq1ubmQtR3mZzTYVNwKPLWuZ
163
165
  rbx/testdata/compatible,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
166
  rbx/testing_utils.py,sha256=ZZLKMUHlZ4HwsuNY50jqSBJ9HhpnFdba7opjDsvXE1U,2084
165
167
  rbx/utils.py,sha256=q1ZmfVCD6rdKVVZFBqwVetldSgGAbIh_KLHseBTUSiQ,4511
166
- rbx_cp-0.5.29.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
167
- rbx_cp-0.5.29.dist-info/METADATA,sha256=tU0tBGxpQHo5ixeqzCwUZUK3q0JDhoMMv7MUVqPntLM,3212
168
- rbx_cp-0.5.29.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
- rbx_cp-0.5.29.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
170
- rbx_cp-0.5.29.dist-info/RECORD,,
168
+ rbx_cp-0.5.31.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
169
+ rbx_cp-0.5.31.dist-info/METADATA,sha256=RQ-vCGczePo4_Nqvi4XUkF3iRKN7kFpyhOGOOoj-LB0,3212
170
+ rbx_cp-0.5.31.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
171
+ rbx_cp-0.5.31.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
172
+ rbx_cp-0.5.31.dist-info/RECORD,,