runem 0.6.0__py3-none-any.whl → 0.7.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.
- runem/VERSION +1 -1
- runem/blocking_print.py +3 -0
- runem/cli/initialise_options.py +0 -1
- runem/command_line.py +4 -7
- runem/config.py +8 -4
- runem/config_parse.py +7 -8
- runem/config_validate.py +47 -0
- runem/informative_dict.py +7 -2
- runem/job.py +1 -3
- runem/job_execute.py +12 -9
- runem/job_filter.py +5 -5
- runem/job_wrapper_python.py +6 -7
- runem/log.py +5 -2
- runem/report.py +4 -3
- runem/run_command.py +8 -10
- runem/runem.py +10 -7
- runem/schema.yml +137 -0
- runem/types/errors.py +10 -0
- runem/types/hooks.py +1 -1
- runem/types/types_jobs.py +21 -24
- runem/yaml_utils.py +19 -0
- runem/yaml_validation.py +28 -0
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info}/METADATA +9 -8
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info}/RECORD +29 -25
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info}/WHEEL +1 -1
- scripts/test_hooks/py.py +63 -1
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info}/entry_points.txt +0 -0
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info/licenses}/LICENSE +0 -0
- {runem-0.6.0.dist-info → runem-0.7.1.dist-info}/top_level.txt +0 -0
runem/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.1
|
runem/blocking_print.py
CHANGED
@@ -21,6 +21,9 @@ def _reset_console() -> Console:
|
|
21
21
|
# `highlight` is what colourises string and number in print() calls.
|
22
22
|
# We do not want this to be auto-magic.
|
23
23
|
highlight=False,
|
24
|
+
# `soft_wrap=True` disables word-wrap & cropping by default:
|
25
|
+
# - `soft_wrap` reads like a misnomer to me
|
26
|
+
soft_wrap=True,
|
24
27
|
)
|
25
28
|
return RICH_CONSOLE
|
26
29
|
|
runem/cli/initialise_options.py
CHANGED
runem/command_line.py
CHANGED
@@ -43,10 +43,8 @@ def _get_argparse_help_formatter() -> typing.Any:
|
|
43
43
|
|
44
44
|
if use_fixed_width:
|
45
45
|
# Use custom formatter with the width specified in the environment variable
|
46
|
-
return (
|
47
|
-
|
48
|
-
prog
|
49
|
-
)
|
46
|
+
return lambda prog: HelpFormatterFixedWidth( # pylint: disable=unnecessary-lambda
|
47
|
+
prog
|
50
48
|
)
|
51
49
|
|
52
50
|
# Use default formatter
|
@@ -294,12 +292,12 @@ def parse_args(
|
|
294
292
|
error_on_log_logic(args.verbose, args.silent)
|
295
293
|
|
296
294
|
if args.show_root_path_and_exit:
|
297
|
-
log(str(config_metadata.cfg_filepath.parent),
|
295
|
+
log(str(config_metadata.cfg_filepath.parent), prefix=False)
|
298
296
|
# cleanly exit
|
299
297
|
sys.exit(0)
|
300
298
|
|
301
299
|
if args.show_version_and_exit:
|
302
|
-
log(str(get_runem_version()),
|
300
|
+
log(str(get_runem_version()), prefix=False)
|
303
301
|
# cleanly exit
|
304
302
|
sys.exit(0)
|
305
303
|
|
@@ -383,7 +381,6 @@ def initialise_options(
|
|
383
381
|
|
384
382
|
Returns the options dictionary
|
385
383
|
"""
|
386
|
-
|
387
384
|
options: OptionsWritable = InformativeDict(
|
388
385
|
{option["name"]: option["default"] for option in config_metadata.options_config}
|
389
386
|
)
|
runem/config.py
CHANGED
@@ -2,9 +2,9 @@ import pathlib
|
|
2
2
|
import sys
|
3
3
|
import typing
|
4
4
|
|
5
|
-
import yaml
|
6
5
|
from packaging.version import Version
|
7
6
|
|
7
|
+
from runem.config_validate import validate_runem_file
|
8
8
|
from runem.log import error, log
|
9
9
|
from runem.runem_version import get_runem_version
|
10
10
|
from runem.types.runem_config import (
|
@@ -13,6 +13,7 @@ from runem.types.runem_config import (
|
|
13
13
|
GlobalSerialisedConfig,
|
14
14
|
UserConfigMetadata,
|
15
15
|
)
|
16
|
+
from runem.yaml_utils import load_yaml_object
|
16
17
|
|
17
18
|
CFG_FILE_YAML = pathlib.Path(".runem.yml")
|
18
19
|
|
@@ -46,7 +47,7 @@ def _search_up_multiple_dirs_for_file(
|
|
46
47
|
|
47
48
|
|
48
49
|
def _find_config_file(
|
49
|
-
config_filename: typing.Union[str, pathlib.Path]
|
50
|
+
config_filename: typing.Union[str, pathlib.Path],
|
50
51
|
) -> typing.Tuple[typing.Optional[pathlib.Path], typing.Tuple[pathlib.Path, ...]]:
|
51
52
|
"""Searches up from the cwd for the given config file-name."""
|
52
53
|
start_dirs = (pathlib.Path(".").absolute(),)
|
@@ -117,8 +118,11 @@ def _conform_global_config_types(
|
|
117
118
|
|
118
119
|
def load_and_parse_config(cfg_filepath: pathlib.Path) -> Config:
|
119
120
|
"""For the given config file pass, project or user, load it & parse/conform it."""
|
120
|
-
|
121
|
-
|
121
|
+
all_config = load_yaml_object(cfg_filepath)
|
122
|
+
validate_runem_file(
|
123
|
+
cfg_filepath,
|
124
|
+
all_config,
|
125
|
+
)
|
122
126
|
|
123
127
|
conformed_config: Config
|
124
128
|
global_config: typing.Optional[GlobalConfig]
|
runem/config_parse.py
CHANGED
@@ -11,7 +11,7 @@ from runem.job import Job
|
|
11
11
|
from runem.job_wrapper import get_job_wrapper
|
12
12
|
from runem.log import error, log, warn
|
13
13
|
from runem.types.common import JobNames, JobPhases, JobTags, OrderedPhases, PhaseName
|
14
|
-
from runem.types.errors import FunctionNotFound
|
14
|
+
from runem.types.errors import FunctionNotFound, SystemExitBad
|
15
15
|
from runem.types.filters import TagFileFilter, TagFileFilters
|
16
16
|
from runem.types.hooks import HookName
|
17
17
|
from runem.types.runem_config import (
|
@@ -81,9 +81,8 @@ def parse_hook_config(
|
|
81
81
|
f"hook config entry is missing '{err.args[0]}' key. Have {tuple(hook.keys())}"
|
82
82
|
) from err
|
83
83
|
except FunctionNotFound as err:
|
84
|
-
|
85
|
-
|
86
|
-
) from err
|
84
|
+
error(f"Whilst loading hook '{str(hook['hook_name'])}'. {str(err)}")
|
85
|
+
raise SystemExitBad(2) from err
|
87
86
|
|
88
87
|
|
89
88
|
def _parse_job( # noqa: C901
|
@@ -110,9 +109,8 @@ def _parse_job( # noqa: C901
|
|
110
109
|
# try and load the function _before_ we schedule it's execution
|
111
110
|
get_job_wrapper(job, cfg_filepath)
|
112
111
|
except FunctionNotFound as err:
|
113
|
-
|
114
|
-
|
115
|
-
) from err
|
112
|
+
error(f"Whilst loading job '{job['label']}'. {str(err)}")
|
113
|
+
raise SystemExitBad(2) from err
|
116
114
|
|
117
115
|
try:
|
118
116
|
phase_id: PhaseName = job["when"]["phase"]
|
@@ -162,7 +160,8 @@ def parse_job_config(
|
|
162
160
|
("cwd" in job["ctx"]) and (job["ctx"]["cwd"] is not None)
|
163
161
|
)
|
164
162
|
if (not have_ctw_cwd) or isinstance(
|
165
|
-
job["ctx"]["cwd"],
|
163
|
+
job["ctx"]["cwd"], # type: ignore # handled above
|
164
|
+
str,
|
166
165
|
):
|
167
166
|
# if
|
168
167
|
# - we don't have a cwd, ctx
|
runem/config_validate.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
import pathlib
|
2
|
+
import typing
|
3
|
+
|
4
|
+
from runem.log import error, log
|
5
|
+
from runem.types.errors import SystemExitBad
|
6
|
+
from runem.yaml_utils import load_yaml_object
|
7
|
+
from runem.yaml_validation import ValidationErrors, validate_yaml
|
8
|
+
|
9
|
+
|
10
|
+
def _load_runem_schema() -> typing.Any:
|
11
|
+
"""Loads and returns the yaml schema for runem.
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
Any: the Draft202012Validator conformant schema.
|
15
|
+
"""
|
16
|
+
schema_path: pathlib.Path = pathlib.Path(__file__).with_name("schema.yml")
|
17
|
+
if not schema_path.exists():
|
18
|
+
error(
|
19
|
+
(
|
20
|
+
"runem schema file not found, cannot continue! "
|
21
|
+
f"Is the install corrupt? {schema_path}"
|
22
|
+
)
|
23
|
+
)
|
24
|
+
raise SystemExitBad(1)
|
25
|
+
schema: typing.Any = load_yaml_object(schema_path)
|
26
|
+
return schema
|
27
|
+
|
28
|
+
|
29
|
+
def validate_runem_file(
|
30
|
+
cfg_filepath: pathlib.Path,
|
31
|
+
all_config: typing.Any,
|
32
|
+
) -> None:
|
33
|
+
"""Validates the config Loader object against the runem schema.
|
34
|
+
|
35
|
+
Exits if the files does not validate.
|
36
|
+
"""
|
37
|
+
schema: typing.Any = _load_runem_schema()
|
38
|
+
errors: ValidationErrors = validate_yaml(all_config, schema)
|
39
|
+
if not errors:
|
40
|
+
# aok
|
41
|
+
return
|
42
|
+
|
43
|
+
error(f"failed to validate runem config [yellow]{cfg_filepath}[/yellow]")
|
44
|
+
for err in errors:
|
45
|
+
path = ".".join(map(str, err.path)) or "<root>"
|
46
|
+
log(f" [yellow]{path}[/yellow]: {err.message}")
|
47
|
+
raise SystemExit("Config validation failed.")
|
runem/informative_dict.py
CHANGED
@@ -9,8 +9,7 @@ class InformativeDict(typing.Dict[K, V], typing.Generic[K, V]):
|
|
9
9
|
"""A dictionary type that prints out the available keys."""
|
10
10
|
|
11
11
|
def __getitem__(self, key: K) -> V:
|
12
|
-
"""Attempt to
|
13
|
-
found."""
|
12
|
+
"""Attempt to get item, raising a detailed exception if the key is not found."""
|
14
13
|
try:
|
15
14
|
return super().__getitem__(key)
|
16
15
|
except KeyError:
|
@@ -24,19 +23,25 @@ class ReadOnlyInformativeDict(InformativeDict[K, V], typing.Generic[K, V]):
|
|
24
23
|
"""A read-only variant of the above."""
|
25
24
|
|
26
25
|
def __setitem__(self, key: K, value: V) -> None:
|
26
|
+
"""Readonly object, setitem disallowed."""
|
27
27
|
raise NotImplementedError("This dictionary is read-only")
|
28
28
|
|
29
29
|
def __delitem__(self, key: K) -> None:
|
30
|
+
"""Readonly object, delitem disallowed."""
|
30
31
|
raise NotImplementedError("This dictionary is read-only")
|
31
32
|
|
32
33
|
def pop(self, *args: typing.Any, **kwargs: typing.Any) -> V:
|
34
|
+
"""Readonly object, pop disallowed."""
|
33
35
|
raise NotImplementedError("This dictionary is read-only")
|
34
36
|
|
35
37
|
def popitem(self) -> typing.Tuple[K, V]:
|
38
|
+
"""Readonly object, popitem disallowed."""
|
36
39
|
raise NotImplementedError("This dictionary is read-only")
|
37
40
|
|
38
41
|
def clear(self) -> None:
|
42
|
+
"""Readonly object, clear disallowed."""
|
39
43
|
raise NotImplementedError("This dictionary is read-only")
|
40
44
|
|
41
45
|
def update(self, *args: typing.Any, **kwargs: typing.Any) -> None:
|
46
|
+
"""Readonly object, update disallowed."""
|
42
47
|
raise NotImplementedError("This dictionary is read-only")
|
runem/job.py
CHANGED
@@ -71,7 +71,6 @@ class Job:
|
|
71
71
|
|
72
72
|
TODO: make a non-static member function
|
73
73
|
"""
|
74
|
-
|
75
74
|
# default to all file-tags
|
76
75
|
tags_for_files: typing.Iterable[str] = file_lists.keys()
|
77
76
|
use_default_tags: bool = job_tags is None
|
@@ -91,7 +90,6 @@ class Job:
|
|
91
90
|
|
92
91
|
TODO: make a non-static member function
|
93
92
|
"""
|
94
|
-
|
95
93
|
# First try one of the following keys.
|
96
94
|
valid_name_keys = ("label", "command")
|
97
95
|
for candidate in valid_name_keys:
|
@@ -101,6 +99,6 @@ class Job:
|
|
101
99
|
|
102
100
|
# The try the python-wrapper address
|
103
101
|
try:
|
104
|
-
return f
|
102
|
+
return f"{job['addr']['file']}.{job['addr']['function']}"
|
105
103
|
except KeyError:
|
106
104
|
raise NoJobName() # pylint: disable=raise-missing-from
|
runem/job_execute.py
CHANGED
@@ -105,7 +105,7 @@ def job_execute_inner(
|
|
105
105
|
reports = function(**all_k_args)
|
106
106
|
except BaseException: # pylint: disable=broad-exception-caught
|
107
107
|
# log that we hit an error on this job and re-raise
|
108
|
-
log(
|
108
|
+
log(prefix=False)
|
109
109
|
error(f"job: job '{Job.get_job_name(job_config)}' failed to complete!")
|
110
110
|
# re-raise
|
111
111
|
raise
|
@@ -132,12 +132,15 @@ def job_execute(
|
|
132
132
|
"""
|
133
133
|
this_id: str = str(uuid.uuid4())
|
134
134
|
running_jobs[this_id] = Job.get_job_name(job_config)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
135
|
+
try:
|
136
|
+
results = job_execute_inner(
|
137
|
+
job_config,
|
138
|
+
config_metadata,
|
139
|
+
file_lists,
|
140
|
+
**kwargs,
|
141
|
+
)
|
142
|
+
finally:
|
143
|
+
# Always tidy-up job statuses
|
144
|
+
completed_jobs[this_id] = running_jobs[this_id]
|
145
|
+
del running_jobs[this_id]
|
143
146
|
return results
|
runem/job_filter.py
CHANGED
@@ -101,21 +101,21 @@ def filter_jobs( # noqa: C901
|
|
101
101
|
if tags_to_run:
|
102
102
|
log(
|
103
103
|
f"filtering for tags {printable_set(tags_to_run)}",
|
104
|
-
|
104
|
+
prefix=True,
|
105
105
|
end="",
|
106
106
|
)
|
107
107
|
if tags_to_avoid:
|
108
108
|
if tags_to_run:
|
109
|
-
log(", ",
|
109
|
+
log(", ", prefix=False, end="")
|
110
110
|
else:
|
111
|
-
log(
|
111
|
+
log(prefix=True, end="")
|
112
112
|
log(
|
113
113
|
f"excluding jobs with tags {printable_set(tags_to_avoid)}",
|
114
|
-
|
114
|
+
prefix=False,
|
115
115
|
end="",
|
116
116
|
)
|
117
117
|
if tags_to_run or tags_to_avoid:
|
118
|
-
log(
|
118
|
+
log(prefix=False)
|
119
119
|
filtered_jobs: PhaseGroupedJobs = defaultdict(list)
|
120
120
|
for phase in config_metadata.phases:
|
121
121
|
if phase not in phases_to_run:
|
runem/job_wrapper_python.py
CHANGED
@@ -15,7 +15,6 @@ def _load_python_function_from_module(
|
|
15
15
|
function_to_load: str,
|
16
16
|
) -> JobFunction:
|
17
17
|
"""Given a job-description dynamically loads the test-function so we can call it."""
|
18
|
-
|
19
18
|
# first locate the module relative to the config file
|
20
19
|
abs_module_file_path: pathlib.Path = (
|
21
20
|
cfg_filepath.parent / module_file_path
|
@@ -56,9 +55,9 @@ def _load_python_function_from_module(
|
|
56
55
|
except AttributeError as err:
|
57
56
|
raise FunctionNotFound(
|
58
57
|
(
|
59
|
-
f"
|
60
|
-
f"exists in '{str(module_file_path)}' as expected in "
|
61
|
-
f"your config at '{str(cfg_filepath)}"
|
58
|
+
f"Check that function '[blue]{function_to_load}[/blue]' "
|
59
|
+
f"exists in '[blue]{str(module_file_path)}[/blue]' as expected in "
|
60
|
+
f"your config at '[blue]{str(cfg_filepath)}[/blue]'"
|
62
61
|
)
|
63
62
|
) from err
|
64
63
|
return function
|
@@ -109,9 +108,9 @@ def get_job_wrapper_py_func(
|
|
109
108
|
) from err
|
110
109
|
|
111
110
|
anchored_file_path = cfg_filepath.parent / module_file_path
|
112
|
-
assert (
|
113
|
-
anchored_file_path
|
114
|
-
)
|
111
|
+
assert anchored_file_path.exists(), (
|
112
|
+
f"{module_file_path} not found at {anchored_file_path}!"
|
113
|
+
)
|
115
114
|
|
116
115
|
module_name = module_file_path.stem.replace(" ", "_").replace("-", "_")
|
117
116
|
|
runem/log.py
CHANGED
@@ -5,7 +5,7 @@ from runem.blocking_print import blocking_print
|
|
5
5
|
|
6
6
|
def log(
|
7
7
|
msg: str = "",
|
8
|
-
|
8
|
+
prefix: typing.Optional[bool] = None,
|
9
9
|
end: typing.Optional[str] = None,
|
10
10
|
) -> None:
|
11
11
|
"""Thin wrapper around 'print', change the 'msg' & handles system-errors.
|
@@ -26,7 +26,10 @@ def log(
|
|
26
26
|
# Remove any markup as it will probably error, if unsanitised.
|
27
27
|
# msg = escape(msg)
|
28
28
|
|
29
|
-
if
|
29
|
+
if prefix is None:
|
30
|
+
prefix = True
|
31
|
+
|
32
|
+
if prefix:
|
30
33
|
# Make it clear that the message comes from `runem` internals.
|
31
34
|
msg = f"[light_slate_grey]runem[/light_slate_grey]: {msg}"
|
32
35
|
|
runem/report.py
CHANGED
@@ -46,12 +46,13 @@ def replace_bar_graph_characters(text: str, end_str: str, replace_char: str) ->
|
|
46
46
|
"""Replaces block characters in lines containing `end_str` with give char.
|
47
47
|
|
48
48
|
Args:
|
49
|
-
|
49
|
+
text (str): Text containing lines of bar-graphs (perhaps)
|
50
|
+
end_str (str): If contained by a line, the bar-graph shapes are replaced.
|
50
51
|
replace_char (str): The character to replace all bocks with
|
51
52
|
|
52
53
|
Returns:
|
53
|
-
|
54
|
-
|
54
|
+
str: The modified `text` with block characters replaced on specific
|
55
|
+
lines.
|
55
56
|
"""
|
56
57
|
# Define the block character and its light shade replacement
|
57
58
|
block_chars = (
|
runem/run_command.py
CHANGED
@@ -41,9 +41,7 @@ RecordSubJobTimeType = typing.Callable[[str, timedelta], None]
|
|
41
41
|
|
42
42
|
|
43
43
|
def parse_stdout(stdout: str, prefix: str) -> str:
|
44
|
-
"""Prefixes each line of
|
45
|
-
lines."""
|
46
|
-
|
44
|
+
"""Prefixes each line of output with a given label, except trailing new lines."""
|
47
45
|
# Edge case: Return the prefix immediately for an empty string
|
48
46
|
if not stdout:
|
49
47
|
return prefix
|
@@ -93,13 +91,13 @@ def _log_command_execution(
|
|
93
91
|
if verbose:
|
94
92
|
log(
|
95
93
|
f"running: start: [blue]{label}[/blue]: [yellow]{cmd_string}[yellow]",
|
96
|
-
|
94
|
+
prefix=decorate_logs,
|
97
95
|
)
|
98
96
|
if valid_exit_ids is not None:
|
99
97
|
valid_exit_strs = ",".join(str(exit_code) for exit_code in valid_exit_ids)
|
100
98
|
log(
|
101
99
|
f"\tallowed return ids are: [green]{valid_exit_strs}[/green]",
|
102
|
-
|
100
|
+
prefix=decorate_logs,
|
103
101
|
)
|
104
102
|
|
105
103
|
if env_overrides:
|
@@ -108,11 +106,11 @@ def _log_command_execution(
|
|
108
106
|
)
|
109
107
|
log(
|
110
108
|
f"ENV OVERRIDES: [yellow]{env_overrides_as_string} {cmd_string}[/yellow]",
|
111
|
-
|
109
|
+
prefix=decorate_logs,
|
112
110
|
)
|
113
111
|
|
114
112
|
if cwd:
|
115
|
-
log(f"cwd: {str(cwd)}",
|
113
|
+
log(f"cwd: {str(cwd)}", prefix=decorate_logs)
|
116
114
|
|
117
115
|
|
118
116
|
def run_command( # noqa: C901
|
@@ -175,7 +173,7 @@ def run_command( # noqa: C901
|
|
175
173
|
parse_stdout(
|
176
174
|
line, prefix=f"[green]| [/green][blue]{label}[/blue]: "
|
177
175
|
),
|
178
|
-
|
176
|
+
prefix=False,
|
179
177
|
)
|
180
178
|
|
181
179
|
# Wait for the subprocess to finish and get the exit code
|
@@ -206,7 +204,7 @@ def run_command( # noqa: C901
|
|
206
204
|
error_string = (
|
207
205
|
f"runem: [red bold]FATAL[/red bold]: command failed: [blue]{label}[/blue]"
|
208
206
|
f"\n\t[yellow]{env_overrides_as_string}{cmd_string}[/yellow]"
|
209
|
-
f"\n[red underline]| ERROR[/red underline]"
|
207
|
+
f"\n[red underline]| ERROR[/red underline]: [blue]{label}[/blue]"
|
210
208
|
f"\n{str(parsed_stdout)}"
|
211
209
|
f"\n[red underline]| ERROR END[/red underline]"
|
212
210
|
)
|
@@ -219,7 +217,7 @@ def run_command( # noqa: C901
|
|
219
217
|
if verbose:
|
220
218
|
log(
|
221
219
|
f"running: done: [blue]{label}[/blue]: [yellow]{cmd_string}[/yellow]",
|
222
|
-
|
220
|
+
prefix=decorate_logs,
|
223
221
|
)
|
224
222
|
|
225
223
|
if record_sub_job_time is not None:
|
runem/runem.py
CHANGED
@@ -19,6 +19,7 @@ We do:
|
|
19
19
|
- time tests and tell you what used the most time, and how much time run-tests saved
|
20
20
|
you
|
21
21
|
"""
|
22
|
+
|
22
23
|
import contextlib
|
23
24
|
import multiprocessing
|
24
25
|
import os
|
@@ -48,6 +49,7 @@ from runem.log import error, log, warn
|
|
48
49
|
from runem.report import report_on_run
|
49
50
|
from runem.run_command import RunemJobError
|
50
51
|
from runem.types.common import OrderedPhases, PhaseName
|
52
|
+
from runem.types.errors import SystemExitBad
|
51
53
|
from runem.types.filters import FilePathListLookup
|
52
54
|
from runem.types.hooks import HookName
|
53
55
|
from runem.types.runem_config import Config, Jobs, PhaseGroupedJobs
|
@@ -68,7 +70,6 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
|
|
68
70
|
|
69
71
|
Return a ConfigMetadata object with all the required information.
|
70
72
|
"""
|
71
|
-
|
72
73
|
# Because we want to be able to show logging whilst parsing .runem.yml config, we
|
73
74
|
# need to check the state of the logging-verbosity switches here, manually, as well.
|
74
75
|
verbose = "--verbose" in argv
|
@@ -105,13 +106,14 @@ def _update_progress(
|
|
105
106
|
"""Updates progress report periodically for running tasks.
|
106
107
|
|
107
108
|
Args:
|
108
|
-
|
109
|
+
phase (str): The currently running phase.
|
109
110
|
running_jobs (Dict[str, str]): The currently running jobs.
|
111
|
+
completed_jobs (Dict[str, str]): The jobs that have finished work.
|
110
112
|
all_jobs (Jobs): All jobs, encompassing both completed and running jobs.
|
111
113
|
is_running (ValueProxy[bool]): Flag indicating if jobs are still running.
|
112
114
|
num_workers (int): Indicates the number of workers performing the jobs.
|
115
|
+
show_spinner (bool): Whether to show the animated spinner or not.
|
113
116
|
"""
|
114
|
-
|
115
117
|
last_running_jobs_set: typing.Set[str] = set()
|
116
118
|
|
117
119
|
# Using the `rich` module to show a loading spinner on console
|
@@ -132,7 +134,8 @@ def _update_progress(
|
|
132
134
|
"blue",
|
133
135
|
) # Reflect current running jobs accurately
|
134
136
|
report: str = (
|
135
|
-
f"[green]{phase}[/green]: {progress}({num_workers}):
|
137
|
+
f"[green]{phase}[/green]: {progress}({num_workers}): "
|
138
|
+
f"{running_jobs_list}"
|
136
139
|
)
|
137
140
|
if show_spinner:
|
138
141
|
assert isinstance(spinner_ctx, Status)
|
@@ -293,8 +296,8 @@ def _main(
|
|
293
296
|
log(f"found {len(file_lists)} batches, ", end="")
|
294
297
|
for tag in sorted(file_lists.keys()):
|
295
298
|
file_list = file_lists[tag]
|
296
|
-
log(f"{len(file_list)} '{tag}' files, ",
|
297
|
-
log(
|
299
|
+
log(f"{len(file_list)} '{tag}' files, ", prefix=False, end="")
|
300
|
+
log(prefix=False) # new line
|
298
301
|
|
299
302
|
filtered_jobs_by_phase: PhaseGroupedJobs = filter_jobs(
|
300
303
|
config_metadata=config_metadata,
|
@@ -371,7 +374,7 @@ def timed_main(argv: typing.List[str]) -> None:
|
|
371
374
|
# we got a failure somewhere, now that we've reported the timings we
|
372
375
|
# re-raise.
|
373
376
|
error(failure_exception.stdout)
|
374
|
-
raise failure_exception
|
377
|
+
raise SystemExitBad(1) from failure_exception
|
375
378
|
|
376
379
|
|
377
380
|
if __name__ == "__main__":
|
runem/schema.yml
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#%RAML 1.0 (← just a comment so VS Code picks up YAML)
|
2
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
3
|
+
$title: Runem pipeline definition
|
4
|
+
$defs:
|
5
|
+
# ----- common pieces -------------------------------------------------------
|
6
|
+
phase:
|
7
|
+
type: string
|
8
|
+
|
9
|
+
addr:
|
10
|
+
type: object
|
11
|
+
required: [file, function]
|
12
|
+
additionalProperties: false
|
13
|
+
properties:
|
14
|
+
file: { type: string, minLength: 1 }
|
15
|
+
function: { type: string, minLength: 1 }
|
16
|
+
|
17
|
+
ctx:
|
18
|
+
type: object
|
19
|
+
additionalProperties: false
|
20
|
+
properties:
|
21
|
+
cwd:
|
22
|
+
oneOf:
|
23
|
+
- type: string
|
24
|
+
- type: array
|
25
|
+
minItems: 1
|
26
|
+
items: { type: string, minLength: 1 }
|
27
|
+
params:
|
28
|
+
type: object # free‑form kv‑pairs for hooks
|
29
|
+
additionalProperties: true
|
30
|
+
|
31
|
+
when:
|
32
|
+
type: object
|
33
|
+
required: [phase]
|
34
|
+
additionalProperties: false
|
35
|
+
properties:
|
36
|
+
phase: { $ref: "#/$defs/phase" }
|
37
|
+
tags:
|
38
|
+
type: array
|
39
|
+
items: { type: string, minLength: 1 }
|
40
|
+
uniqueItems: true
|
41
|
+
|
42
|
+
# ----- top‑level entity types ---------------------------------------------
|
43
|
+
config:
|
44
|
+
type: object
|
45
|
+
required: []
|
46
|
+
additionalProperties: false
|
47
|
+
properties:
|
48
|
+
min_version:
|
49
|
+
type: string
|
50
|
+
|
51
|
+
phases:
|
52
|
+
type: array
|
53
|
+
minItems: 1
|
54
|
+
items: { $ref: "#/$defs/phase" }
|
55
|
+
uniqueItems: true
|
56
|
+
|
57
|
+
files:
|
58
|
+
type: [array, 'null']
|
59
|
+
minItems: 0
|
60
|
+
items:
|
61
|
+
type: object
|
62
|
+
required: [filter]
|
63
|
+
additionalProperties: false
|
64
|
+
properties:
|
65
|
+
filter:
|
66
|
+
type: object
|
67
|
+
required: [tag, regex]
|
68
|
+
additionalProperties: false
|
69
|
+
properties:
|
70
|
+
tag: { type: string, minLength: 1 }
|
71
|
+
regex: { type: string, minLength: 1 } # leave pattern‑checking to the engine
|
72
|
+
|
73
|
+
options:
|
74
|
+
type: [array, 'null']
|
75
|
+
minItems: 0
|
76
|
+
items:
|
77
|
+
type: object
|
78
|
+
required: [option]
|
79
|
+
additionalProperties: false
|
80
|
+
properties:
|
81
|
+
option:
|
82
|
+
type: object
|
83
|
+
required: [name, type, default, desc]
|
84
|
+
additionalProperties: false
|
85
|
+
properties:
|
86
|
+
name: { type: string, minLength: 1 }
|
87
|
+
alias: { type: string, minLength: 1 }
|
88
|
+
desc: { type: string, minLength: 1 }
|
89
|
+
type:
|
90
|
+
const: bool # always "bool" per sample
|
91
|
+
default: { type: boolean }
|
92
|
+
|
93
|
+
hook:
|
94
|
+
type: object
|
95
|
+
required: [hook_name]
|
96
|
+
oneOf:
|
97
|
+
- required: [command]
|
98
|
+
- required: [addr]
|
99
|
+
additionalProperties: false
|
100
|
+
properties:
|
101
|
+
hook_name: { type: string, minLength: 1 }
|
102
|
+
addr: { $ref: "#/$defs/addr" }
|
103
|
+
command: { type: string, minLength: 1 }
|
104
|
+
|
105
|
+
job:
|
106
|
+
type: object
|
107
|
+
oneOf:
|
108
|
+
- required: [command]
|
109
|
+
- required: [addr]
|
110
|
+
additionalProperties: false
|
111
|
+
properties:
|
112
|
+
label: { type: string, minLength: 1 }
|
113
|
+
addr: { $ref: "#/$defs/addr" }
|
114
|
+
command: { type: string, minLength: 1 }
|
115
|
+
ctx: { $ref: "#/$defs/ctx" }
|
116
|
+
when: { $ref: "#/$defs/when" }
|
117
|
+
oneOf:
|
118
|
+
- required: [addr] # either addr
|
119
|
+
- required: [command] # or command, but not both
|
120
|
+
not:
|
121
|
+
anyOf:
|
122
|
+
- required: [addr, command] # forbid both together
|
123
|
+
|
124
|
+
# ---------- ROOT -------------------------------------------------------------
|
125
|
+
type: array
|
126
|
+
minItems: 1
|
127
|
+
items:
|
128
|
+
type: object
|
129
|
+
additionalProperties: false
|
130
|
+
oneOf:
|
131
|
+
- required: [config]
|
132
|
+
- required: [hook]
|
133
|
+
- required: [job]
|
134
|
+
properties:
|
135
|
+
config: { $ref: "#/$defs/config" }
|
136
|
+
hook: { $ref: "#/$defs/hook" }
|
137
|
+
job: { $ref: "#/$defs/job" }
|
runem/types/errors.py
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
|
1
4
|
class FunctionNotFound(ValueError):
|
2
5
|
"""Thrown when the test-function cannot be found."""
|
3
6
|
|
4
7
|
pass
|
8
|
+
|
9
|
+
|
10
|
+
class SystemExitBad(SystemExit):
|
11
|
+
def __init__(self, code: Optional[int] = None) -> None:
|
12
|
+
super().__init__()
|
13
|
+
self.code = 1 if code is None else code # non-zero bad exit code
|
14
|
+
assert self.code > 0, "A bad exit code should be non-zero and >0"
|
runem/types/hooks.py
CHANGED
runem/types/types_jobs.py
CHANGED
@@ -1,28 +1,25 @@
|
|
1
|
-
"""
|
1
|
+
"""Job‑typing helpers.
|
2
|
+
|
3
|
+
Cross‑version advice
|
4
|
+
--------------------
|
5
|
+
* Type variadic keyword arguments as **kwargs: Unpack[KwArgsT] for clarity.
|
6
|
+
* Always import Unpack from ``typing_extensions``.
|
7
|
+
- Std‑lib Unpack appears only in Py 3.12+.
|
8
|
+
- ``typing_extensions`` works on 3.9‑3.12, so one import path keeps
|
9
|
+
mypy/pyright happy without conditional logic.
|
10
|
+
|
11
|
+
Example:
|
12
|
+
~~~~~~~
|
13
|
+
from typing_extensions import TypedDict, Unpack
|
14
|
+
|
15
|
+
|
16
|
+
class SaveKwArgs(TypedDict):
|
17
|
+
path: str
|
18
|
+
overwrite: bool
|
19
|
+
|
2
20
|
|
3
|
-
|
4
|
-
|
5
|
-
We have tried several ways to define a Generic type that encapsulates
|
6
|
-
`**kwargs: SingleType`
|
7
|
-
... but none of the solutions worked with python 3.9 -> 3.12 and mypy 1.9.0,
|
8
|
-
so we have to recommend instead using:
|
9
|
-
`**kwargs: Unpack[KwArgsType]`
|
10
|
-
|
11
|
-
For this to work across versions of python where support for Unpack changes;
|
12
|
-
for example `Unpack` is a python 3.12 feature, but available in the
|
13
|
-
`typing_extensions` module.
|
14
|
-
|
15
|
-
So, for now, it looks like we get away with importing `Unpack` from the
|
16
|
-
`typing_extensions` module, even in python 3.12, so we will use, and
|
17
|
-
recommend using, the `typing_extensions` of `Unpack`, until it becomes
|
18
|
-
obsolete.
|
19
|
-
|
20
|
-
Alternatively, we can use the following, but it's unnecessarily verbose.
|
21
|
-
|
22
|
-
if sys.version_info >= (3, 12): # pragma: no coverage
|
23
|
-
from typing import Unpack
|
24
|
-
else: # pragma: no coverage
|
25
|
-
from typing_extensions import Unpack
|
21
|
+
def save_job(**kwargs: Unpack[SaveKwArgs]) -> None:
|
22
|
+
...
|
26
23
|
"""
|
27
24
|
|
28
25
|
import pathlib
|
runem/yaml_utils.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import pathlib
|
2
|
+
import typing
|
3
|
+
|
4
|
+
import yaml
|
5
|
+
|
6
|
+
|
7
|
+
def load_yaml_object(yaml_file: pathlib.Path) -> typing.Any:
|
8
|
+
"""Loads using full_load, a yaml file.
|
9
|
+
|
10
|
+
This is likely to have safety concerns in non-trusted projects.
|
11
|
+
|
12
|
+
Returns:
|
13
|
+
YAML Loader object: the full PyYAML loader object.
|
14
|
+
"""
|
15
|
+
# Do a full, untrusted load of the runem config
|
16
|
+
# TODO: work out safety concerns of this
|
17
|
+
with yaml_file.open("r+", encoding="utf-8") as file_handle:
|
18
|
+
full_yaml_object: typing.Any = yaml.full_load(file_handle)
|
19
|
+
return full_yaml_object
|
runem/yaml_validation.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
from typing import Any, List
|
2
|
+
|
3
|
+
from jsonschema import Draft202012Validator, ValidationError
|
4
|
+
|
5
|
+
# For now just return the raw ValidationErrors as a list
|
6
|
+
ValidationErrors = List[ValidationError]
|
7
|
+
|
8
|
+
|
9
|
+
def validate_yaml(yaml_data: Any, schema: Any) -> ValidationErrors:
|
10
|
+
"""Validates the give yaml data against the given schema, returning any errors.
|
11
|
+
|
12
|
+
We use more future-looking validation so that we can have richer and more
|
13
|
+
descriptive schema.
|
14
|
+
|
15
|
+
Params:
|
16
|
+
instance: JSON data loaded via `load_json` or similar
|
17
|
+
schema: schema object compatible with a Draft202012Validator
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
ValidationErrors: a sorted list of errors in the file, empty if none found
|
21
|
+
"""
|
22
|
+
validator = Draft202012Validator(schema)
|
23
|
+
errors: ValidationErrors = sorted(
|
24
|
+
validator.iter_errors(yaml_data),
|
25
|
+
key=lambda e: e.path,
|
26
|
+
)
|
27
|
+
|
28
|
+
return errors
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: runem
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.1
|
4
4
|
Summary: Awesome runem created by lursight
|
5
5
|
Author: lursight
|
6
6
|
License: Specify your license here
|
@@ -16,31 +16,32 @@ Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
17
17
|
Requires-Dist: packaging>=22.0
|
18
18
|
Requires-Dist: PyYAML>=5.0.0
|
19
|
+
Requires-Dist: jsonschema>=4.22
|
19
20
|
Requires-Dist: rich>10.0.0
|
20
21
|
Requires-Dist: typing_extensions>3.0.0
|
21
22
|
Provides-Extra: tests
|
22
|
-
Requires-Dist: black==24.10.0; extra == "tests"
|
23
23
|
Requires-Dist: coverage==7.5; extra == "tests"
|
24
|
-
Requires-Dist: docformatter==1.7.5; extra == "tests"
|
25
24
|
Requires-Dist: flake8-bugbear==24.2.6; extra == "tests"
|
26
25
|
Requires-Dist: flake8==7.0.0; extra == "tests"
|
27
26
|
Requires-Dist: gitchangelog==3.0.4; extra == "tests"
|
28
|
-
Requires-Dist: isort==5.13.2; extra == "tests"
|
29
27
|
Requires-Dist: mkdocs==1.5.3; extra == "tests"
|
30
28
|
Requires-Dist: mypy==1.9.0; extra == "tests"
|
31
29
|
Requires-Dist: pydocstyle==6.3.0; extra == "tests"
|
32
|
-
Requires-Dist: pylint==3.
|
30
|
+
Requires-Dist: pylint==3.3.6; extra == "tests"
|
33
31
|
Requires-Dist: pylama==8.4.1; extra == "tests"
|
34
|
-
Requires-Dist: pytest-cov==6.
|
32
|
+
Requires-Dist: pytest-cov==6.1.1; extra == "tests"
|
35
33
|
Requires-Dist: pytest-profiling==1.7.0; extra == "tests"
|
36
34
|
Requires-Dist: pytest-xdist==3.6.1; extra == "tests"
|
37
|
-
Requires-Dist: pytest==8.3.
|
35
|
+
Requires-Dist: pytest==8.3.5; extra == "tests"
|
36
|
+
Requires-Dist: ruff==0.11.6; extra == "tests"
|
38
37
|
Requires-Dist: setuptools; extra == "tests"
|
39
38
|
Requires-Dist: termplotlib==0.3.9; extra == "tests"
|
40
39
|
Requires-Dist: tox; extra == "tests"
|
41
40
|
Requires-Dist: types-PyYAML==6.0.12.20240311; extra == "tests"
|
42
41
|
Requires-Dist: requests-mock==1.11.0; extra == "tests"
|
42
|
+
Requires-Dist: types-jsonschema; extra == "tests"
|
43
43
|
Requires-Dist: types-setuptools; extra == "tests"
|
44
|
+
Dynamic: license-file
|
44
45
|
|
45
46
|
<!-- [](https://codecov.io/gh/lursight/runem) -->
|
46
47
|
[](https://github.com/lursight/runem/actions/workflows/main.yml)
|
@@ -1,41 +1,46 @@
|
|
1
|
-
runem/VERSION,sha256=
|
1
|
+
runem/VERSION,sha256=kCMRx7s4-hZY_0A972FY7nN4_p1uLihPocOHNcMb0ws,6
|
2
2
|
runem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
runem/__main__.py,sha256=dsOiVZegpfK9JOs5n7UmbX5iwwbj7iFkEbLoVeEgAn4,136
|
4
4
|
runem/base.py,sha256=EZfR7FIlwEdU9Vfe47Wk2DOO8GQqpKxxLNKp6YHueZ4,316
|
5
|
-
runem/blocking_print.py,sha256=
|
5
|
+
runem/blocking_print.py,sha256=UKU_BM7wzPvn6RKw_tFPw4Lzpjdtbwq5MIaAdL1_zN8,2435
|
6
6
|
runem/cli.py,sha256=wEt_Jnumhl8SiOdKdSJzLkJpWv6n3_Odhi_HeIixr1k,134
|
7
|
-
runem/command_line.py,sha256=
|
8
|
-
runem/config.py,sha256=
|
7
|
+
runem/command_line.py,sha256=Q5xH7kGzc3YpIbGpU43B_pwKvp-LchSW7IgfGnf2Ke0,14437
|
8
|
+
runem/config.py,sha256=PWj4wj90WrRiGuXOUy6mpevJXQM57epDWG5NYDrGK2w,6049
|
9
9
|
runem/config_metadata.py,sha256=krDomUcADsAeUQrxwNmOS58eeaNIlqmhWIKWv8mUH4A,3300
|
10
|
-
runem/config_parse.py,sha256=
|
10
|
+
runem/config_parse.py,sha256=A1pLfEL5CEaexH-Kn_PYthAa9mwmOS5UrD0kEbDcqPo,13843
|
11
|
+
runem/config_validate.py,sha256=gtatObD1qBkEK-0CJ2rJPT5A7EBTWE_9V_AiGZZ1FQI,1424
|
11
12
|
runem/files.py,sha256=59boeFvUANYOS-PllIjeKIht6lNINZ43WxahDg90oAc,4392
|
12
13
|
runem/hook_manager.py,sha256=H0TL3HCqU2mgKm_-dgCD7TsK5T1bLT4g7x6kpytMPhU,4350
|
13
|
-
runem/informative_dict.py,sha256=
|
14
|
-
runem/job.py,sha256=
|
15
|
-
runem/job_execute.py,sha256
|
16
|
-
runem/job_filter.py,sha256=
|
14
|
+
runem/informative_dict.py,sha256=4UUE_RU6zEX1JFFlVUK5EMdmPdJ5ZOo5scU93fYi0iU,1831
|
15
|
+
runem/job.py,sha256=SX_uHaFxocFm2QT2hRTfAv7mADnMj2th85L_JzYwS-4,3440
|
16
|
+
runem/job_execute.py,sha256=Sn5v7KoyCOw2FH-bJaur_zYChwmvCXhmdZ692B3SZA8,4792
|
17
|
+
runem/job_filter.py,sha256=4KMjsI5-tiK0b90RTlivxm5Xdt0gWdRz30voqqL_ijI,5374
|
17
18
|
runem/job_runner_simple_command.py,sha256=iP5an6yixW8o4C0ZBtu6csb-oVK3Q62ZZgtHBmxlXaU,2428
|
18
19
|
runem/job_wrapper.py,sha256=q5GtopZ5vhSJ581rwU4-lF9KnbL3ZYgOC8fqaCnXD_g,983
|
19
|
-
runem/job_wrapper_python.py,sha256=
|
20
|
-
runem/log.py,sha256=
|
20
|
+
runem/job_wrapper_python.py,sha256=9rM6OXs0rNRVNwZ7OVB6L0K7RocEpiG0N-qIMuprjRA,4335
|
21
|
+
runem/log.py,sha256=MEGWEBeFs0YVkr_U9UXnl4Isqc5MvGbEn_I3fo-V35Q,1422
|
21
22
|
runem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
runem/report.py,sha256=
|
23
|
-
runem/run_command.py,sha256=
|
24
|
-
runem/runem.py,sha256=
|
23
|
+
runem/report.py,sha256=qiGu0PPcbeFh9KJHtFKjH_LQHDfYuDgLVbUA81fNsLI,9124
|
24
|
+
runem/run_command.py,sha256=jz-vP9yw3WDM_0mc5KrBpqFcASSQUFB9zz9u_le-m8A,7692
|
25
|
+
runem/runem.py,sha256=Lm0mg8oTe7pCvQVZob6KR6zq7G5xoMTjrCfhBlCQwZQ,13436
|
25
26
|
runem/runem_version.py,sha256=MbETwZO2Tb1Y3hX_OYZjKepEMKA1cjNvr-7Cqhz6e3s,271
|
27
|
+
runem/schema.yml,sha256=LmQVtVbziaH_Z3aaE5-njR7qPYTP8qqruNQt8rVfv5M,3720
|
26
28
|
runem/utils.py,sha256=MEYfox09rLvb6xmay_3rV1cWmdqMbhaAjOomYGNk15k,602
|
27
|
-
runem/
|
29
|
+
runem/yaml_utils.py,sha256=RyAvEp679JvavE0Kbs263Ofh8_tTXXHUdtblSeHovfU,554
|
30
|
+
runem/yaml_validation.py,sha256=j8vnufeV8FRwg15wbitL_QLP4nh25Rx1L_14Oc2puwU,877
|
31
|
+
runem/cli/initialise_options.py,sha256=91QjAHdfhUNI9nWVDnHAqNshxCXytXQfbsEaQo3FMLc,917
|
28
32
|
runem/types/__init__.py,sha256=0bWG7hE7VeqJ2oIu-xhrqQud8hcNp6WNbF3uMfT_n9g,314
|
29
33
|
runem/types/common.py,sha256=gPMSoJ3yRUYjHnoviRrpSg0gRwsGLFGWGpbTWkq4jX0,279
|
30
|
-
runem/types/errors.py,sha256=
|
34
|
+
runem/types/errors.py,sha256=9A4V5qT3ofolqRxrqBI9i9jX3r60x8nQnQnt9w1Y3co,403
|
31
35
|
runem/types/filters.py,sha256=8R5fyMssN0ISGBilJhEtbdHFl6OP7uI51WKkB5SH6EA,255
|
32
|
-
runem/types/hooks.py,sha256=
|
36
|
+
runem/types/hooks.py,sha256=9Q5THuBEH0Asdx5cj0caNmO54RckwR0tT3AgRXFROhs,292
|
33
37
|
runem/types/options.py,sha256=y8_hyWYvhalC9-kZbvoDtxm0trZgyyGcswQqfuQy_pM,265
|
34
38
|
runem/types/runem_config.py,sha256=qG_bghm5Nr-ZTbaZbf1v8Fx447V-hgEvvRy5NZ3t-Io,5141
|
35
|
-
runem/types/types_jobs.py,sha256=
|
39
|
+
runem/types/types_jobs.py,sha256=99b2TLwBiqjOurxRWWU7E9BlphR0KketUUSqUD_Kh5c,4432
|
40
|
+
runem-0.7.1.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
36
41
|
scripts/test_hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
42
|
scripts/test_hooks/json_validators.py,sha256=N2FyWcpjWzfFGycXLo-ecNLJkxTFPPbqPfVBcJLBlb4,967
|
38
|
-
scripts/test_hooks/py.py,sha256=
|
43
|
+
scripts/test_hooks/py.py,sha256=Nku44p6ZqJb5d9uo2bPfpeqf8g1LDHRDqL4ou0Y0G_k,10520
|
39
44
|
scripts/test_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
45
|
scripts/test_hooks/runem_hooks.py,sha256=FJMuDBEOz3dr9gBW3WW6yKbUJs_LFXb3klpqSzCAZRk,628
|
41
46
|
scripts/test_hooks/yarn.py,sha256=1QsG1rKAclpZoqp86ntkuvzYaYN4UkEvO0JhO2Kf5C8,1082
|
@@ -44,9 +49,8 @@ tests/data/help_output.3.10.txt,sha256=5TUpNITVL6pD5BpFAl-Orh3vkOpStveijZzvgJuI_
|
|
44
49
|
tests/data/help_output.3.11.txt,sha256=ycrF-xKgdQ8qrWzkkR-vbHe7NulUTsCsS0_Gda8xYDs,4162
|
45
50
|
tests/test_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
51
|
tests/test_types/test_public_api.py,sha256=QHiwt7CetQur65JSbFRnOzQxhCJkX5MVLymHHVd_6yc,160
|
47
|
-
runem-0.
|
48
|
-
runem-0.
|
49
|
-
runem-0.
|
50
|
-
runem-0.
|
51
|
-
runem-0.
|
52
|
-
runem-0.6.0.dist-info/RECORD,,
|
52
|
+
runem-0.7.1.dist-info/METADATA,sha256=wX6dRVH8E905TMINRtxhwtGoVeyaokkjrQcg-2h1TgY,5894
|
53
|
+
runem-0.7.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
54
|
+
runem-0.7.1.dist-info/entry_points.txt,sha256=nu0g_vBeuPihYtimbtlNusxWovylMppvJ8UxdJlJfvM,46
|
55
|
+
runem-0.7.1.dist-info/top_level.txt,sha256=NkdxkwLKNNhxItveR2KqNqTshTZ268m5D7SjJEmG4-Y,20
|
56
|
+
runem-0.7.1.dist-info/RECORD,,
|
scripts/test_hooks/py.py
CHANGED
@@ -9,7 +9,69 @@ from runem.run_command import RunCommandUnhandledError, run_command
|
|
9
9
|
from runem.types import FilePathList, JobKwargs, JobName, JobReturnData, Options
|
10
10
|
|
11
11
|
|
12
|
-
def
|
12
|
+
def _job_py_code_ruff_reformat(
|
13
|
+
**kwargs: typing.Any,
|
14
|
+
) -> None:
|
15
|
+
"""Runs python formatting code in serial order as one influences the other."""
|
16
|
+
label: JobName = kwargs["label"]
|
17
|
+
options: Options = kwargs["options"]
|
18
|
+
python_files: FilePathList = kwargs["file_list"]
|
19
|
+
|
20
|
+
# put into 'check' mode if requested on the command line
|
21
|
+
extra_args = []
|
22
|
+
if options["check-only"]:
|
23
|
+
extra_args.append("--check")
|
24
|
+
|
25
|
+
if not options["ruff"]:
|
26
|
+
# Do not run `ruff` if opted-out
|
27
|
+
return
|
28
|
+
|
29
|
+
# If ruff is enabled we do NOT run black etc. because ruff does that
|
30
|
+
# for us, faster and better.
|
31
|
+
ruff_format_cmd = [
|
32
|
+
"python3",
|
33
|
+
"-m",
|
34
|
+
"ruff",
|
35
|
+
"format",
|
36
|
+
*extra_args,
|
37
|
+
*python_files,
|
38
|
+
]
|
39
|
+
kwargs["label"] = f"{label} ruff"
|
40
|
+
run_command(cmd=ruff_format_cmd, **kwargs)
|
41
|
+
|
42
|
+
|
43
|
+
def _job_py_ruff_lint(
|
44
|
+
**kwargs: typing.Any,
|
45
|
+
) -> None:
|
46
|
+
"""Runs python formatting code in serial order as one influences the other."""
|
47
|
+
label: JobName = kwargs["label"]
|
48
|
+
options: Options = kwargs["options"]
|
49
|
+
python_files: FilePathList = kwargs["file_list"]
|
50
|
+
|
51
|
+
# try to auto-fix issues (one benefit of ruff over flake8 etc.)
|
52
|
+
extra_args = []
|
53
|
+
if options["fix"]:
|
54
|
+
extra_args.append("--fix")
|
55
|
+
|
56
|
+
if not options["ruff"]:
|
57
|
+
# Do not run `ruff` if opted-out
|
58
|
+
return
|
59
|
+
|
60
|
+
# If ruff is enabled we do NOT run black etc. because ruff does that
|
61
|
+
# for us, faster and better.
|
62
|
+
ruff_lint_cmd = [
|
63
|
+
"python3",
|
64
|
+
"-m",
|
65
|
+
"ruff",
|
66
|
+
"check",
|
67
|
+
*extra_args,
|
68
|
+
*python_files,
|
69
|
+
]
|
70
|
+
kwargs["label"] = f"{label} ruff"
|
71
|
+
run_command(cmd=ruff_lint_cmd, **kwargs)
|
72
|
+
|
73
|
+
|
74
|
+
def _job_py_code_reformat_deprecated(
|
13
75
|
**kwargs: Unpack[JobKwargs],
|
14
76
|
) -> None:
|
15
77
|
"""Runs python formatting code in serial order as one influences the other."""
|
File without changes
|
File without changes
|
File without changes
|