runem 0.5.0__py3-none-any.whl → 0.6.0__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 CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
runem/blocking_print.py CHANGED
@@ -13,7 +13,14 @@ def _reset_console() -> Console:
13
13
 
14
14
  RICH_CONSOLE = Console(
15
15
  log_path=False, # Do NOT print the source path.
16
- markup=False, # Do NOT print markup e.g. `[blink]Don't Panic![/blink]`.
16
+ # We allow markup here, BUT stdout/stderr from other procs should have
17
+ # `escape()` called on them so they don't error here.
18
+ # This means 'rich' effects/colors can be judiciously applied:
19
+ # e.g. `[blink]Don't Panic![/blink]`.
20
+ markup=True,
21
+ # `highlight` is what colourises string and number in print() calls.
22
+ # We do not want this to be auto-magic.
23
+ highlight=False,
17
24
  )
18
25
  return RICH_CONSOLE
19
26
 
@@ -31,7 +38,8 @@ def _reset_console_for_tests() -> None:
31
38
  RICH_CONSOLE = Console(
32
39
  log_path=False, # Do NOT print the source path.
33
40
  log_time=False, # Do not prefix with log time e.g. `[time] log message`.
34
- markup=False, # Do NOT print markup e.g. `[blink]Don't Panic![/blink]`.
41
+ markup=True, # Allow some markup e.g. `[blink]Don't Panic![/blink]`.
42
+ highlight=False,
35
43
  width=999, # A very wide width.
36
44
  )
37
45
 
runem/log.py CHANGED
@@ -3,13 +3,32 @@ import typing
3
3
  from runem.blocking_print import blocking_print
4
4
 
5
5
 
6
- def log(msg: str = "", decorate: bool = True, end: typing.Optional[str] = None) -> None:
6
+ def log(
7
+ msg: str = "",
8
+ decorate: bool = True,
9
+ end: typing.Optional[str] = None,
10
+ ) -> None:
7
11
  """Thin wrapper around 'print', change the 'msg' & handles system-errors.
8
12
 
9
13
  One way we change it is to decorate the output with 'runem'
14
+
15
+ Parameters:
16
+ msg: str - the message to log out. Any `rich` markup will be escaped
17
+ and not applied.
18
+ decorate: str - whether to add runem-specific prefix text. We do this
19
+ to identify text that comes from the app vs text that
20
+ comes from hooks or other third-parties.
21
+ end: Optional[str] - same as the end option used by `print()` and
22
+ `rich`
23
+ Returns:
24
+ Nothing
10
25
  """
26
+ # Remove any markup as it will probably error, if unsanitised.
27
+ # msg = escape(msg)
28
+
11
29
  if decorate:
12
- msg = f"runem: {msg}"
30
+ # Make it clear that the message comes from `runem` internals.
31
+ msg = f"[light_slate_grey]runem[/light_slate_grey]: {msg}"
13
32
 
14
33
  # print in a blocking manner, waiting for system resources to free up if a
15
34
  # runem job is contending on stdout or similar.
@@ -17,8 +36,8 @@ def log(msg: str = "", decorate: bool = True, end: typing.Optional[str] = None)
17
36
 
18
37
 
19
38
  def warn(msg: str) -> None:
20
- log(f"WARNING: {msg}")
39
+ log(f"[yellow]WARNING[/yellow]: {msg}")
21
40
 
22
41
 
23
42
  def error(msg: str) -> None:
24
- log(f"ERROR: {msg}")
43
+ log(f"[red]ERROR[/red]: {msg}")
runem/report.py CHANGED
@@ -211,7 +211,10 @@ def _print_reports_by_phase(
211
211
  for job_report_url_info in report_urls:
212
212
  if not job_report_url_info:
213
213
  continue
214
- log(f"report: {str(job_report_url_info[0])}: {str(job_report_url_info[1])}")
214
+ log(
215
+ f"report: [blue]{str(job_report_url_info[0])}[/blue]: "
216
+ f"{str(job_report_url_info[1])}"
217
+ )
215
218
 
216
219
 
217
220
  def report_on_run(
runem/run_command.py CHANGED
@@ -7,17 +7,33 @@ from subprocess import STDOUT as SUBPROCESS_STDOUT
7
7
  from subprocess import Popen
8
8
  from timeit import default_timer as timer
9
9
 
10
+ from rich.markup import escape
11
+
10
12
  from runem.log import log
11
13
 
12
14
  TERMINAL_WIDTH = 86
13
15
 
14
16
 
15
- class RunCommandBadExitCode(RuntimeError):
16
- pass
17
+ class RunemJobError(RuntimeError):
18
+ """An exception type that stores the stdout/stderr.
19
+
20
+ Designed so that we do not print the full stdout via the exception stack, instead,
21
+ allows an opportunity to parse the markup in it.
22
+ """
23
+
24
+ def __init__(self, friendly_message: str, stdout: str):
25
+ self.stdout = stdout
26
+ super().__init__(friendly_message)
17
27
 
18
28
 
19
- class RunCommandUnhandledError(RuntimeError):
20
- pass
29
+ class RunCommandBadExitCode(RunemJobError):
30
+ def __init__(self, stdout: str):
31
+ super().__init__(friendly_message="Bad exit-code", stdout=stdout)
32
+
33
+
34
+ class RunCommandUnhandledError(RunemJobError):
35
+ def __init__(self, stdout: str):
36
+ super().__init__(friendly_message="Unhandled job error", stdout=stdout)
21
37
 
22
38
 
23
39
  # A function type for recording timing information.
@@ -27,25 +43,25 @@ RecordSubJobTimeType = typing.Callable[[str, timedelta], None]
27
43
  def parse_stdout(stdout: str, prefix: str) -> str:
28
44
  """Prefixes each line of the output with a given label, except trailing new
29
45
  lines."""
46
+
30
47
  # Edge case: Return the prefix immediately for an empty string
31
48
  if not stdout:
32
49
  return prefix
33
50
 
34
- # Split stdout into lines, noting if it ends with a newline
35
- ends_with_newline = stdout.endswith("\n")
51
+ # Stop errors in `rich` by parsing out anything that might look like
52
+ # rich-markup.
53
+ stdout = escape(stdout)
54
+
55
+ # Split stdout into lines
36
56
  lines = stdout.split("\n")
37
57
 
38
58
  # Apply prefix to all lines except the last if it's empty (due to a trailing newline)
39
- modified_lines = [f"{prefix}{line}" for line in lines[:-1]] + (
40
- [lines[-1]]
41
- if lines[-1] == "" and ends_with_newline
42
- else [f"{prefix}{lines[-1]}"]
59
+ modified_lines = [f"{prefix}{escape(line)}" for line in lines[:-1]] + (
60
+ [f"{prefix}{escape(lines[-1])}"]
43
61
  )
44
62
 
45
63
  # Join the lines back together, appropriately handling the final newline
46
- modified_stdout = "\n".join(modified_lines)
47
- # if ends_with_newline:
48
- # modified_stdout += "\n"
64
+ modified_stdout: str = "\n".join(modified_lines)
49
65
 
50
66
  return modified_stdout
51
67
 
@@ -69,24 +85,34 @@ def _log_command_execution(
69
85
  label: str,
70
86
  env_overrides: typing.Optional[typing.Dict[str, str]],
71
87
  valid_exit_ids: typing.Optional[typing.Tuple[int, ...]],
88
+ decorate_logs: bool,
72
89
  verbose: bool,
73
90
  cwd: typing.Optional[pathlib.Path] = None,
74
91
  ) -> None:
75
92
  """Logs out useful debug information on '--verbose'."""
76
93
  if verbose:
77
- log(f"running: start: {label}: {cmd_string}")
94
+ log(
95
+ f"running: start: [blue]{label}[/blue]: [yellow]{cmd_string}[yellow]",
96
+ decorate=decorate_logs,
97
+ )
78
98
  if valid_exit_ids is not None:
79
99
  valid_exit_strs = ",".join(str(exit_code) for exit_code in valid_exit_ids)
80
- log(f"\tallowed return ids are: {valid_exit_strs}")
100
+ log(
101
+ f"\tallowed return ids are: [green]{valid_exit_strs}[/green]",
102
+ decorate=decorate_logs,
103
+ )
81
104
 
82
105
  if env_overrides:
83
106
  env_overrides_as_string = " ".join(
84
107
  [f"{key}='{value}'" for key, value in env_overrides.items()]
85
108
  )
86
- log(f"ENV OVERRIDES: {env_overrides_as_string} {cmd_string}")
109
+ log(
110
+ f"ENV OVERRIDES: [yellow]{env_overrides_as_string} {cmd_string}[/yellow]",
111
+ decorate=decorate_logs,
112
+ )
87
113
 
88
114
  if cwd:
89
- log(f"cwd: {str(cwd)}")
115
+ log(f"cwd: {str(cwd)}", decorate=decorate_logs)
90
116
 
91
117
 
92
118
  def run_command( # noqa: C901
@@ -98,6 +124,7 @@ def run_command( # noqa: C901
98
124
  valid_exit_ids: typing.Optional[typing.Tuple[int, ...]] = None,
99
125
  cwd: typing.Optional[pathlib.Path] = None,
100
126
  record_sub_job_time: typing.Optional[RecordSubJobTimeType] = None,
127
+ decorate_logs: bool = True,
101
128
  **kwargs: typing.Any,
102
129
  ) -> str:
103
130
  """Runs the given command, returning stdout or throwing on any error."""
@@ -115,6 +142,7 @@ def run_command( # noqa: C901
115
142
  label,
116
143
  env_overrides,
117
144
  valid_exit_ids,
145
+ decorate_logs,
118
146
  verbose,
119
147
  cwd,
120
148
  )
@@ -143,7 +171,12 @@ def run_command( # noqa: C901
143
171
  stdout += line
144
172
  if verbose:
145
173
  # print each line of output, assuming that each has a newline
146
- log(parse_stdout(line, prefix=f"{label}: "))
174
+ log(
175
+ parse_stdout(
176
+ line, prefix=f"[green]| [/green][blue]{label}[/blue]: "
177
+ ),
178
+ decorate=False,
179
+ )
147
180
 
148
181
  # Wait for the subprocess to finish and get the exit code
149
182
  process.wait()
@@ -154,15 +187,15 @@ def run_command( # noqa: C901
154
187
  )
155
188
  raise RunCommandBadExitCode(
156
189
  (
157
- f"non-zero exit {process.returncode} (allowed are "
158
- f"{valid_exit_strs}) from {cmd_string}"
190
+ f"non-zero exit [red]{process.returncode}[/red] (allowed are "
191
+ f"[green]{valid_exit_strs}[/green]) from {cmd_string}"
159
192
  )
160
193
  )
161
194
  except BaseException as err:
162
195
  if ignore_fails:
163
196
  return ""
164
197
  parsed_stdout: str = (
165
- parse_stdout(stdout, prefix=f"{label}: ERROR: ") if process else ""
198
+ parse_stdout(stdout, prefix="[red]| [/red]") if process else ""
166
199
  )
167
200
  env_overrides_as_string = ""
168
201
  if env_overrides:
@@ -171,11 +204,11 @@ def run_command( # noqa: C901
171
204
  )
172
205
  env_overrides_as_string = f"{env_overrides_as_string} "
173
206
  error_string = (
174
- f"runem: test: FATAL: command failed: {label}"
175
- f"\n\t{env_overrides_as_string}{cmd_string}"
176
- f"\nERROR"
207
+ f"runem: [red bold]FATAL[/red bold]: command failed: [blue]{label}[/blue]"
208
+ f"\n\t[yellow]{env_overrides_as_string}{cmd_string}[/yellow]"
209
+ f"\n[red underline]| ERROR[/red underline]"
177
210
  f"\n{str(parsed_stdout)}"
178
- f"\nERROR END"
211
+ f"\n[red underline]| ERROR END[/red underline]"
179
212
  )
180
213
 
181
214
  if isinstance(err, RunCommandBadExitCode):
@@ -184,7 +217,10 @@ def run_command( # noqa: C901
184
217
  raise RunCommandUnhandledError(error_string) from err
185
218
 
186
219
  if verbose:
187
- log(f"running: done: {label}: {cmd_string}")
220
+ log(
221
+ f"running: done: [blue]{label}[/blue]: [yellow]{cmd_string}[/yellow]",
222
+ decorate=decorate_logs,
223
+ )
188
224
 
189
225
  if record_sub_job_time is not None:
190
226
  # Capture how long this run took
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
+ import contextlib
22
23
  import multiprocessing
23
24
  import os
24
25
  import pathlib
@@ -30,10 +31,9 @@ from datetime import timedelta
30
31
  from itertools import repeat
31
32
  from multiprocessing.managers import DictProxy, ValueProxy
32
33
  from timeit import default_timer as timer
33
- from types import TracebackType
34
34
 
35
- from rich.console import Console, ConsoleOptions, ConsoleRenderable, RenderResult
36
35
  from rich.spinner import Spinner
36
+ from rich.status import Status
37
37
  from rich.text import Text
38
38
 
39
39
  from runem.blocking_print import RICH_CONSOLE
@@ -46,6 +46,7 @@ from runem.job_execute import job_execute
46
46
  from runem.job_filter import filter_jobs
47
47
  from runem.log import error, log, warn
48
48
  from runem.report import report_on_run
49
+ from runem.run_command import RunemJobError
49
50
  from runem.types.common import OrderedPhases, PhaseName
50
51
  from runem.types.filters import FilePathListLookup
51
52
  from runem.types.hooks import HookName
@@ -56,7 +57,7 @@ from runem.types.types_jobs import (
56
57
  JobRunMetadatasByPhase,
57
58
  JobTiming,
58
59
  )
59
- from runem.utils import printable_set
60
+ from runem.utils import printable_set_coloured
60
61
 
61
62
 
62
63
  def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
@@ -92,38 +93,8 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
92
93
  return config_metadata
93
94
 
94
95
 
95
- class DummySpinner(ConsoleRenderable): # pragma: no cover
96
- """A dummy spinner for when spinners are disabled."""
97
-
98
- def __init__(self) -> None:
99
- self.text = ""
100
-
101
- def __rich__(self) -> Text:
102
- """Return a rich Text object for rendering."""
103
- return Text(self.text)
104
-
105
- def __rich_console__(
106
- self, console: Console, options: ConsoleOptions
107
- ) -> RenderResult:
108
- """Yield an empty string or placeholder text."""
109
- yield Text(self.text)
110
-
111
- def __enter__(self) -> None:
112
- """Support for context manager."""
113
- pass
114
-
115
- def __exit__(
116
- self,
117
- exc_type: typing.Optional[typing.Type[BaseException]],
118
- exc_value: typing.Optional[BaseException],
119
- traceback: typing.Optional[TracebackType],
120
- ) -> None:
121
- """Support for context manager."""
122
- pass
123
-
124
-
125
96
  def _update_progress(
126
- label: str,
97
+ phase: str,
127
98
  running_jobs: typing.Dict[str, str],
128
99
  completed_jobs: typing.Dict[str, str],
129
100
  all_jobs: Jobs,
@@ -140,30 +111,36 @@ def _update_progress(
140
111
  is_running (ValueProxy[bool]): Flag indicating if jobs are still running.
141
112
  num_workers (int): Indicates the number of workers performing the jobs.
142
113
  """
143
- # Using the `rich` module to show a loading spinner on console
144
- spinner: typing.Union[Spinner, DummySpinner]
145
- if show_spinner:
146
- spinner = Spinner("dots", text="Starting tasks...")
147
- else:
148
- spinner = DummySpinner()
149
114
 
150
115
  last_running_jobs_set: typing.Set[str] = set()
151
116
 
152
- with RICH_CONSOLE.status(spinner):
117
+ # Using the `rich` module to show a loading spinner on console
118
+ spinner_ctx: typing.Union[Status, typing.ContextManager[None]] = (
119
+ RICH_CONSOLE.status(Spinner("dots", text="Starting tasks..."))
120
+ if show_spinner
121
+ else contextlib.nullcontext()
122
+ )
123
+
124
+ with spinner_ctx:
153
125
  while is_running.value:
154
126
  running_jobs_set: typing.Set[str] = set(running_jobs.values())
155
127
 
156
128
  # Progress report
157
129
  progress: str = f"{len(completed_jobs)}/{len(all_jobs)}"
158
- running_jobs_list = printable_set(
159
- running_jobs_set
130
+ running_jobs_list = printable_set_coloured(
131
+ running_jobs_set,
132
+ "blue",
160
133
  ) # Reflect current running jobs accurately
161
- report: str = f"{label}: {progress}({num_workers}): {running_jobs_list}"
134
+ report: str = (
135
+ f"[green]{phase}[/green]: {progress}({num_workers}): {running_jobs_list}"
136
+ )
162
137
  if show_spinner:
163
- spinner.text = report
138
+ assert isinstance(spinner_ctx, Status)
139
+ spinner_ctx.update(Text.from_markup(report))
164
140
  else:
165
141
  if last_running_jobs_set != running_jobs_set:
166
142
  RICH_CONSOLE.log(report)
143
+ last_running_jobs_set = running_jobs_set
167
144
 
168
145
  # Sleep for reduced CPU usage
169
146
  time.sleep(0.1)
@@ -176,7 +153,7 @@ def _process_jobs(
176
153
  phase: PhaseName,
177
154
  jobs: Jobs,
178
155
  show_spinner: bool,
179
- ) -> typing.Optional[BaseException]:
156
+ ) -> typing.Optional[RunemJobError]:
180
157
  """Execute each given job asynchronously.
181
158
 
182
159
  This is where the major real-world time savings happen, and it could be
@@ -196,12 +173,12 @@ def _process_jobs(
196
173
  num_concurrent_procs: int = min(max_num_concurrent_procs, len(jobs))
197
174
  log(
198
175
  (
199
- f"Running '{phase}' with {num_concurrent_procs} workers (of "
176
+ f"Running '[green]{phase}[/green]' with {num_concurrent_procs} workers (of "
200
177
  f"{max_num_concurrent_procs} max) processing {len(jobs)} jobs"
201
178
  )
202
179
  )
203
180
 
204
- subprocess_error: typing.Optional[BaseException] = None
181
+ subprocess_error: typing.Optional[RunemJobError] = None
205
182
 
206
183
  with multiprocessing.Manager() as manager:
207
184
  running_jobs: DictProxy[typing.Any, typing.Any] = manager.dict()
@@ -235,7 +212,7 @@ def _process_jobs(
235
212
  repeat(file_lists),
236
213
  ),
237
214
  )
238
- except BaseException as err: # pylint: disable=broad-exception-caught
215
+ except RunemJobError as err: # pylint: disable=broad-exception-caught
239
216
  subprocess_error = err
240
217
  finally:
241
218
  # Signal the terminal_writer process to exit
@@ -251,7 +228,7 @@ def _process_jobs_by_phase(
251
228
  filtered_jobs_by_phase: PhaseGroupedJobs,
252
229
  in_out_job_run_metadatas: JobRunMetadatasByPhase,
253
230
  show_spinner: bool,
254
- ) -> typing.Optional[BaseException]:
231
+ ) -> typing.Optional[RunemJobError]:
255
232
  """Execute each job asynchronously, grouped by phase.
256
233
 
257
234
  Whilst it is conceptually useful to group jobs by 'phase', Phases are
@@ -275,7 +252,7 @@ def _process_jobs_by_phase(
275
252
  if config_metadata.args.verbose:
276
253
  log(f"Running Phase {phase}")
277
254
 
278
- failure_exception: typing.Optional[BaseException] = _process_jobs(
255
+ failure_exception: typing.Optional[RunemJobError] = _process_jobs(
279
256
  config_metadata,
280
257
  file_lists,
281
258
  in_out_job_run_metadatas,
@@ -293,7 +270,7 @@ def _process_jobs_by_phase(
293
270
 
294
271
 
295
272
  MainReturnType = typing.Tuple[
296
- ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[BaseException]
273
+ ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[RunemJobError]
297
274
  ]
298
275
 
299
276
 
@@ -333,7 +310,7 @@ def _main(
333
310
 
334
311
  start = timer()
335
312
 
336
- failure_exception: typing.Optional[BaseException] = _process_jobs_by_phase(
313
+ failure_exception: typing.Optional[RunemJobError] = _process_jobs_by_phase(
337
314
  config_metadata,
338
315
  file_lists,
339
316
  filtered_jobs_by_phase,
@@ -362,7 +339,7 @@ def timed_main(argv: typing.List[str]) -> None:
362
339
  start = timer()
363
340
  config_metadata: ConfigMetadata
364
341
  job_run_metadatas: JobRunMetadatasByPhase
365
- failure_exception: typing.Optional[BaseException]
342
+ failure_exception: typing.Optional[RunemJobError]
366
343
  config_metadata, job_run_metadatas, failure_exception = _main(argv)
367
344
  phase_run_oder: OrderedPhases = config_metadata.phases
368
345
  end = timer()
@@ -372,14 +349,15 @@ def timed_main(argv: typing.List[str]) -> None:
372
349
  system_time_spent, wall_clock_time_saved = report_on_run(
373
350
  phase_run_oder, job_run_metadatas, time_taken
374
351
  )
375
- message: str = "DONE: runem took"
352
+ message: str = "[green bold]DONE[/green bold]: runem took"
376
353
  if failure_exception:
377
- message = "FAILED: your jobs failed after"
354
+ message = "[red bold]FAILED[/red bold]: your jobs failed after"
378
355
  log(
379
356
  (
380
357
  f"{message}: {time_taken.total_seconds()}s, "
381
- f"saving you {wall_clock_time_saved.total_seconds()}s, "
382
- f"without runem you would have waited {system_time_spent.total_seconds()}s"
358
+ f"saving you [green]{wall_clock_time_saved.total_seconds()}s[/green], "
359
+ "without runem you would have waited "
360
+ f"[red]{system_time_spent.total_seconds()}s[/red]"
383
361
  )
384
362
  )
385
363
 
@@ -392,6 +370,7 @@ def timed_main(argv: typing.List[str]) -> None:
392
370
  if failure_exception is not None:
393
371
  # we got a failure somewhere, now that we've reported the timings we
394
372
  # re-raise.
373
+ error(failure_exception.stdout)
395
374
  raise failure_exception
396
375
 
397
376
 
runem/types/__init__.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from runem.types.common import FilePathList, JobName
2
2
  from runem.types.options import Options
3
- from runem.types.types_jobs import HookKwargs, JobKwargs, JobReturnData
3
+ from runem.types.types_jobs import HookKwargs, JobKwargs, JobReturn, JobReturnData
4
4
 
5
5
  __all__ = [
6
6
  "FilePathList",
7
7
  "HookKwargs",
8
8
  "JobName",
9
+ "JobReturn",
9
10
  "JobReturnData",
10
11
  "Options",
11
12
  "JobKwargs",
runem/utils.py CHANGED
@@ -4,3 +4,15 @@ import typing
4
4
  def printable_set(some_set: typing.Set[typing.Any]) -> str:
5
5
  """Get a printable, deterministic string version of a set."""
6
6
  return ", ".join([f"'{set_item}'" for set_item in sorted(list(some_set))])
7
+
8
+
9
+ def printable_set_coloured(some_set: typing.Set[typing.Any], colour: str) -> str:
10
+ """`printable_set` but elements are surrounded with colour mark-up.
11
+
12
+ Parameters:
13
+ some_set: a set of anything
14
+ colour: a `rich` Console supported colour
15
+ """
16
+ return ", ".join(
17
+ [f"'[{colour}]{set_item}[/{colour}]'" for set_item in sorted(list(some_set))]
18
+ )
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.2
2
+ Name: runem
3
+ Version: 0.6.0
4
+ Summary: Awesome runem created by lursight
5
+ Author: lursight
6
+ License: Specify your license here
7
+ Project-URL: Homepage, https://github.com/lursight/runem/
8
+ Keywords: example,runem
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Python: >=3.7
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: packaging>=22.0
18
+ Requires-Dist: PyYAML>=5.0.0
19
+ Requires-Dist: rich>10.0.0
20
+ Requires-Dist: typing_extensions>3.0.0
21
+ Provides-Extra: tests
22
+ Requires-Dist: black==24.10.0; extra == "tests"
23
+ Requires-Dist: coverage==7.5; extra == "tests"
24
+ Requires-Dist: docformatter==1.7.5; extra == "tests"
25
+ Requires-Dist: flake8-bugbear==24.2.6; extra == "tests"
26
+ Requires-Dist: flake8==7.0.0; extra == "tests"
27
+ Requires-Dist: gitchangelog==3.0.4; extra == "tests"
28
+ Requires-Dist: isort==5.13.2; extra == "tests"
29
+ Requires-Dist: mkdocs==1.5.3; extra == "tests"
30
+ Requires-Dist: mypy==1.9.0; extra == "tests"
31
+ Requires-Dist: pydocstyle==6.3.0; extra == "tests"
32
+ Requires-Dist: pylint==3.1.0; extra == "tests"
33
+ Requires-Dist: pylama==8.4.1; extra == "tests"
34
+ Requires-Dist: pytest-cov==6.0.0; extra == "tests"
35
+ Requires-Dist: pytest-profiling==1.7.0; extra == "tests"
36
+ Requires-Dist: pytest-xdist==3.6.1; extra == "tests"
37
+ Requires-Dist: pytest==8.3.3; extra == "tests"
38
+ Requires-Dist: setuptools; extra == "tests"
39
+ Requires-Dist: termplotlib==0.3.9; extra == "tests"
40
+ Requires-Dist: tox; extra == "tests"
41
+ Requires-Dist: types-PyYAML==6.0.12.20240311; extra == "tests"
42
+ Requires-Dist: requests-mock==1.11.0; extra == "tests"
43
+ Requires-Dist: types-setuptools; extra == "tests"
44
+
45
+ <!-- [![codecov](https://codecov.io/gh/lursight/runem/branch/main/graph/badge.svg?token=run-test_token_here)](https://codecov.io/gh/lursight/runem) -->
46
+ [![CI](https://github.com/lursight/runem/actions/workflows/main.yml/badge.svg)](https://github.com/lursight/runem/actions/workflows/main.yml)
47
+ [![DOCS](https://lursight.github.io/runem/docs/VIEW-DOCS-31c553.svg)](https://lursight.github.io/runem/)
48
+
49
+ # Run’em
50
+
51
+ **Your Blueprint of Commands. Your Engine of Parallel Execution.**
52
+ Run’em is your definitive blueprint of tasks and commands—instantly discoverable, effortlessly parallel, and elegantly extensible.
53
+
54
+ ## Core Strengths
55
+
56
+ **Blueprint** - discover tasks and onboard smoothly\
57
+ **Parallel** - get results quicker\
58
+ **Simple** - define task easily\
59
+ **Extensible** - add tasks quickly\
60
+ **Filters** - powerful task selection\
61
+ **Reports** - see metrics on tasks
62
+
63
+ ## Why Run’em?
64
+ - **Command Blueprint:** Instantly see and run all your tasks. No guesswork, no rummaging.
65
+ - **Effortless Parallelism:** Execute tasks side-by-side to obliterate downtime.
66
+ - **Simple YAML Declarations:** Define everything in one `.runem.yml`.
67
+ - **Extensible & Smart:** Adapt to monorepos, complex workflows, and evolving needs.
68
+ - **Discoverable by Design:** `runem --help` guides your team, new hires, or contributors to every defined command.
69
+
70
+ ## Contents
71
+ - [Run’em](#runem)
72
+ - [Core Strengths](#core-strengths)
73
+ - [Why Run’em?](#why-runem)
74
+ - [Contents](#contents)
75
+ - [Highlights](#highlights)
76
+ - [Quick Start](#quick-start)
77
+ - [Basic Use](#basic-use)
78
+ - [Advanced Use](#advanced-use)
79
+ - [Help & Discovery](#help--discovery)
80
+ - [Troubleshooting](#troubleshooting)
81
+ - [Contribute & Support](#contribute--support)
82
+ - [About Run’em](#about-runem)
83
+
84
+ # Highlights
85
+ ## Blueprint of Commands:
86
+ The blueprint (available via `--help`) gives you a manifest of all jobs and tasks in a
87
+ project. A single source of truth for all tasks.
88
+ ## Parallel Execution:
89
+ Maximise speed with automatic concurrency. Runem tries to run all tasks as quickly as
90
+ possible, looking at resources, with dependencies. It is not yet a full
91
+ dependency-execution graph, but by version 1.0.0 it will be.
92
+ ## Filtering:
93
+ Use powerful and flexible filtering. Select or excluded tasks by `tags`, `name` and
94
+ `phase`. Chose the task to be run based on your needs, right now.
95
+
96
+ You can also customise filtering by adding your own command `options`.
97
+
98
+ See `--tags`, `--not-tags`, `--jobs`, `--not-jobs`, `--phases` and `--not-phases`.
99
+ ## Powerful Insights:** Understand what ran, how fast, and what failed.
100
+ **Quiet by Default:** Focus on what matters, and reveal detail only when needed.
101
+
102
+ # Quick Start
103
+ **Install:**
104
+ ```bash
105
+ pip install runem
106
+ ```
107
+ **Define a task:**
108
+
109
+ ```yaml
110
+ `# .runem.yml
111
+ - job:
112
+ command: echo "hello world!"
113
+ ```
114
+
115
+ **Run:**
116
+
117
+ ```bash
118
+ runem
119
+ ```
120
+
121
+ Run multiple commands in parallel, see timing, and keep output minimal. Need detail?
122
+
123
+ ```bash
124
+ runem --verbose
125
+ ```
126
+
127
+ [Quick Start Docs](https://lursight.github.io/runem/docs/quick_start.html)
128
+
129
+ # Basic Use
130
+
131
+ Get comfortable with typical workflows:
132
+ [Basic Use Docs](https://lursight.github.io/runem/docs/basic_use.html)
133
+
134
+ # Advanced Use
135
+
136
+ Scale up with multi-phase configs, filtered execution, and custom reporting:
137
+ [Advanced Configuration](https://lursight.github.io/runem/docs/configuration.html)
138
+ [Custom Reporting](https://lursight.github.io/runem/docs/reports.html)
139
+
140
+ # Help & Discovery
141
+
142
+ `runem --help` is your radar—instantly mapping out every available task:
143
+ [Help & Job Discovery](https://lursight.github.io/runem/docs/help_and_job_discovery.html)
144
+
145
+ # Troubleshooting
146
+
147
+ Swift solutions to common issues:
148
+ [Troubleshooting & Known Issues](https://lursight.github.io/runem/docs/troubleshooting_known_issues.html)
149
+
150
+ ---
151
+
152
+ # Contribute & Support
153
+
154
+ Brought to you by [Lursight Ltd.](https://lursight.com) and an open community.
155
+ [CONTRIBUTING.md](CONTRIBUTING.md)
156
+ [❤️ Sponsor](https://github.com/sponsors/lursight/)
157
+
158
+ # About Run’em
159
+
160
+ Run’em exists to accelerate your team’s delivery and reduce complexity. Learn about our [Mission](https://lursight.github.io/runem/docs/mission.html).
161
+
@@ -1,8 +1,8 @@
1
- runem/VERSION,sha256=oK1QZAE5pST4ZZEVcUW_HUZ06pwGW_6iFVjw97BEMMg,6
1
+ runem/VERSION,sha256=l6XW5UCmEg0Jw53bZn4Ojiusf8wv_vgTuC4I_WA2W84,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=R3c3HSnRMPCf7ykDXdKG2hKH4CzbBaxmOfP3xEU_wEI,1919
5
+ runem/blocking_print.py,sha256=2nCvc10zXl1DRJldkruKthKjfmZKErcmxQzt3pjmN-c,2289
6
6
  runem/cli.py,sha256=wEt_Jnumhl8SiOdKdSJzLkJpWv6n3_Odhi_HeIixr1k,134
7
7
  runem/command_line.py,sha256=qkZFCCq9hUl6RO398SJzoigv8di5jGw2sdNwgTVBdd8,14474
8
8
  runem/config.py,sha256=UiEU0Jyg5qjrNStvasWYjMOABQHhpZjbPiX3-sH_CMg,5969
@@ -17,15 +17,15 @@ runem/job_filter.py,sha256=7vgG4YWJ9gyGBFjV7QbSojG5ofYoszAmxXx9HnMLkHo,5384
17
17
  runem/job_runner_simple_command.py,sha256=iP5an6yixW8o4C0ZBtu6csb-oVK3Q62ZZgtHBmxlXaU,2428
18
18
  runem/job_wrapper.py,sha256=q5GtopZ5vhSJ581rwU4-lF9KnbL3ZYgOC8fqaCnXD_g,983
19
19
  runem/job_wrapper_python.py,sha256=rx7J_N-JXs8GgMq7Sla7B9s_ZAfofKUhEnzgMcq_bts,4303
20
- runem/log.py,sha256=dIrocigvIJs1ZGkAzTogXkAK-0ZW3q5FkjpDgLdeW-E,630
20
+ runem/log.py,sha256=6r6HIJyvp19P6PZNo93qdIxE0SpTAsY4ELrETBl1dC4,1363
21
21
  runem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- runem/report.py,sha256=IedwW9mmJfGC6vKQdKDHQH5eoiYzTWHaaD4a3J4e_Pc,9020
23
- runem/run_command.py,sha256=Egl_j4bJ9mwi2JEFCsl0W6WH2IRgIdpMN7qdj8voClQ,6386
24
- runem/runem.py,sha256=9QZgmXNPXOscRRLWDgOcPZ0lYRcG7jOog1M_o3MXZbs,13667
22
+ runem/report.py,sha256=beye95AV9Lop_K7eOoLY40vKt0VGkIdg-ig-SmJ_5MY,9083
23
+ runem/run_command.py,sha256=R77jqAtrXPBkFtT7QXJfnQkivU4h01M8G0Q2sjWY6Gs,7691
24
+ runem/runem.py,sha256=6XZqf59_ZMyrBaK1FlaLzMXYNoDQsTUcbFqTbpZL1ik,13192
25
25
  runem/runem_version.py,sha256=MbETwZO2Tb1Y3hX_OYZjKepEMKA1cjNvr-7Cqhz6e3s,271
26
- runem/utils.py,sha256=3N_kel9LsriiMq7kOjT14XhfxUOgz4hdDg97wlLKm3U,221
26
+ runem/utils.py,sha256=MEYfox09rLvb6xmay_3rV1cWmdqMbhaAjOomYGNk15k,602
27
27
  runem/cli/initialise_options.py,sha256=zx_EduWQk7yGBr3XUNrffHCSInPv05edFItHLnlo9dk,918
28
- runem/types/__init__.py,sha256=6TzF4KV9tDGuDTr2qAXmWWkfDU52WuVlQ8Hcz48aYDk,286
28
+ runem/types/__init__.py,sha256=0bWG7hE7VeqJ2oIu-xhrqQud8hcNp6WNbF3uMfT_n9g,314
29
29
  runem/types/common.py,sha256=gPMSoJ3yRUYjHnoviRrpSg0gRwsGLFGWGpbTWkq4jX0,279
30
30
  runem/types/errors.py,sha256=rbM5BA6UhY1X7Q0OZLUNsG7JXAjgNFTG5KQuqPNuZm8,103
31
31
  runem/types/filters.py,sha256=8R5fyMssN0ISGBilJhEtbdHFl6OP7uI51WKkB5SH6EA,255
@@ -44,9 +44,9 @@ tests/data/help_output.3.10.txt,sha256=5TUpNITVL6pD5BpFAl-Orh3vkOpStveijZzvgJuI_
44
44
  tests/data/help_output.3.11.txt,sha256=ycrF-xKgdQ8qrWzkkR-vbHe7NulUTsCsS0_Gda8xYDs,4162
45
45
  tests/test_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  tests/test_types/test_public_api.py,sha256=QHiwt7CetQur65JSbFRnOzQxhCJkX5MVLymHHVd_6yc,160
47
- runem-0.5.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
48
- runem-0.5.0.dist-info/METADATA,sha256=9isKJbNwlMOME8i2ipmSBySxD-Om_tVIp0ji_3WJeRM,6215
49
- runem-0.5.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
50
- runem-0.5.0.dist-info/entry_points.txt,sha256=nu0g_vBeuPihYtimbtlNusxWovylMppvJ8UxdJlJfvM,46
51
- runem-0.5.0.dist-info/top_level.txt,sha256=NkdxkwLKNNhxItveR2KqNqTshTZ268m5D7SjJEmG4-Y,20
52
- runem-0.5.0.dist-info/RECORD,,
47
+ runem-0.6.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
48
+ runem-0.6.0.dist-info/METADATA,sha256=00_gsiXfiHPCwcZumnmOU2KjaUTvR6kF9c9nr4p2YHc,5892
49
+ runem-0.6.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
50
+ runem-0.6.0.dist-info/entry_points.txt,sha256=nu0g_vBeuPihYtimbtlNusxWovylMppvJ8UxdJlJfvM,46
51
+ runem-0.6.0.dist-info/top_level.txt,sha256=NkdxkwLKNNhxItveR2KqNqTshTZ268m5D7SjJEmG4-Y,20
52
+ runem-0.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,164 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: runem
3
- Version: 0.5.0
4
- Summary: Awesome runem created by lursight
5
- Author: lursight
6
- License: Specify your license here
7
- Project-URL: Homepage, https://github.com/lursight/runem/
8
- Keywords: example,runem
9
- Classifier: Programming Language :: Python :: 3.7
10
- Classifier: Programming Language :: Python :: 3.8
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Requires-Python: >=3.7
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: packaging>=22.0
18
- Requires-Dist: PyYAML>=5.0.0
19
- Requires-Dist: rich>10.0.0
20
- Requires-Dist: typing_extensions>3.0.0
21
- Provides-Extra: tests
22
- Requires-Dist: black==24.10.0; extra == "tests"
23
- Requires-Dist: coverage==7.5; extra == "tests"
24
- Requires-Dist: docformatter==1.7.5; extra == "tests"
25
- Requires-Dist: flake8-bugbear==24.2.6; extra == "tests"
26
- Requires-Dist: flake8==7.0.0; extra == "tests"
27
- Requires-Dist: gitchangelog==3.0.4; extra == "tests"
28
- Requires-Dist: isort==5.13.2; extra == "tests"
29
- Requires-Dist: mkdocs==1.5.3; extra == "tests"
30
- Requires-Dist: mypy==1.9.0; extra == "tests"
31
- Requires-Dist: pydocstyle==6.3.0; extra == "tests"
32
- Requires-Dist: pylint==3.1.0; extra == "tests"
33
- Requires-Dist: pylama==8.4.1; extra == "tests"
34
- Requires-Dist: pytest-cov==6.0.0; extra == "tests"
35
- Requires-Dist: pytest-profiling==1.7.0; extra == "tests"
36
- Requires-Dist: pytest-xdist==3.6.1; extra == "tests"
37
- Requires-Dist: pytest==8.3.3; extra == "tests"
38
- Requires-Dist: setuptools; extra == "tests"
39
- Requires-Dist: termplotlib==0.3.9; extra == "tests"
40
- Requires-Dist: tox; extra == "tests"
41
- Requires-Dist: types-PyYAML==6.0.12.20240311; extra == "tests"
42
- Requires-Dist: requests-mock==1.11.0; extra == "tests"
43
- Requires-Dist: types-setuptools; extra == "tests"
44
-
45
- <!-- [![codecov](https://codecov.io/gh/lursight/runem/branch/main/graph/badge.svg?token=run-test_token_here)](https://codecov.io/gh/lursight/runem) -->
46
- [![CI](https://github.com/lursight/runem/actions/workflows/main.yml/badge.svg)](https://github.com/lursight/runem/actions/workflows/main.yml)
47
- [![DOCS](https://lursight.github.io/runem/docs/VIEW-DOCS-31c553.svg)](https://lursight.github.io/runem/)
48
-
49
- # Run'em: Accelerate Your Development Workflow
50
- **Boost Efficiency and Save Time**
51
- Runem is a flexible, multi-process tool designed to speed up your everyday tasks by running them in parallel. Whether you're testing, linting, or deploying, runem helps you work smarter and faster.
52
-
53
- ## Why Choose Run'em?
54
- - **Streamlined Task Management**: Configure tasks with ease using declarative .runem.yml files.
55
- - **Multiprocess Execution**: Run multiple tasks simultaneously, minimizing wall-clock time.
56
- - **Optimized for Monorepos**: Supports multiple projects and task types, with easy filtering and configuration.
57
- - **Detailed Reporting**: Get insights into task execution time and efficiency gains.
58
-
59
- ## Contents
60
- - [Run'em: Accelerate Your Development Workflow](#runem-accelerate-your-development-workflow)
61
- - [Why Choose Run'em?](#why-choose-runem)
62
- - [Contents](#contents)
63
- - [Features At A Glance:](#features-at-a-glance)
64
- - [Using Run'em](#using-runem)
65
- - [Installation](#installation)
66
- - [Quick-start](#quick-start)
67
- - [Basic quick-start](#basic-quick-start)
68
- - [A more complete quick-start](#a-more-complete-quick-start)
69
- - [Basic Use](#basic-use)
70
- - [Advanced Use](#advanced-use)
71
- - [Advanced configuration options](#advanced-configuration-options)
72
- - [Custom reports](#custom-reports)
73
- - [Help and job discovery](#help-and-job-discovery)
74
- - [Troubleshooting](#troubleshooting)
75
- - [Contributing to and supporting runem](#contributing-to-and-supporting-runem)
76
- - [Development](#development)
77
- - [Sponsor](#sponsor)
78
- - [About runem](#about-runem)
79
-
80
-
81
- # Features At A Glance:
82
- - **Tagging**: Easily run specific job groups (e.g., lint, test, python).
83
- - **Phases**: Organize tasks by phase (e.g., edit, test, deploy).
84
- - **Configurable Options**: Customize how jobs are executed using simple options.
85
- - **Declarative**: Jobs are define using simple YAML in [.runem.yml](https://lursight.github.io/runem/docs/configuration.html) .
86
-
87
- # Using Run'em
88
-
89
- ## Installation
90
-
91
- ```bash
92
- pip install runem
93
- ```
94
-
95
- ## Quick-start
96
-
97
- ## Basic quick-start
98
- Create the following `.runem.yml` file at the root of your project:
99
-
100
- ```yml
101
- - job:
102
- command: echo "hello world!"
103
- ```
104
-
105
- Then anywhere in your project run `runem` to see how and when that task is run, and how long it took:
106
- ```bash
107
- runem
108
- ```
109
-
110
- To see the actual log output you will need to use `--verbose` as `runem` hides anything that isn't important. Only failures and reports are considered important.
111
- ```bash
112
- # Or, to see "hello world!", use --verbose
113
- runem --verbose # add --verbose to see the actual output
114
- ```
115
-
116
- To see how you can control your job use `--help`:
117
- ```bash
118
- runem --help
119
- ```
120
-
121
- ### A more complete quick-start
122
-
123
- See [quick-start docs](https://lursight.github.io/runem/docs/quick_start.html) for more quick-start tips.
124
-
125
- ## Basic Use
126
-
127
- See [docs on basic use and use-cases](https://lursight.github.io/runem/docs/basic_use.html) for a comprehensive introduction.
128
-
129
- ## Advanced Use
130
-
131
- ### Advanced configuration options
132
- See [configuration docs](https://lursight.github.io/runem/docs/configuration.html) for advanced configuration and use.
133
-
134
- ### Custom reports
135
- See [reporting docs](https://lursight.github.io/runem/docs/reports.html) for more information on how reporting works.
136
-
137
-
138
- # Help and job discovery
139
-
140
- `--help` is designed to help your team discover what jobs and tasks they can automated. Read more at
141
- [help and discovery docs](https://lursight.github.io/runem/docs/help_and_job_discovery.html).
142
-
143
- # Troubleshooting
144
-
145
- See [troubleshooting and known issues docs](https://lursight.github.io/runem/docs/troubleshooting_known_issues.html).
146
-
147
- ---
148
- # Contributing to and supporting runem
149
-
150
- Awesome runem created by lursight
151
-
152
- ## Development
153
-
154
- Read the [CONTRIBUTING.md](CONTRIBUTING.md) file.
155
-
156
- ## Sponsor
157
-
158
- [❤️ Sponsor this project](https://github.com/sponsors/lursight/)
159
-
160
- # About runem
161
- The runem mission is to improve developer velocity at
162
- [Lursight Ltd.](https://lursight.com), read more about the runem
163
- [mission](https://lursight.github.io/runem/docs/mission.html).
164
-
File without changes