rbx.cp 0.5.50__py3-none-any.whl → 0.5.52__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/cli.py +17 -4
- rbx/box/code.py +2 -1
- rbx/box/download.py +11 -1
- rbx/box/environment.py +11 -1
- rbx/box/header.py +73 -0
- rbx/box/main.py +4 -0
- rbx/box/naming.py +9 -0
- rbx/box/packaging/boca/packager.py +3 -1
- rbx/box/packaging/contest_main.py +6 -4
- rbx/box/packaging/main.py +15 -1
- rbx/box/packaging/moj/packager.py +6 -1
- rbx/box/packaging/polygon/packager.py +30 -7
- rbx/box/packaging/polygon/polygon_api.py +1327 -0
- rbx/box/packaging/polygon/upload.py +336 -0
- rbx/box/packaging/polygon/xml_schema.py +6 -0
- rbx/box/solutions.py +55 -25
- rbx/box/stresses.py +9 -6
- rbx/box/testcase_utils.py +15 -0
- rbx/box/ui/captured_log.py +9 -5
- rbx/box/ui/css/app.tcss +1 -1
- rbx/box/ui/run.py +19 -18
- rbx/box/unit.py +1 -1
- rbx/resources/packagers/boca/checker.sh +5 -0
- rbx/resources/templates/rbx.h +90 -0
- rbx/testing_utils.py +2 -2
- {rbx_cp-0.5.50.dist-info → rbx_cp-0.5.52.dist-info}/METADATA +3 -1
- {rbx_cp-0.5.50.dist-info → rbx_cp-0.5.52.dist-info}/RECORD +30 -26
- {rbx_cp-0.5.50.dist-info → rbx_cp-0.5.52.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.50.dist-info → rbx_cp-0.5.52.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.50.dist-info → rbx_cp-0.5.52.dist-info}/entry_points.txt +0 -0
rbx/box/cli.py
CHANGED
@@ -27,6 +27,7 @@ from rbx.box import (
|
|
27
27
|
from rbx.box.contest import main as contest
|
28
28
|
from rbx.box.contest.contest_package import find_contest_yaml
|
29
29
|
from rbx.box.environment import VerificationLevel, get_environment_path
|
30
|
+
from rbx.box.header import generate_header
|
30
31
|
from rbx.box.packaging import main as packaging
|
31
32
|
from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
|
32
33
|
from rbx.box.solutions import (
|
@@ -121,10 +122,12 @@ def main(
|
|
121
122
|
state.STATE.debug_logs = debug_logs
|
122
123
|
|
123
124
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
125
|
+
@app.command('ui', hidden=True)
|
126
|
+
@package.within_problem
|
127
|
+
def ui():
|
128
|
+
from rbx.box.ui import main as ui_pkg
|
129
|
+
|
130
|
+
ui_pkg.start()
|
128
131
|
|
129
132
|
|
130
133
|
@app.command(
|
@@ -755,6 +758,16 @@ def unit_tests():
|
|
755
758
|
unit.run_unit_tests(s)
|
756
759
|
|
757
760
|
|
761
|
+
@app.command(
|
762
|
+
'header',
|
763
|
+
rich_help_panel='Configuration',
|
764
|
+
help='Generate the rbx.h header file.',
|
765
|
+
)
|
766
|
+
@package.within_problem
|
767
|
+
def header():
|
768
|
+
generate_header()
|
769
|
+
|
770
|
+
|
758
771
|
@app.command(
|
759
772
|
'environment, env',
|
760
773
|
rich_help_panel='Configuration',
|
rbx/box/code.py
CHANGED
@@ -193,7 +193,7 @@ def _check_stack_limit():
|
|
193
193
|
```
|
194
194
|
function rbx() {{
|
195
195
|
local rbx_bin=`bash -c "type -P rbx"`
|
196
|
-
ulimit -s {target_text // 1024} && $rbx_bin $@
|
196
|
+
ulimit -s {target_text // 1024} && $rbx_bin "$@"
|
197
197
|
}}
|
198
198
|
```
|
199
199
|
"""
|
@@ -415,6 +415,7 @@ def compile_item(
|
|
415
415
|
|
416
416
|
download.maybe_add_testlib(code, artifacts)
|
417
417
|
download.maybe_add_jngen(code, artifacts)
|
418
|
+
download.maybe_add_rbx_header(code, artifacts)
|
418
419
|
artifacts.inputs.append(
|
419
420
|
GradingFileInput(src=generator_path, dest=PosixPath(file_mapping.compilable))
|
420
421
|
)
|
rbx/box/download.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
|
|
5
5
|
import typer
|
6
6
|
|
7
7
|
from rbx import annotations, console
|
8
|
-
from rbx.box import package
|
8
|
+
from rbx.box import header, package
|
9
9
|
from rbx.box.schema import CodeItem
|
10
10
|
from rbx.config import get_builtin_checker, get_jngen, get_testlib
|
11
11
|
from rbx.grading import steps
|
@@ -20,6 +20,16 @@ def get_local_artifact(name: str) -> Optional[steps.GradingFileInput]:
|
|
20
20
|
return None
|
21
21
|
|
22
22
|
|
23
|
+
def maybe_add_rbx_header(code: CodeItem, artifacts: steps.GradingArtifacts):
|
24
|
+
header.get_header()
|
25
|
+
artifact = get_local_artifact('rbx.h')
|
26
|
+
assert artifact is not None
|
27
|
+
compilation_files = package.get_compilation_files(code)
|
28
|
+
if any(dest == artifact.dest for _, dest in compilation_files):
|
29
|
+
return
|
30
|
+
artifacts.inputs.append(artifact)
|
31
|
+
|
32
|
+
|
23
33
|
def maybe_add_testlib(code: CodeItem, artifacts: steps.GradingArtifacts):
|
24
34
|
# Try to get from compilation files, then from package folder, then from tool.
|
25
35
|
artifact = get_local_artifact('testlib.h') or steps.testlib_grading_input()
|
rbx/box/environment.py
CHANGED
@@ -4,7 +4,7 @@ from enum import Enum
|
|
4
4
|
from typing import Annotated, List, Optional, Type, TypeVar
|
5
5
|
|
6
6
|
import typer
|
7
|
-
from pydantic import BaseModel, ConfigDict, ValidationError
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
8
8
|
|
9
9
|
from rbx import config, console, utils
|
10
10
|
from rbx.box.extensions import Extensions, LanguageExtensions
|
@@ -143,6 +143,13 @@ class EnvironmentLanguage(BaseModel):
|
|
143
143
|
return self.get_extension(name, cls) or cls()
|
144
144
|
|
145
145
|
|
146
|
+
class TimingConfig(BaseModel):
|
147
|
+
model_config = ConfigDict(extra='forbid')
|
148
|
+
|
149
|
+
# Formula to use to calculate the time limit for the environment.
|
150
|
+
formula: str = 'step_up(max(fastest * 3, slowest * 1.5), 100)'
|
151
|
+
|
152
|
+
|
146
153
|
class Environment(BaseModel):
|
147
154
|
model_config = ConfigDict(extra='forbid')
|
148
155
|
|
@@ -167,6 +174,9 @@ class Environment(BaseModel):
|
|
167
174
|
# Identifier of the preset that should be used when creating new problems.
|
168
175
|
preset: str = 'default'
|
169
176
|
|
177
|
+
# Timing configuration for the environment.
|
178
|
+
timing: TimingConfig = Field(default_factory=TimingConfig)
|
179
|
+
|
170
180
|
# Extensions to be added to the environment.
|
171
181
|
extensions: Optional[Extensions] = None
|
172
182
|
|
rbx/box/header.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import functools
|
2
|
+
import importlib
|
3
|
+
import importlib.resources
|
4
|
+
import pathlib
|
5
|
+
from typing import Callable, Dict, Type
|
6
|
+
|
7
|
+
from rbx.box import package
|
8
|
+
from rbx.box.schema import Primitive
|
9
|
+
|
10
|
+
|
11
|
+
@functools.cache
|
12
|
+
def get_header() -> pathlib.Path:
|
13
|
+
generate_header()
|
14
|
+
|
15
|
+
return pathlib.Path('rbx.h')
|
16
|
+
|
17
|
+
|
18
|
+
def generate_header():
|
19
|
+
with importlib.resources.as_file(
|
20
|
+
importlib.resources.files('rbx') / 'resources' / 'templates' / 'rbx.h'
|
21
|
+
) as file:
|
22
|
+
with file.open('r') as f:
|
23
|
+
header = f.read()
|
24
|
+
|
25
|
+
with pathlib.Path('rbx.h').open('w') as f:
|
26
|
+
f.write(_preprocess_header(header))
|
27
|
+
|
28
|
+
|
29
|
+
def _preprocess_header(header: str) -> str:
|
30
|
+
return (
|
31
|
+
header.replace('//<rbx::string_var>', _get_string_var_block())
|
32
|
+
.replace('//<rbx::int_var>', _get_int_var_block())
|
33
|
+
.replace('//<rbx::float_var>', _get_float_var_block())
|
34
|
+
.replace('//<rbx::bool_var>', _get_bool_var_block())
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
def _get_string_var_block() -> str:
|
39
|
+
return _get_var_block(_get_vars_of_type(str, lambda x: f'{x:!r}'))
|
40
|
+
|
41
|
+
|
42
|
+
def _get_int_var_block() -> str:
|
43
|
+
return _get_var_block(_get_vars_of_type(int, lambda x: str(x)))
|
44
|
+
|
45
|
+
|
46
|
+
def _get_float_var_block() -> str:
|
47
|
+
return _get_var_block(_get_vars_of_type(float, lambda x: f'{x}'))
|
48
|
+
|
49
|
+
|
50
|
+
def _get_bool_var_block() -> str:
|
51
|
+
return _get_var_block(_get_vars_of_type(bool, lambda x: 'true' if x else 'false'))
|
52
|
+
|
53
|
+
|
54
|
+
def _get_vars_of_type(
|
55
|
+
type: Type, transform: Callable[[Primitive], str]
|
56
|
+
) -> Dict[str, str]:
|
57
|
+
pkg = package.find_problem_package_or_die()
|
58
|
+
vars = pkg.expanded_vars
|
59
|
+
return {
|
60
|
+
name: transform(value)
|
61
|
+
for name, value in vars.items()
|
62
|
+
if isinstance(value, type)
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
def _get_var_block(mappings: Dict[str, str]) -> str:
|
67
|
+
entries = []
|
68
|
+
# Iterate over sorted keys to ensure a deterministic order.
|
69
|
+
for name in sorted(mappings):
|
70
|
+
value = mappings[name]
|
71
|
+
entry = f' if (name == "{name}") {{\n return {value};\n }}\n'
|
72
|
+
entries.append(entry)
|
73
|
+
return ''.join(entries)
|
rbx/box/main.py
CHANGED
rbx/box/naming.py
CHANGED
@@ -20,3 +20,12 @@ def get_problem_shortname() -> Optional[str]:
|
|
20
20
|
return problem.short_name
|
21
21
|
|
22
22
|
return None
|
23
|
+
|
24
|
+
|
25
|
+
def get_problem_name_with_contest_info() -> str:
|
26
|
+
problem = package.find_problem_package_or_die()
|
27
|
+
contest = contest_package.find_contest_package()
|
28
|
+
short_name = get_problem_shortname()
|
29
|
+
if contest is None or short_name is None:
|
30
|
+
return problem.name
|
31
|
+
return f'{contest.name}-{short_name}-{problem.name}'
|
@@ -6,7 +6,7 @@ from typing import List
|
|
6
6
|
import typer
|
7
7
|
|
8
8
|
from rbx import console
|
9
|
-
from rbx.box import naming, package
|
9
|
+
from rbx.box import header, naming, package
|
10
10
|
from rbx.box.environment import get_extension_or_default
|
11
11
|
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
|
12
12
|
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
@@ -150,9 +150,11 @@ class BocaPackager(BasePackager):
|
|
150
150
|
checker_text = checker_path.read_text()
|
151
151
|
testlib = get_testlib().read_text()
|
152
152
|
checker = package.get_checker().path.read_text()
|
153
|
+
rbx_header = header.get_header().read_text()
|
153
154
|
return (
|
154
155
|
checker_text.replace('{{rbxFlags}}', extension.flags_with_defaults()['cc'])
|
155
156
|
.replace('{{testlib_content}}', testlib)
|
157
|
+
.replace('{{rbx_header_content}}', rbx_header)
|
156
158
|
.replace('{{checker_content}}', checker)
|
157
159
|
)
|
158
160
|
|
@@ -2,6 +2,7 @@ import pathlib
|
|
2
2
|
import tempfile
|
3
3
|
from typing import Type
|
4
4
|
|
5
|
+
import syncer
|
5
6
|
import typer
|
6
7
|
|
7
8
|
from rbx import annotations, console
|
@@ -18,7 +19,7 @@ from rbx.box.packaging.packager import (
|
|
18
19
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
19
20
|
|
20
21
|
|
21
|
-
def run_contest_packager(
|
22
|
+
async def run_contest_packager(
|
22
23
|
contest_packager_cls: Type[BaseContestPackager],
|
23
24
|
packager_cls: Type[BasePackager],
|
24
25
|
verification: environment.VerificationParam,
|
@@ -33,7 +34,7 @@ def run_contest_packager(
|
|
33
34
|
)
|
34
35
|
with cd.new_package_cd(problem.get_path()):
|
35
36
|
package.clear_package_cache()
|
36
|
-
package_path = run_packager(packager_cls, verification=verification)
|
37
|
+
package_path = await run_packager(packager_cls, verification=verification)
|
37
38
|
built_packages.append(
|
38
39
|
BuiltProblemPackage(
|
39
40
|
path=problem.get_path() / package_path,
|
@@ -73,7 +74,8 @@ def run_contest_packager(
|
|
73
74
|
|
74
75
|
@app.command('polygon', help='Build a contest package for Polygon.')
|
75
76
|
@contest_package.within_contest
|
76
|
-
|
77
|
+
@syncer.sync
|
78
|
+
async def polygon(
|
77
79
|
verification: environment.VerificationParam,
|
78
80
|
):
|
79
81
|
from rbx.box.packaging.polygon.packager import (
|
@@ -81,6 +83,6 @@ def polygon(
|
|
81
83
|
PolygonPackager,
|
82
84
|
)
|
83
85
|
|
84
|
-
run_contest_packager(
|
86
|
+
await run_contest_packager(
|
85
87
|
PolygonContestPackager, PolygonPackager, verification=verification
|
86
88
|
)
|
rbx/box/packaging/main.py
CHANGED
@@ -6,7 +6,8 @@ import syncer
|
|
6
6
|
import typer
|
7
7
|
|
8
8
|
from rbx import annotations, console
|
9
|
-
from rbx.box import environment, package
|
9
|
+
from rbx.box import environment, header, package
|
10
|
+
from rbx.box.naming import get_problem_name_with_contest_info
|
10
11
|
from rbx.box.package import get_build_path
|
11
12
|
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
12
13
|
from rbx.box.statements.build_statements import build_statement
|
@@ -21,6 +22,8 @@ async def run_packager(
|
|
21
22
|
) -> pathlib.Path:
|
22
23
|
from rbx.box import builder
|
23
24
|
|
25
|
+
header.generate_header()
|
26
|
+
|
24
27
|
if not await builder.verify(verification=verification):
|
25
28
|
console.console.print(
|
26
29
|
'[error]Build or verification failed, check the report.[/error]'
|
@@ -68,11 +71,22 @@ async def run_packager(
|
|
68
71
|
@syncer.sync
|
69
72
|
async def polygon(
|
70
73
|
verification: environment.VerificationParam,
|
74
|
+
upload: bool = typer.Option(
|
75
|
+
False,
|
76
|
+
'--upload',
|
77
|
+
'-u',
|
78
|
+
help='If set, will upload the package to Polygon.',
|
79
|
+
),
|
71
80
|
):
|
72
81
|
from rbx.box.packaging.polygon.packager import PolygonPackager
|
73
82
|
|
74
83
|
await run_packager(PolygonPackager, verification=verification)
|
75
84
|
|
85
|
+
if upload:
|
86
|
+
from rbx.box.packaging.polygon.upload import upload_problem
|
87
|
+
|
88
|
+
await upload_problem(name=get_problem_name_with_contest_info())
|
89
|
+
|
76
90
|
|
77
91
|
@app.command('boca', help='Build a package for BOCA.')
|
78
92
|
@package.within_problem
|
@@ -5,7 +5,7 @@ from typing import List
|
|
5
5
|
import typer
|
6
6
|
|
7
7
|
from rbx import console
|
8
|
-
from rbx.box import package
|
8
|
+
from rbx.box import header, package
|
9
9
|
from rbx.box.environment import get_extension_or_default
|
10
10
|
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
|
11
11
|
from rbx.box.packaging.boca.packager import BocaPackager
|
@@ -153,6 +153,11 @@ class MojPackager(BocaPackager):
|
|
153
153
|
testlib_path.parent.mkdir(parents=True, exist_ok=True)
|
154
154
|
testlib_path.write_text(get_testlib().read_text())
|
155
155
|
|
156
|
+
# Prepare rbx.h
|
157
|
+
rbx_header_path = into_path / 'scripts' / 'rbx.h'
|
158
|
+
rbx_header_path.parent.mkdir(parents=True, exist_ok=True)
|
159
|
+
rbx_header_path.write_text(header.get_header().read_text())
|
160
|
+
|
156
161
|
# Prepare checker
|
157
162
|
checker_path = into_path / 'scripts' / 'checker.cpp'
|
158
163
|
checker_path.parent.mkdir(parents=True, exist_ok=True)
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import functools
|
2
2
|
import pathlib
|
3
3
|
import shutil
|
4
|
-
from typing import List
|
4
|
+
from typing import List, Optional
|
5
5
|
|
6
6
|
import iso639
|
7
7
|
import typer
|
8
8
|
|
9
9
|
from rbx import console
|
10
|
-
from rbx.box import package
|
10
|
+
from rbx.box import header, package
|
11
11
|
from rbx.box.packaging.packager import (
|
12
12
|
BaseContestPackager,
|
13
13
|
BasePackager,
|
@@ -16,6 +16,7 @@ from rbx.box.packaging.packager import (
|
|
16
16
|
BuiltStatement,
|
17
17
|
)
|
18
18
|
from rbx.box.packaging.polygon import xml_schema as polygon_schema
|
19
|
+
from rbx.box.schema import TaskType
|
19
20
|
from rbx.config import get_testlib
|
20
21
|
|
21
22
|
DAT_TEMPLATE = """
|
@@ -36,7 +37,7 @@ def code_to_langs(langs: List[str]) -> List[str]:
|
|
36
37
|
|
37
38
|
|
38
39
|
@functools.cache
|
39
|
-
def
|
40
|
+
def is_valid_lang_code(lang: str) -> bool:
|
40
41
|
try:
|
41
42
|
code_to_langs([lang])
|
42
43
|
except iso639.LanguageNotFoundError:
|
@@ -49,6 +50,10 @@ def _is_valid_lang_code(lang: str) -> bool:
|
|
49
50
|
|
50
51
|
|
51
52
|
class PolygonPackager(BasePackager):
|
53
|
+
@classmethod
|
54
|
+
def task_types(cls) -> List[TaskType]:
|
55
|
+
return [TaskType.BATCH, TaskType.COMMUNICATION]
|
56
|
+
|
52
57
|
def _validate(self):
|
53
58
|
langs = self.languages()
|
54
59
|
pkg = package.find_problem_package_or_die()
|
@@ -83,6 +88,14 @@ class PolygonPackager(BasePackager):
|
|
83
88
|
cpy=polygon_schema.File(path='check.cpp'),
|
84
89
|
)
|
85
90
|
|
91
|
+
def _get_interactor(self) -> Optional[polygon_schema.Interactor]:
|
92
|
+
pkg = package.find_problem_package_or_die()
|
93
|
+
if pkg.interactor is None:
|
94
|
+
return None
|
95
|
+
return polygon_schema.Interactor(
|
96
|
+
source=polygon_schema.File(path='files/interactor.cpp', type='cpp.g++17'),
|
97
|
+
)
|
98
|
+
|
86
99
|
def _get_manual_test(self) -> polygon_schema.Test:
|
87
100
|
# TODO: return samples
|
88
101
|
return polygon_schema.Test(method='manual')
|
@@ -118,7 +131,10 @@ class PolygonPackager(BasePackager):
|
|
118
131
|
return polygon_schema.Judging(testsets=[self._get_single_testset()])
|
119
132
|
|
120
133
|
def _get_files(self) -> List[polygon_schema.File]:
|
121
|
-
return [
|
134
|
+
return [
|
135
|
+
polygon_schema.File(path='files/testlib.h', type='h.g++'),
|
136
|
+
polygon_schema.File(path='files/rbx.h', type='h.g++'),
|
137
|
+
]
|
122
138
|
|
123
139
|
def _statement_application_type(self, statement: BuiltStatement) -> str:
|
124
140
|
return 'application/pdf'
|
@@ -148,7 +164,8 @@ class PolygonPackager(BasePackager):
|
|
148
164
|
for built_statement in built_statements
|
149
165
|
]
|
150
166
|
|
151
|
-
|
167
|
+
@classmethod
|
168
|
+
def name(cls) -> str:
|
152
169
|
return 'polygon'
|
153
170
|
|
154
171
|
def package(
|
@@ -157,11 +174,14 @@ class PolygonPackager(BasePackager):
|
|
157
174
|
into_path: pathlib.Path,
|
158
175
|
built_statements: List[BuiltStatement],
|
159
176
|
):
|
177
|
+
pkg = package.find_problem_package_or_die()
|
178
|
+
|
160
179
|
problem = polygon_schema.Problem(
|
161
180
|
names=self._get_names(),
|
162
181
|
checker=self._get_checker(),
|
163
182
|
judging=self._get_judging(),
|
164
183
|
files=self._get_files(),
|
184
|
+
interactor=self._get_interactor(),
|
165
185
|
# TODO: revisit polygon problem statements
|
166
186
|
# statements=self._process_statements(built_statements, into_path),
|
167
187
|
)
|
@@ -179,8 +199,11 @@ class PolygonPackager(BasePackager):
|
|
179
199
|
files_path = into_path / 'files'
|
180
200
|
files_path.mkdir(parents=True, exist_ok=True)
|
181
201
|
shutil.copyfile(get_testlib(), files_path / 'testlib.h')
|
202
|
+
shutil.copyfile(header.get_header(), files_path / 'rbx.h')
|
182
203
|
shutil.copyfile(package.get_checker().path, files_path / 'check.cpp')
|
183
204
|
shutil.copyfile(package.get_checker().path, into_path / 'check.cpp')
|
205
|
+
if pkg.interactor is not None:
|
206
|
+
shutil.copyfile(pkg.interactor.path, files_path / 'interactor.cpp')
|
184
207
|
|
185
208
|
# Copy all testcases
|
186
209
|
(into_path / 'tests').mkdir(parents=True, exist_ok=True)
|
@@ -219,7 +242,7 @@ class PolygonContestPackager(BaseContestPackager):
|
|
219
242
|
value=self.get_statement_for_language(lang).title,
|
220
243
|
)
|
221
244
|
for lang in self.languages()
|
222
|
-
if
|
245
|
+
if is_valid_lang_code(lang)
|
223
246
|
]
|
224
247
|
if names:
|
225
248
|
names[0].main = True
|
@@ -284,7 +307,7 @@ class PolygonContestPackager(BaseContestPackager):
|
|
284
307
|
) -> pathlib.Path:
|
285
308
|
filtered_statements = []
|
286
309
|
for built_statement in built_statements:
|
287
|
-
if not
|
310
|
+
if not is_valid_lang_code(built_statement.statement.language):
|
288
311
|
continue
|
289
312
|
filtered_statements.append(built_statement)
|
290
313
|
|