rbx.cp 0.5.52__py3-none-any.whl → 0.5.54__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/checkers.py CHANGED
@@ -226,6 +226,10 @@ def _check_sanitizer_warnings(run_log: Optional[RunLog]) -> bool:
226
226
  return run_log.warnings
227
227
 
228
228
 
229
+ def _is_testlib_eof(stderr: str) -> bool:
230
+ return 'wrong output format Unexpected end of file' in stderr
231
+
232
+
229
233
  async def check(
230
234
  checker_digest: str,
231
235
  run_log: Optional[RunLog],
@@ -283,15 +287,21 @@ async def check_communication(
283
287
  # interactor exited before it. Thus, check the interactor, as it might have
284
288
  # returned a checker verdict.
285
289
  #
286
- # Also, do the same if the solution returned a non-zero exit code. Checker verdict
287
- # should usually supersede a solution's RTE verdict.
290
+ # Also, treat the case where the solution has a non-zero exit code similarly.
291
+ # This is aligned with what Polygon/Codeforces does: for some languages, we don't
292
+ # get a SIGPIPE. Instead, we get a non-zero exit code which we can't distinguish
293
+ # from a normal RTE. Thus, we decide that we should prioritize the interactor verdict
294
+ # over the solution's exit code in these cases.
288
295
  if (
289
296
  interactor_run_log is not None
290
297
  and run_log is not None
291
298
  and (
292
299
  run_log.exitcode == -signal.SIGPIPE
293
300
  or run_log.exitstatus == SandboxBase.EXIT_TERMINATED
294
- or run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
301
+ or (
302
+ run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
303
+ and not _is_testlib_eof(interactor_stderr.read_text())
304
+ )
295
305
  )
296
306
  ):
297
307
  result = _check_interactor()
@@ -0,0 +1,18 @@
1
+ import pathlib
2
+
3
+ import mkdocs_gen_files
4
+
5
+ from rbx.box.contest.schema import Contest
6
+ from rbx.box.environment import Environment
7
+ from rbx.box.package import Package
8
+ from rbx.box.presets.lock_schema import PresetLock
9
+ from rbx.box.presets.schema import Preset
10
+ from rbx.box.statements.schema import Statement
11
+ from rbx.utils import dump_schema_str
12
+
13
+ models = [Package, Environment, Contest, Preset, PresetLock, Statement]
14
+
15
+ for model in models:
16
+ path = pathlib.Path('schemas') / f'{model.__name__}.json'
17
+ with mkdocs_gen_files.open(str(path), 'w') as f:
18
+ f.write(dump_schema_str(model))
@@ -250,6 +250,24 @@ def _get_statement_blocks(statement: Statement) -> StatementBlocks:
250
250
  )
251
251
 
252
252
 
253
+ def _get_explanations(explanations: Dict[int, str]) -> str:
254
+ entries = []
255
+ for i in sorted(explanations):
256
+ explanation = explanations[i]
257
+ entries.append(f'\\textbf{{Explanation for example {i + 1}}}\n\n{explanation}')
258
+ return '\n\n'.join(entries)
259
+
260
+
261
+ def _get_notes_with_explanations(blocks: StatementBlocks) -> Optional[str]:
262
+ notes = blocks.blocks.get('notes')
263
+ explanations = blocks.explanations
264
+ if notes is None and not explanations:
265
+ return None
266
+ if notes is None:
267
+ return _get_explanations(blocks.explanations)
268
+ return notes + '\n\n' + _get_explanations(blocks.explanations)
269
+
270
+
253
271
  def _upload_statement_resources(problem: api.Problem, statement: Statement):
254
272
  assets = get_relative_assets(statement.path, statement.assets)
255
273
  for asset, relative_asset in assets:
@@ -293,7 +311,7 @@ def _upload_statement(problem: api.Problem):
293
311
  input=blocks.blocks.get('input'),
294
312
  output=blocks.blocks.get('output'),
295
313
  interaction=blocks.blocks.get('interaction'),
296
- notes=blocks.blocks.get('notes'),
314
+ notes=_get_notes_with_explanations(blocks),
297
315
  )
298
316
  problem.save_statement(
299
317
  lang=code_to_langs([language])[0], problem_statement=polygon_statement
@@ -311,8 +329,8 @@ async def upload_problem(name: str):
311
329
  name = _normalize_problem_name(name)
312
330
  problem = _find_or_create_problem(name)
313
331
  _update_problem_info(problem)
314
- _update_checker(problem)
315
332
  _update_rbx_header(problem)
333
+ _update_checker(problem)
316
334
 
317
335
  if (
318
336
  pkg.type == TaskType.COMMUNICATION
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import contextlib
2
3
  import os
3
4
  import signal
@@ -5,22 +6,26 @@ import threading
5
6
  from typing import Optional, Set
6
7
 
7
8
  _processing_context_pids: Optional[Set[int]] = None
9
+ _terminate_all_on_error = False
8
10
  _lock = threading.Lock()
9
11
 
10
12
  # Creating a processing context is not thread-safe, but adding to it is.
11
13
 
12
14
 
13
15
  @contextlib.contextmanager
14
- def new_processing_context():
15
- global _processing_context_pids
16
+ def new_processing_context(terminate_all_on_error: bool = False):
17
+ global _processing_context_pids, _terminate_all_on_error
16
18
  with _lock:
17
19
  old_processing_context_pids = _processing_context_pids
20
+ _old_terminate_all_on_error = _terminate_all_on_error
18
21
  _processing_context_pids = set()
22
+ _terminate_all_on_error = terminate_all_on_error
19
23
  try:
20
24
  yield
21
25
  finally:
22
26
  with _lock:
23
27
  _processing_context_pids = old_processing_context_pids
28
+ _terminate_all_on_error = _old_terminate_all_on_error
24
29
 
25
30
 
26
31
  def get_processing_context() -> Set[int]:
@@ -36,7 +41,8 @@ def add_to_processing_context(pid: int):
36
41
  _processing_context_pids.add(pid)
37
42
 
38
43
 
39
- def terminate_all_processes_in_context():
44
+ def terminate_all_processes_in_context(clear: bool = True):
45
+ global _processing_context_pids
40
46
  with _lock:
41
47
  if _processing_context_pids is None:
42
48
  return
@@ -45,4 +51,37 @@ def terminate_all_processes_in_context():
45
51
  os.kill(pid, signal.SIGTERM)
46
52
  except OSError:
47
53
  pass
48
- _processing_context_pids.clear()
54
+ if clear:
55
+ _processing_context_pids.clear()
56
+
57
+
58
+ async def wait_all_processes_in_context(wait_for: int):
59
+ global _processing_context_pids, _terminate_all_on_error
60
+ wait_pids = set()
61
+ while len(get_processing_context()) < wait_for:
62
+ await asyncio.sleep(0.01)
63
+
64
+ with _lock:
65
+ if _processing_context_pids is None:
66
+ return
67
+ wait_pids.update(_processing_context_pids)
68
+
69
+ wait_lock = threading.Lock()
70
+ finished_pids = []
71
+
72
+ def process(pid: int, returncode: int):
73
+ with wait_lock:
74
+ finished_pids.append(pid)
75
+ if returncode != 0 and _terminate_all_on_error:
76
+ terminate_all_processes_in_context()
77
+
78
+ def wait_all_processes():
79
+ while len(finished_pids) < len(wait_pids):
80
+ try:
81
+ pid, status = os.wait()
82
+ except ChildProcessError:
83
+ return
84
+ if pid in wait_pids:
85
+ process(pid, os.waitstatus_to_exitcode(status))
86
+
87
+ await asyncio.to_thread(wait_all_processes)
rbx/grading/steps.py CHANGED
@@ -193,6 +193,11 @@ class RunLogMetadata(BaseModel):
193
193
  retryIndex: Optional[int] = None
194
194
 
195
195
 
196
+ class ProcessingContextLog(BaseModel):
197
+ pid: int = -1
198
+ exitindex: int = -1
199
+
200
+
196
201
  class RunLog(BaseModel):
197
202
  exitcode: int = 0
198
203
  exitstatus: str = SandboxBase.EXIT_SANDBOX_ERROR
@@ -330,14 +335,29 @@ def _expand_part(part: str, sandbox: SandboxBase) -> List[str]:
330
335
  return [part]
331
336
 
332
337
 
338
+ def _get_java_memory_limits(sandbox: SandboxBase) -> Tuple[int, int]:
339
+ max_memory = sandbox.params.address_space
340
+ if max_memory is None:
341
+ max_memory = 2048
342
+ return max_memory, min(512, int(max_memory * 0.9))
343
+
344
+
333
345
  def _split_and_expand(command: str, sandbox: SandboxBase) -> List[str]:
334
346
  res = []
335
- parts = shlex.split(command.format(memory=sandbox.params.address_space or 2048))
347
+ max_mem, init_mem = _get_java_memory_limits(sandbox)
348
+ parts = shlex.split(command.format(memory=max_mem, initialMemory=init_mem))
336
349
  for part in parts:
337
350
  res.extend(_expand_part(part, sandbox))
338
351
  return res
339
352
 
340
353
 
354
+ def get_exe_from_command(command: str) -> str:
355
+ cmds = shlex.split(command)
356
+ if not cmds:
357
+ return command
358
+ return cmds[0]
359
+
360
+
341
361
  def _is_c_command(exe_command: str) -> bool:
342
362
  return 'gcc' in exe_command or 'clang' in exe_command
343
363
 
@@ -351,15 +371,26 @@ def is_cxx_command(exe_command: str) -> bool:
351
371
 
352
372
 
353
373
  def is_cxx_sanitizer_command(command: str) -> bool:
354
- cmds = shlex.split(command)
355
- if not cmds:
374
+ exe = get_exe_from_command(command)
375
+ if not exe:
356
376
  return False
357
- exe = cmds[0]
358
377
  if not is_cxx_command(exe):
359
378
  return False
360
379
  return 'fsanitize' in command
361
380
 
362
381
 
382
+ def is_java_command(exe_command: str) -> bool:
383
+ return 'javac' in exe_command or 'java' in exe_command
384
+
385
+
386
+ def is_kotlin_command(exe_command: str) -> bool:
387
+ return 'kotlinc' in exe_command or 'kotlin' in exe_command
388
+
389
+
390
+ def is_java_like_command(exe_command: str) -> bool:
391
+ return is_java_command(exe_command) or is_kotlin_command(exe_command)
392
+
393
+
363
394
  @functools.cache
364
395
  def _complain_about_clang() -> None:
365
396
  console.print(
@@ -539,6 +570,10 @@ def compile(
539
570
  stderr_file = pathlib.PosixPath(f'compile-{i}.stderr')
540
571
  sandbox.params.set_stdall(stdout=stdout_file, stderr=stderr_file)
541
572
 
573
+ # Remove memory constraints for Java.
574
+ if is_java_like_command(get_exe_from_command(command)):
575
+ sandbox.params.address_space = None
576
+
542
577
  if bits_artifact is not None and _is_cpp_command(cmd[0]):
543
578
  # Include from sandbox directory to import bits/stdc++.h.
544
579
  cmd.append('-I.')
@@ -604,6 +639,10 @@ async def run(
604
639
  cmd = _split_and_expand(command, sandbox)
605
640
  sandbox.set_params(params)
606
641
 
642
+ # Remove memory constraints for Java.
643
+ if is_java_like_command(get_exe_from_command(command)):
644
+ sandbox.params.address_space = None
645
+
607
646
  if not await asyncio.to_thread(sandbox.execute_without_std, cmd):
608
647
  console.print(
609
648
  '[error]Sandbox crashed while processing command:[/error]',
@@ -614,7 +653,7 @@ async def run(
614
653
  return None
615
654
 
616
655
  if sandbox.get_exit_code() != 0 and kill_on_processing_context_exit:
617
- processing_context.terminate_all_processes_in_context()
656
+ processing_context.terminate_all_processes_in_context(clear=False)
618
657
 
619
658
  if not _process_output_artifacts(artifacts, sandbox):
620
659
  return None
@@ -657,22 +696,27 @@ async def run_coordinated(
657
696
  interactor: CoordinatedRunParams,
658
697
  solution: CoordinatedRunParams,
659
698
  ) -> Tuple[Optional[RunLog], Optional[RunLog]]:
660
- with processing_context.new_processing_context():
699
+ with processing_context.new_processing_context(terminate_all_on_error=True):
700
+ # Schedule both runs to execute immediately.
661
701
  runs = tuple(
662
- run(
663
- params.command,
664
- params.params,
665
- params.sandbox,
666
- params.artifacts,
667
- params.metadata,
668
- kill_on_processing_context_exit=True,
702
+ asyncio.create_task(
703
+ run(
704
+ params.command,
705
+ params.params,
706
+ params.sandbox,
707
+ params.artifacts,
708
+ params.metadata,
709
+ kill_on_processing_context_exit=True,
710
+ )
669
711
  )
670
712
  for params in [interactor, solution]
671
713
  )
672
- return typing.cast(
714
+ await processing_context.wait_all_processes_in_context(wait_for=2)
715
+ logs = typing.cast(
673
716
  Tuple[Optional[RunLog], Optional[RunLog]],
674
717
  tuple(await asyncio.gather(*runs)),
675
718
  )
719
+ return logs
676
720
 
677
721
 
678
722
  def _normalize_checked_words(s: str) -> Tuple[str, ...]:
rbx/utils.py CHANGED
@@ -57,21 +57,32 @@ def get_app_path() -> pathlib.Path:
57
57
  return pathlib.Path(app_dir)
58
58
 
59
59
 
60
- def ensure_schema(model: Type[BaseModel]) -> pathlib.Path:
61
- path = get_app_path() / 'schemas' / f'{model.__name__}.json'
60
+ def dump_schema_str(model: Type[BaseModel]) -> str:
61
+ return json.dumps(model.model_json_schema(), indent=4)
62
+
63
+
64
+ def dump_schema(model: Type[BaseModel], path: pathlib.Path):
62
65
  path.parent.mkdir(parents=True, exist_ok=True)
63
- schema = json.dumps(model.model_json_schema(), indent=4)
66
+ schema = dump_schema_str(model)
64
67
  path.write_text(schema)
68
+
69
+
70
+ def ensure_schema(model: Type[BaseModel]) -> pathlib.Path:
71
+ path = get_app_path() / 'schemas' / f'{model.__name__}.json'
72
+ dump_schema(model, path)
65
73
  return path.resolve()
66
74
 
67
75
 
68
76
  def model_json(model: BaseModel) -> str:
69
- ensure_schema(model.__class__)
70
77
  return model.model_dump_json(indent=4, exclude_unset=True, exclude_none=True)
71
78
 
72
79
 
80
+ def uploaded_schema_path(model: Type[BaseModel]) -> str:
81
+ return f'https://rsalesc.github.io/rbx/schemas/{model.__name__}.json'
82
+
83
+
73
84
  def model_to_yaml(model: BaseModel) -> str:
74
- path = ensure_schema(model.__class__)
85
+ path = uploaded_schema_path(model.__class__)
75
86
  return f'# yaml-language-server: $schema={path}\n\n' + yaml.dump(
76
87
  model.model_dump(mode='json', exclude_unset=True, exclude_none=True),
77
88
  sort_keys=False,
@@ -80,7 +91,6 @@ def model_to_yaml(model: BaseModel) -> str:
80
91
 
81
92
 
82
93
  def model_from_yaml(model: Type[T], s: str) -> T:
83
- ensure_schema(model)
84
94
  return model(**yaml.safe_load(s))
85
95
 
86
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.52
3
+ Version: 0.5.54
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -21,6 +21,7 @@ Requires-Dist: latexbuild (>=0.2.2,<0.3.0)
21
21
  Requires-Dist: mechanize (>=0.4.10,<0.5.0)
22
22
  Requires-Dist: more-itertools (>=10.5.0,<11.0.0)
23
23
  Requires-Dist: nest-asyncio (>=1.6.0,<2.0.0)
24
+ Requires-Dist: psutil (>=7.0.0,<8.0.0)
24
25
  Requires-Dist: pydantic (==2.8.2)
25
26
  Requires-Dist: pydantic-xml[lxml] (>=2.11.0,<3.0.0)
26
27
  Requires-Dist: pyte (>=0.8.2,<0.9.0)
@@ -4,7 +4,7 @@ rbx/autoenum.py,sha256=cusv8ClXRlDVvhZ8eDrtYcL_2peXlHugAey_ht8roXk,12025
4
4
  rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  rbx/box/builder.py,sha256=MDm2qqmhedAbhn3rWP6cDwbBsGhV6sz_2sg1zLkPDw0,3613
6
6
  rbx/box/cd.py,sha256=9a_SOnzoJBXxxffp4Wbf3UKXIwKuN3Hvj7K6SocALwE,1194
7
- rbx/box/checkers.py,sha256=eb4tqtVUJYlme_Vkj2TGkUABM7EMS0P1EswhMYjN7BI,11459
7
+ rbx/box/checkers.py,sha256=5Pj6a8y5YCVY3KvV7B_WLo30uDmIhnEgxE6_KKZqzvY,11923
8
8
  rbx/box/cli.py,sha256=GPoGDgmaV1lXL9puXhUbZUIEU0lOMvdH1kD4TGCXkCo,26738
9
9
  rbx/box/code.py,sha256=hmA2EoGOr13AYzicHNsnU2SkFW-44l7kOdu8QRwTJlI,18848
10
10
  rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
@@ -19,6 +19,7 @@ rbx/box/contest/statements.py,sha256=Or8gFb6P_oViGdeiVgepXsvd_W84mA7LRaVmiAXWWSg
19
19
  rbx/box/creation.py,sha256=Evz7K6JoarD-4JJQsZsgoxU9FgCF9Z7-LfuroG4Cqls,2444
20
20
  rbx/box/deferred.py,sha256=II3X9e87JCOZtmspnHh-n4PFqh-FsH_oc0XJHZ9ZYVQ,691
21
21
  rbx/box/download.py,sha256=DxAiAk4lDYWEz1C9UTvZzHTq6hgm4fxGezApm2IkCTM,2601
22
+ rbx/box/dump_schemas.py,sha256=3j5t47_vJmXj0BCczxDX6ByOcsfolGEDNCBXlPpk86w,593
22
23
  rbx/box/environment.py,sha256=fZnNQCdpG3xzKne0FY7gkuaopy6fdYvVFAXeHmC1ZOo,11498
23
24
  rbx/box/extensions.py,sha256=Von8kIeXvNFTkGlMRMTvL2HIHPwlkuiMswr-ydbGV1w,519
24
25
  rbx/box/formatting.py,sha256=3phFRHzqVXj4Ok1yDhCq6Clbw6KlqwJNpMhs--oTWFI,405
@@ -39,7 +40,7 @@ rbx/box/packaging/packager.py,sha256=da2haC1L9cG30myneMrRIAdGubtid0Xmy38BHKPCZZ4
39
40
  rbx/box/packaging/polygon/packager.py,sha256=GfZ-Dc2TDKkb3QNnfOy8yxldho2L401Ao06oWg--Gcs,11714
40
41
  rbx/box/packaging/polygon/polygon_api.py,sha256=mPKEqiwANJ1nr-JhOgzGMaDhnbljsAgzzPHW6kkf7R4,41016
41
42
  rbx/box/packaging/polygon/test.py,sha256=bgEju5PwudgyfwxXJagm8fM6CJVlWM6l_-2q1V-oKaQ,3069
42
- rbx/box/packaging/polygon/upload.py,sha256=2WyEXlfprLUDbq-9AMB5hXOQVvvmsT1-stO60MHrEo0,11182
43
+ rbx/box/packaging/polygon/upload.py,sha256=woaqf2vcWmvCyBXtAesBUOaMcVUns3hkuzXqzcgAjyo,11826
43
44
  rbx/box/packaging/polygon/xml_schema.py,sha256=ZgcLyvxggMUccbTNdzflue5G-FTN2_ZmOGGF7FD0Y5A,2851
44
45
  rbx/box/presets/__init__.py,sha256=BwmjBw8wF8yiZFjCYBjMk-HGMZaRwhlfszbWAj3B0vw,18689
45
46
  rbx/box/presets/fetch.py,sha256=F-BCOlvEBEyDqtOhiDuGPn4EDtA4Bwm-fqHJ7zZGlW8,1975
@@ -97,8 +98,8 @@ rbx/grading/judge/sandboxes/timeit.py,sha256=jQonQhaVI93diYQsJYXwS77IO1t5iZVKJtm
97
98
  rbx/grading/judge/storage.py,sha256=3vv0HvtenbUZBH33CB5ZzX66ppL22G6munBaAA9BgwQ,9418
98
99
  rbx/grading/judge/test.py,sha256=ll0Iw7zyOpGdKPD_PGH7dvUkb4stQLu-ikbQnqJvuAc,944
99
100
  rbx/grading/judge/testiso.py,sha256=v14DtkWiZFJ9AKMzrb0_vZKPWDt8jz8iIw1Z2O-Advk,1397
100
- rbx/grading/processing_context.py,sha256=2fxa610WzXGvICDVWkPCG721w-1tXDVT7w_KtSnD0OM,1213
101
- rbx/grading/steps.py,sha256=RpA6HjQ4NaZLMryyFFGBW53QojLoD9VH5yjuZMycWds,25752
101
+ rbx/grading/processing_context.py,sha256=EOxsRTKB_JEgcKNodDWPIYaBramANU-6QnDkqdF8tEk,2556
102
+ rbx/grading/steps.py,sha256=VscFO-fnKtSspdB1Cnu18l8feaREbsh0MNkexJ6obOc,27163
102
103
  rbx/grading/steps_with_caching.py,sha256=nez2YwgauGXKRjhk6tQxTDGQ-HEk7KfZOeAPhsxi5iw,3150
103
104
  rbx/grading/steps_with_caching_run_test.py,sha256=mh4DRInrOGhnQFWD1SlcjDm_HvcSDFTDMSpAlG-Q5SI,15570
104
105
  rbx/grading_utils.py,sha256=lL2KtSkOsMElqrRoApQTbFcqVOeHVWUDTMCa3IsLpC4,4484
@@ -191,9 +192,9 @@ rbx/test.py,sha256=tZyTrXDK8MLR-1TyCRxiOiNz-PhlXVpshfUGphakkT4,11830
191
192
  rbx/testcase.py,sha256=yKOq3CAJZ1YTmInvnoIs0u1iJnRj_X85XiWbLI-p9d8,1951
192
193
  rbx/testcase_rendering.py,sha256=nfmv6dSEqd4aR3TsaODwkKGK6AXty_DDKtWf_ejiQpI,2084
193
194
  rbx/testing_utils.py,sha256=x_PqD8Zd2PkN91NxVHUnSTs044-1WK5KKtttKQBXpFs,2083
194
- rbx/utils.py,sha256=6e1eXRzNE-52D0UVtqclePxqR4Haiqt8qWCrSVjnGuE,4585
195
- rbx_cp-0.5.52.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
196
- rbx_cp-0.5.52.dist-info/METADATA,sha256=zA9fGYWC651-X-bxyI87ll66EM9GqMSn7FF28UIcAkE,3348
197
- rbx_cp-0.5.52.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
198
- rbx_cp-0.5.52.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
199
- rbx_cp-0.5.52.dist-info/RECORD,,
195
+ rbx/utils.py,sha256=SfR844_i0ebRDMkmS_w1YdZiWPc6h2RGADygewlWRbA,4845
196
+ rbx_cp-0.5.54.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
197
+ rbx_cp-0.5.54.dist-info/METADATA,sha256=Ki-VbHPb5TES3dtzJ_muEqjIuzJRACCQ5MIhdIGG0G4,3387
198
+ rbx_cp-0.5.54.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
199
+ rbx_cp-0.5.54.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
200
+ rbx_cp-0.5.54.dist-info/RECORD,,