rbx.cp 0.8.0__py3-none-any.whl → 0.9.1__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.
Files changed (55) hide show
  1. rbx/box/cd.py +2 -2
  2. rbx/box/cli.py +11 -2
  3. rbx/box/code.py +2 -2
  4. rbx/box/contest/build_contest_statements.py +2 -2
  5. rbx/box/contest/contest_package.py +1 -1
  6. rbx/box/contest/main.py +29 -2
  7. rbx/box/environment.py +143 -83
  8. rbx/box/formatting.py +2 -1
  9. rbx/box/package.py +5 -5
  10. rbx/box/packaging/__init__.py +0 -0
  11. rbx/box/packaging/boca/__init__.py +0 -0
  12. rbx/box/packaging/polygon/packager.py +3 -3
  13. rbx/box/presets/__init__.py +369 -53
  14. rbx/box/presets/lock_schema.py +42 -2
  15. rbx/box/presets/schema.py +4 -0
  16. rbx/box/remote.py +3 -3
  17. rbx/box/retries.py +3 -2
  18. rbx/box/sanitizers/warning_stack.py +2 -2
  19. rbx/box/setter_config.py +18 -0
  20. rbx/box/solutions.py +24 -18
  21. rbx/box/statements/build_statements.py +6 -6
  22. rbx/box/statements/builders.py +1 -1
  23. rbx/box/stresses.py +2 -2
  24. rbx/box/testcase_utils.py +3 -3
  25. rbx/config.py +7 -0
  26. rbx/grading/caching.py +24 -2
  27. rbx/grading/conftest.py +2 -2
  28. rbx/grading/grading_context.py +25 -0
  29. rbx/grading/judge/sandbox.py +2 -1
  30. rbx/grading/judge/sandboxes/isolate.py +3 -2
  31. rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
  32. rbx/grading/judge/storage.py +2 -1
  33. rbx/grading/steps.py +2 -1
  34. rbx/grading/steps_with_caching_run_test.py +281 -5
  35. rbx/resources/default_setter_config.mac.yml +15 -0
  36. rbx/resources/default_setter_config.yml +15 -0
  37. rbx/resources/envs/default.rbx.yml +43 -13
  38. rbx/resources/envs/isolate.rbx.yml +2 -3
  39. rbx/resources/presets/default/contest/.gitignore +5 -1
  40. rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -1
  41. rbx/resources/presets/default/env.rbx.yml +67 -0
  42. rbx/resources/presets/default/preset.rbx.yml +6 -2
  43. rbx/resources/presets/default/problem/.gitignore +1 -1
  44. rbx/resources/presets/default/{contest/statement/template.rbx.tex → shared/problem_template.rbx.tex} +13 -7
  45. rbx/submitors/codeforces.py +3 -2
  46. rbx/test.py +1 -1
  47. rbx/utils.py +6 -1
  48. {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/METADATA +2 -1
  49. {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/RECORD +54 -52
  50. rbx/resources/presets/default/problem/statement/icpc.sty +0 -322
  51. /rbx/resources/presets/default/{problem/statement/template.rbx.tex → shared/contest_template.rbx.tex} +0 -0
  52. /rbx/resources/presets/default/{contest/statement → shared}/icpc.sty +0 -0
  53. {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/LICENSE +0 -0
  54. {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/WHEEL +0 -0
  55. {rbx_cp-0.8.0.dist-info → rbx_cp-0.9.1.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union
5
5
 
6
6
  import typer
7
7
 
8
- from rbx import console
8
+ from rbx import console, utils
9
9
  from rbx.box import cd, package
10
10
  from rbx.box.formatting import href, ref
11
11
 
@@ -85,7 +85,7 @@ REGISTERED_EXPANDERS: List['Expander'] = [
85
85
 
86
86
 
87
87
  def _relative_to_pkg(path: pathlib.Path) -> pathlib.Path:
88
- return path.resolve().relative_to(pathlib.Path.cwd())
88
+ return utils.abspath(path).relative_to(pathlib.Path.cwd())
89
89
 
90
90
 
91
91
  def _try_cacheable_paths(
@@ -167,4 +167,4 @@ def expand_file(file: str) -> pathlib.Path:
167
167
 
168
168
  def is_path_remote(path: pathlib.Path) -> bool:
169
169
  remote_dir = package.get_problem_remote_dir()
170
- return path.resolve().is_relative_to(remote_dir.resolve())
170
+ return utils.abspath(path).is_relative_to(utils.abspath(remote_dir))
rbx/box/retries.py CHANGED
@@ -5,6 +5,7 @@ import tempfile
5
5
  from contextlib import contextmanager
6
6
  from typing import Awaitable, Callable, List, Optional
7
7
 
8
+ from rbx import utils
8
9
  from rbx.box import package
9
10
  from rbx.box.setter_config import RepeatsConfig, get_setter_config
10
11
  from rbx.grading.steps import Evaluation, Outcome
@@ -59,8 +60,8 @@ class FileToRecover:
59
60
 
60
61
  def _move_to_temp_dir(path: pathlib.Path, temp_dir: pathlib.Path) -> FileToRecover:
61
62
  problem_path = package.find_problem()
62
- path = path.resolve()
63
- temp_dir = temp_dir.resolve()
63
+ path = utils.abspath(path)
64
+ temp_dir = utils.abspath(temp_dir)
64
65
  relative = path.relative_to(problem_path)
65
66
 
66
67
  temp_path = temp_dir / relative
@@ -2,7 +2,7 @@ import functools
2
2
  import pathlib
3
3
  import shutil
4
4
 
5
- from rbx import console
5
+ from rbx import console, utils
6
6
  from rbx.box.schema import CodeItem
7
7
  from rbx.grading.judge.cacher import FileCacher
8
8
  from rbx.grading.steps import GradingFileOutput
@@ -60,7 +60,7 @@ def _get_warning_runs_dir(root: pathlib.Path) -> pathlib.Path:
60
60
 
61
61
 
62
62
  def get_warning_stack() -> WarningStack:
63
- current_root = pathlib.Path.cwd().resolve()
63
+ current_root = utils.abspath(pathlib.Path.cwd())
64
64
  return _get_warning_stack(current_root)
65
65
 
66
66
 
rbx/box/setter_config.py CHANGED
@@ -9,6 +9,7 @@ import typer
9
9
  from pydantic import BaseModel, Field
10
10
 
11
11
  from rbx import config, console, utils
12
+ from rbx.grading.grading_context import CacheLevel
12
13
 
13
14
  app = typer.Typer(no_args_is_help=True)
14
15
 
@@ -52,6 +53,19 @@ class RepeatsConfig(BaseModel):
52
53
  )
53
54
 
54
55
 
56
+ class CachingConfig(BaseModel):
57
+ level: CacheLevel = Field(
58
+ default=CacheLevel.CACHE_ALL,
59
+ description='Whether to enable caching and which caching level to use.',
60
+ )
61
+
62
+ check_integrity: bool = Field(
63
+ default=True,
64
+ description='Whether to check the integrity of the cached result, and evict it'
65
+ 'if file has changed since it was cached.',
66
+ )
67
+
68
+
55
69
  class SetterConfig(BaseModel):
56
70
  sanitizers: SanitizersConfig = Field(
57
71
  default_factory=SanitizersConfig, # type: ignore
@@ -75,6 +89,10 @@ class SetterConfig(BaseModel):
75
89
  default=True,
76
90
  description='Whether to use hyperlinks in the terminal output.',
77
91
  )
92
+ caching: CachingConfig = Field(
93
+ default_factory=CachingConfig, # type: ignore
94
+ description='Configuration for caching.',
95
+ )
78
96
 
79
97
  def substitute_command(self, command: str, sanitized: bool = False) -> str:
80
98
  exe = shlex.split(command)[0]
rbx/box/solutions.py CHANGED
@@ -54,6 +54,7 @@ from rbx.box.testcase_utils import (
54
54
  parse_interaction,
55
55
  print_interaction,
56
56
  )
57
+ from rbx.grading import grading_context
57
58
  from rbx.grading.limits import Limits
58
59
  from rbx.grading.steps import (
59
60
  Evaluation,
@@ -498,7 +499,7 @@ async def _generate_testcase_interactively(
498
499
  console.console.print(testcase.inputPath.read_text())
499
500
  else:
500
501
  console.console.print(
501
- f'Input was written to [item]{testcase.inputPath.resolve()}[/item]'
502
+ f'Input was written to [item]{utils.abspath(testcase.inputPath)}[/item]'
502
503
  )
503
504
  console.console.print()
504
505
 
@@ -660,23 +661,28 @@ async def run_and_print_interactive_solutions(
660
661
  tracked_solutions,
661
662
  verification=verification,
662
663
  )
663
- testcase = await _generate_testcase_interactively(
664
- progress=progress,
665
- generator=generator,
666
- testcase_entry=testcase_entry,
667
- check=check,
668
- custom_output=custom_output,
669
- sanitized=sanitized,
670
- print=print,
671
- )
672
- items = _run_interactive_solutions(
673
- testcase,
674
- skeleton=skeleton,
675
- progress=progress,
676
- verification=verification,
677
- check=check,
678
- sanitized=sanitized,
679
- )
664
+
665
+ should_cache = testcase_entry is not None
666
+ with grading_context.cache_level(
667
+ grading_context.CacheLevel.CACHE_COMPILATION, when=not should_cache
668
+ ):
669
+ testcase = await _generate_testcase_interactively(
670
+ progress=progress,
671
+ generator=generator,
672
+ testcase_entry=testcase_entry,
673
+ check=check,
674
+ custom_output=custom_output,
675
+ sanitized=sanitized,
676
+ print=print,
677
+ )
678
+ items = _run_interactive_solutions(
679
+ testcase,
680
+ skeleton=skeleton,
681
+ progress=progress,
682
+ verification=verification,
683
+ check=check,
684
+ sanitized=sanitized,
685
+ )
680
686
 
681
687
  for item in items:
682
688
  sol = skeleton.find_solution_skeleton(item.solution)
@@ -6,7 +6,7 @@ from typing import Annotated, Any, Dict, List, Optional, Tuple
6
6
  import syncer
7
7
  import typer
8
8
 
9
- from rbx import annotations, console
9
+ from rbx import annotations, console, utils
10
10
  from rbx.box import environment, naming, package
11
11
  from rbx.box.formatting import href
12
12
  from rbx.box.schema import Package, expand_any_vars
@@ -46,7 +46,7 @@ def get_environment_languages_for_statement() -> List[StatementCodeLanguage]:
46
46
  res.append(
47
47
  StatementCodeLanguage(
48
48
  id=language.name,
49
- name=language.readable_name or language.name,
49
+ name=language.readableName or language.name,
50
50
  command=cmd or '',
51
51
  )
52
52
  )
@@ -173,7 +173,7 @@ def get_relative_assets(
173
173
  relative_to: pathlib.Path,
174
174
  assets: List[str],
175
175
  ) -> List[Tuple[pathlib.Path, pathlib.Path]]:
176
- relative_to = relative_to.resolve()
176
+ relative_to = utils.abspath(relative_to)
177
177
  if not relative_to.is_dir():
178
178
  relative_to = relative_to.parent
179
179
  res = []
@@ -192,7 +192,7 @@ def get_relative_assets(
192
192
  raise typer.Exit(1)
193
193
  res.extend(get_relative_assets(relative_to, list(map(str, globbed))))
194
194
  continue
195
- if not relative_path.resolve().is_relative_to(relative_to):
195
+ if not utils.abspath(relative_path).is_relative_to(relative_to):
196
196
  console.console.print(
197
197
  f'[error]Asset [item]{asset}[/item] is not relative to your statement.[/error]'
198
198
  )
@@ -200,8 +200,8 @@ def get_relative_assets(
200
200
 
201
201
  res.append(
202
202
  (
203
- relative_path.resolve(),
204
- relative_path.resolve().relative_to(relative_to),
203
+ utils.abspath(relative_path),
204
+ utils.abspath(relative_path).relative_to(relative_to),
205
205
  )
206
206
  )
207
207
 
@@ -319,7 +319,7 @@ class rbxTeXBuilder(StatementBuilder):
319
319
  params = typing.cast(rbxToTeX, params)
320
320
  if not params.template:
321
321
  return []
322
- return [((root / params.template).resolve(), params.template)]
322
+ return [(utils.abspath(root / params.template), params.template)]
323
323
 
324
324
  def build(
325
325
  self,
rbx/box/stresses.py CHANGED
@@ -9,7 +9,7 @@ import syncer
9
9
  import typer
10
10
  from pydantic import BaseModel
11
11
 
12
- from rbx import console
12
+ from rbx import console, utils
13
13
  from rbx.box import checkers, generators, package, tasks, validators
14
14
  from rbx.box.code import SanitizationLevel, compile_item
15
15
  from rbx.box.generators import (
@@ -311,7 +311,7 @@ def print_stress_report(report: StressReport):
311
311
  console.console.print(f'Found [item]{len(report.findings)}[/item] testcases.')
312
312
 
313
313
  findings_dir = package.get_problem_runs_dir() / '.stress' / 'findings'
314
- console.console.print(f'Findings: {findings_dir.resolve()}')
314
+ console.console.print(f'Findings: {utils.abspath(findings_dir)}')
315
315
  console.console.print()
316
316
 
317
317
  for i, finding in enumerate(report.findings):
rbx/box/testcase_utils.py CHANGED
@@ -7,7 +7,7 @@ import rich.text
7
7
  import typer
8
8
  from pydantic import BaseModel
9
9
 
10
- from rbx import console
10
+ from rbx import console, utils
11
11
  from rbx.box import package
12
12
  from rbx.box.package import get_build_testgroup_path, get_build_tests_path
13
13
  from rbx.box.schema import Testcase, TestcaseGroup
@@ -141,8 +141,8 @@ def get_samples() -> List[Testcase]:
141
141
  tcs = find_built_testcases(package.get_testgroup('samples'))
142
142
  return [
143
143
  Testcase(
144
- inputPath=tc.inputPath.resolve(),
145
- outputPath=tc.outputPath.resolve()
144
+ inputPath=utils.abspath(tc.inputPath),
145
+ outputPath=utils.abspath(tc.outputPath)
146
146
  if tc.outputPath is not None and tc.outputPath.is_file()
147
147
  else None,
148
148
  )
rbx/config.py CHANGED
@@ -90,6 +90,13 @@ def get_empty_app_persist_path() -> pathlib.Path:
90
90
  return app_dir
91
91
 
92
92
 
93
+ def get_resources_file(path: pathlib.Path) -> pathlib.Path:
94
+ file_path = importlib.resources.files('rbx') / 'resources' / path # type: ignore
95
+ if file_path.is_file():
96
+ return file_path
97
+ raise FileNotFoundError(f'File {path} not found in {_RESOURCES_PKG}.')
98
+
99
+
93
100
  def get_app_file(path: pathlib.Path) -> pathlib.Path:
94
101
  file_path = get_app_path() / path
95
102
  if file_path.is_file():
rbx/grading/caching.py CHANGED
@@ -15,7 +15,12 @@ from rbx.grading.judge.cacher import FileCacher
15
15
  from rbx.grading.judge.digester import digest_cooperatively
16
16
  from rbx.grading.judge.storage import copyfileobj
17
17
  from rbx.grading.profiling import Profiler
18
- from rbx.grading.steps import DigestHolder, GradingArtifacts, GradingLogsHolder
18
+ from rbx.grading.steps import (
19
+ DigestHolder,
20
+ GradingArtifacts,
21
+ GradingFileOutput,
22
+ GradingLogsHolder,
23
+ )
19
24
 
20
25
  VERBOSE = False
21
26
 
@@ -109,10 +114,27 @@ def _build_fingerprint_list(
109
114
  return fingerprints
110
115
 
111
116
 
117
+ def _maybe_check_integrity(output: GradingFileOutput):
118
+ if not grading_context.should_check_integrity():
119
+ return
120
+ if output.dest is None or not output.dest.is_symlink():
121
+ return
122
+ if output.digest is None or output.digest.value is None:
123
+ return
124
+ with output.dest.open('rb') as f:
125
+ fingerprint = digest_cooperatively(f)
126
+ if fingerprint != output.digest.value:
127
+ raise ValueError(
128
+ f'Cache was tampered with, file {output.dest} has changed since it was cached.\nPlease run `rbx clean` to reset the cache.'
129
+ )
130
+
131
+
112
132
  def _build_output_fingerprint_list(artifacts_list: List[GradingArtifacts]) -> List[str]:
113
133
  fingerprints = []
114
134
  for artifacts in artifacts_list:
115
135
  for output in artifacts.outputs:
136
+ if output.hash:
137
+ _maybe_check_integrity(output)
116
138
  if output.dest is None or output.intermediate or output.hash:
117
139
  continue
118
140
  if not output.dest.is_file():
@@ -313,7 +335,7 @@ class DependencyCache:
313
335
  self.cacher = cacher
314
336
  self.db = shelve.open(self._cache_name())
315
337
  tmp_dir = pathlib.Path(tempfile.mkdtemp())
316
- self.transient_db = shelve.open(tmp_dir / '.cache_db')
338
+ self.transient_db = shelve.open(str(tmp_dir / '.cache_db'))
317
339
  atexit.register(lambda: self.db.close())
318
340
  atexit.register(lambda: self.transient_db.close())
319
341
  atexit.register(lambda: shutil.rmtree(tmp_dir))
rbx/grading/conftest.py CHANGED
@@ -28,6 +28,6 @@ def sandbox(request, file_cacher: FileCacher) -> Iterator[SandboxBase]:
28
28
 
29
29
  @pytest.fixture
30
30
  def dependency_cache(
31
- request, cleandir: pathlib.Path, storage: Storage
31
+ request, cleandir: pathlib.Path, file_cacher: FileCacher
32
32
  ) -> Iterator[DependencyCache]:
33
- yield DependencyCache(cleandir / '.box', storage)
33
+ yield DependencyCache(cleandir / '.box', file_cacher)
@@ -94,3 +94,28 @@ class compression(ConditionedContext):
94
94
  if self.use_compression_token is not None:
95
95
  use_compression_var.reset(self.use_compression_token)
96
96
  return None
97
+
98
+
99
+ check_integrity_var = contextvars.ContextVar('check_integrity', default=True)
100
+
101
+
102
+ def should_check_integrity() -> bool:
103
+ return check_integrity_var.get()
104
+
105
+
106
+ class check_integrity(ConditionedContext):
107
+ def __init__(self, enabled: bool, when: Condition = True):
108
+ super().__init__(when)
109
+ self.enabled = enabled
110
+ self.token = None
111
+
112
+ def __enter__(self):
113
+ if not self.should_enter():
114
+ return self
115
+ self.token = check_integrity_var.set(self.enabled)
116
+ return self
117
+
118
+ def __exit__(self, exc_type, exc_val, exc_tb):
119
+ if self.token is not None:
120
+ check_integrity_var.reset(self.token)
121
+ return None
@@ -16,6 +16,7 @@ from typing import IO, Any, Dict, List, Optional
16
16
 
17
17
  import pydantic
18
18
 
19
+ from rbx import utils
19
20
  from rbx.grading.judge import cacher, storage
20
21
 
21
22
  logger = logging.getLogger(__name__)
@@ -468,7 +469,7 @@ class SandboxBase(abc.ABC):
468
469
  if override:
469
470
  real_path.unlink(missing_ok=True)
470
471
  try:
471
- real_path.symlink_to(from_path.resolve())
472
+ real_path.symlink_to(utils.abspath(from_path))
472
473
  except NotImplementedError:
473
474
  return None
474
475
  return real_path
@@ -9,6 +9,7 @@ import subprocess
9
9
  import tempfile
10
10
  from typing import IO, Any, Dict, List, Optional
11
11
 
12
+ from rbx import utils
12
13
  from rbx.config import get_app_path
13
14
  from rbx.grading.judge.cacher import FileCacher
14
15
  from rbx.grading.judge.sandbox import (
@@ -180,10 +181,10 @@ class IsolateSandbox(SandboxBase):
180
181
  """
181
182
  outer_paths: List[pathlib.Path] = []
182
183
  for inner_path in inner_paths:
183
- abs_inner_path = (self._home_dest / inner_path).resolve()
184
+ abs_inner_path = utils.abspath(self._home_dest / inner_path)
184
185
  # If an inner path is absolute (e.g., /fifo0/u0_to_m) then
185
186
  # it may be outside home and we should ignore it.
186
- if not abs_inner_path.is_relative_to(self._home_dest.resolve()):
187
+ if not abs_inner_path.is_relative_to(utils.abspath(self._home_dest)):
187
188
  continue
188
189
  rel_inner_path = abs_inner_path.relative_to(self._home_dest)
189
190
  outer_path = self._home / rel_inner_path
@@ -11,6 +11,7 @@ import sys
11
11
  import tempfile
12
12
  from typing import Any, Dict, List, Optional
13
13
 
14
+ from rbx import utils
14
15
  from rbx.grading.judge.cacher import FileCacher
15
16
  from rbx.grading.judge.sandbox import (
16
17
  SandboxBase,
@@ -303,8 +304,8 @@ class StupidSandbox(SandboxBase):
303
304
  real_command = (
304
305
  [
305
306
  sys.executable,
306
- str(self.get_timeit_executable().resolve()),
307
- str(self.relative_path(self.get_current_log_name()).resolve()),
307
+ str(utils.abspath(self.get_timeit_executable())),
308
+ str(utils.abspath(self.relative_path(self.get_current_log_name()))),
308
309
  ]
309
310
  + self.get_timeit_args()
310
311
  + command
@@ -10,6 +10,7 @@ from typing import IO, AnyStr, Dict, List, Optional, Type, TypeVar
10
10
  import lz4.frame
11
11
  from pydantic import BaseModel
12
12
 
13
+ from rbx import utils
13
14
  from rbx.grading import grading_context
14
15
 
15
16
  logger = logging.getLogger(__name__)
@@ -408,7 +409,7 @@ class FilesystemStorage(Storage):
408
409
  def filename_from_symlink(self, link: pathlib.Path) -> Optional[str]:
409
410
  if not link.is_symlink():
410
411
  return None
411
- filename = link.readlink().resolve()
412
+ filename = utils.abspath(link.readlink())
412
413
  if not filename.is_file():
413
414
  return None
414
415
  return str(filename.relative_to(self.path))
rbx/grading/steps.py CHANGED
@@ -523,7 +523,8 @@ def _get_system_bits_stdcpp(command: str) -> Optional[GradingFileInput]:
523
523
  if not bits_candidate.is_file():
524
524
  continue
525
525
  return GradingFileInput(
526
- src=bits_candidate.resolve().absolute(), dest=pathlib.Path('bits/stdc++.h')
526
+ src=utils.abspath(bits_candidate),
527
+ dest=pathlib.Path('bits/stdc++.h'),
527
528
  )
528
529
  return None
529
530