runem 0.4.0__py3-none-any.whl → 0.5.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.4.0
1
+ 0.5.0
@@ -0,0 +1,26 @@
1
+ import argparse
2
+
3
+ from runem.config_metadata import ConfigMetadata
4
+ from runem.informative_dict import InformativeDict
5
+ from runem.types.options import OptionsWritable
6
+
7
+
8
+ def initialise_options(
9
+ config_metadata: ConfigMetadata,
10
+ args: argparse.Namespace,
11
+ ) -> OptionsWritable:
12
+ """Initialises and returns the set of options to use for this run.
13
+
14
+ Returns the options dictionary
15
+ """
16
+
17
+ options: OptionsWritable = InformativeDict(
18
+ {option["name"]: option["default"] for option in config_metadata.options_config}
19
+ )
20
+ if config_metadata.options_config and args.overrides_on: # pragma: no branch
21
+ for option_name in args.overrides_on: # pragma: no branch
22
+ options[option_name] = True
23
+ if config_metadata.options_config and args.overrides_off: # pragma: no branch
24
+ for option_name in args.overrides_off:
25
+ options[option_name] = False
26
+ return options
runem/command_line.py CHANGED
@@ -53,6 +53,14 @@ def _get_argparse_help_formatter() -> typing.Any:
53
53
  return argparse.HelpFormatter
54
54
 
55
55
 
56
+ def error_on_log_logic(verbose: bool, silent: bool) -> None:
57
+ """Simply errors if we get logical inconsistencies in the logging-logic."""
58
+ if verbose and silent:
59
+ log("cannot parse '--verbose' and '--silent'")
60
+ # error exit
61
+ sys.exit(1)
62
+
63
+
56
64
  def parse_args(
57
65
  config_metadata: ConfigMetadata, argv: typing.List[str]
58
66
  ) -> ConfigMetadata:
@@ -130,7 +138,9 @@ def parse_args(
130
138
  nargs="+",
131
139
  default=config_metadata.all_job_tags,
132
140
  help=(
133
- "Only jobs with the given tags. "
141
+ # TODO: clarify the logic by which we add/remove jobs based on tags
142
+ # e.g. exclusive-in, union, x-or etc.
143
+ "Only run jobs with the given tags. "
134
144
  f"Defaults to '{sorted(config_metadata.all_job_tags)}'."
135
145
  ),
136
146
  required=False,
@@ -238,6 +248,16 @@ def parse_args(
238
248
  required=False,
239
249
  )
240
250
 
251
+ parser.add_argument(
252
+ "--silent",
253
+ "-s",
254
+ dest="silent",
255
+ action=argparse.BooleanOptionalAction,
256
+ default=False,
257
+ help=("Whether to show warning messages or not. "),
258
+ required=False,
259
+ )
260
+
241
261
  parser.add_argument(
242
262
  "--spinner",
243
263
  dest="show_spinner",
@@ -271,6 +291,8 @@ def parse_args(
271
291
 
272
292
  args = parser.parse_args(argv[1:])
273
293
 
294
+ error_on_log_logic(args.verbose, args.silent)
295
+
274
296
  if args.show_root_path_and_exit:
275
297
  log(str(config_metadata.cfg_filepath.parent), decorate=False)
276
298
  # cleanly exit
runem/config_parse.py CHANGED
@@ -149,6 +149,7 @@ def parse_job_config(
149
149
  in_out_job_names: JobNames,
150
150
  in_out_phases: JobPhases,
151
151
  phase_order: OrderedPhases,
152
+ silent: bool = False,
152
153
  ) -> None:
153
154
  """Parses and validates a job-entry read in from disk.
154
155
 
@@ -208,6 +209,7 @@ def parse_job_config(
208
209
  in_out_job_names,
209
210
  in_out_phases,
210
211
  phase_order,
212
+ warn_missing_phase=(not silent),
211
213
  )
212
214
  except KeyError as err:
213
215
  raise ValueError(
@@ -215,8 +217,11 @@ def parse_job_config(
215
217
  ) from err
216
218
 
217
219
 
218
- def parse_config(
219
- config: Config, cfg_filepath: pathlib.Path, hooks_only: bool = False
220
+ def parse_config( # noqa: C901
221
+ config: Config,
222
+ cfg_filepath: pathlib.Path,
223
+ silent: bool = False,
224
+ hooks_only: bool = False,
220
225
  ) -> typing.Tuple[
221
226
  Hooks, # hooks:
222
227
  OrderedPhases, # phase_order:
@@ -282,7 +287,10 @@ def parse_config(
282
287
 
283
288
  if not phase_order:
284
289
  if not hooks_only:
285
- warn("phase ordering not configured! Runs will be non-deterministic!")
290
+ if silent: # pragma: no cover
291
+ pass
292
+ else:
293
+ warn("phase ordering not configured! Runs will be non-deterministic!")
286
294
  phase_order = tuple(job_phases)
287
295
 
288
296
  # now parse out the job_configs
@@ -301,6 +309,7 @@ def parse_config(
301
309
  in_out_job_names=job_names,
302
310
  in_out_phases=job_phases,
303
311
  phase_order=phase_order,
312
+ silent=silent,
304
313
  )
305
314
  return (
306
315
  hooks,
@@ -341,7 +350,9 @@ def generate_config(
341
350
 
342
351
 
343
352
  def _load_user_hooks_from_config(
344
- user_config: Config, cfg_filepath: pathlib.Path
353
+ user_config: Config,
354
+ cfg_filepath: pathlib.Path,
355
+ silent: bool,
345
356
  ) -> Hooks:
346
357
  hooks: Hooks
347
358
  (
@@ -353,7 +364,7 @@ def _load_user_hooks_from_config(
353
364
  _,
354
365
  _,
355
366
  _,
356
- ) = parse_config(user_config, cfg_filepath, hooks_only=True)
367
+ ) = parse_config(user_config, cfg_filepath, silent, hooks_only=True)
357
368
  return hooks
358
369
 
359
370
 
@@ -361,6 +372,7 @@ def load_config_metadata(
361
372
  config: Config,
362
373
  cfg_filepath: pathlib.Path,
363
374
  user_configs: typing.List[typing.Tuple[Config, pathlib.Path]],
375
+ silent: bool = False,
364
376
  verbose: bool = False,
365
377
  ) -> ConfigMetadata:
366
378
  hooks: Hooks
@@ -380,12 +392,14 @@ def load_config_metadata(
380
392
  job_names,
381
393
  job_phases,
382
394
  tags,
383
- ) = parse_config(config, cfg_filepath)
395
+ ) = parse_config(config, cfg_filepath, silent)
384
396
 
385
397
  user_config: Config
386
398
  user_config_path: pathlib.Path
387
399
  for user_config, user_config_path in user_configs:
388
- user_hooks: Hooks = _load_user_hooks_from_config(user_config, user_config_path)
400
+ user_hooks: Hooks = _load_user_hooks_from_config(
401
+ user_config, user_config_path, silent
402
+ )
389
403
  if user_hooks:
390
404
  if verbose:
391
405
  log(f"hooks: loading user hooks from '{str(user_config_path)}'")
runem/job_execute.py CHANGED
@@ -11,7 +11,7 @@ from runem.config_metadata import ConfigMetadata
11
11
  from runem.informative_dict import ReadOnlyInformativeDict
12
12
  from runem.job import Job
13
13
  from runem.job_wrapper import get_job_wrapper
14
- from runem.log import error, log
14
+ from runem.log import error, log, warn
15
15
  from runem.types.common import FilePathList, JobTags
16
16
  from runem.types.filters import FilePathListLookup
17
17
  from runem.types.runem_config import JobConfig
@@ -50,7 +50,8 @@ def job_execute_inner(
50
50
 
51
51
  if not file_list:
52
52
  # no files to work on
53
- log(f"WARNING: skipping job '{label}', no files for job")
53
+ if not config_metadata.args.silent:
54
+ warn(f"skipping job '{label}', no files for job")
54
55
  return {
55
56
  "job": (f"{label}: no files!", timedelta(0)),
56
57
  "commands": [],
runem/runem.py CHANGED
@@ -37,7 +37,7 @@ from rich.spinner import Spinner
37
37
  from rich.text import Text
38
38
 
39
39
  from runem.blocking_print import RICH_CONSOLE
40
- from runem.command_line import parse_args
40
+ from runem.command_line import error_on_log_logic, parse_args
41
41
  from runem.config import load_project_config, load_user_configs
42
42
  from runem.config_metadata import ConfigMetadata
43
43
  from runem.config_parse import load_config_metadata
@@ -67,12 +67,19 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
67
67
 
68
68
  Return a ConfigMetadata object with all the required information.
69
69
  """
70
+
71
+ # Because we want to be able to show logging whilst parsing .runem.yml config, we
72
+ # need to check the state of the logging-verbosity switches here, manually, as well.
73
+ verbose = "--verbose" in argv
74
+ silent = ("--silent" in argv) or ("-s" in argv)
75
+ error_on_log_logic(verbose, silent)
76
+
70
77
  config: Config
71
78
  cfg_filepath: pathlib.Path
72
79
  config, cfg_filepath = load_project_config()
73
80
  user_configs: typing.List[typing.Tuple[Config, pathlib.Path]] = load_user_configs()
74
81
  config_metadata: ConfigMetadata = load_config_metadata(
75
- config, cfg_filepath, user_configs, verbose=("--verbose" in argv)
82
+ config, cfg_filepath, user_configs, silent, verbose=("--verbose" in argv)
76
83
  )
77
84
 
78
85
  # Now we parse the cli arguments extending them with information from the
@@ -1,15 +1,23 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: runem
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Awesome runem created by lursight
5
- Home-page: https://github.com/lursight/runem/
6
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
7
15
  Description-Content-Type: text/markdown
8
16
  License-File: LICENSE
9
- Requires-Dist: packaging
10
- Requires-Dist: PyYAML
11
- Requires-Dist: rich
12
- Requires-Dist: typing_extensions
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
13
21
  Provides-Extra: tests
14
22
  Requires-Dist: black==24.10.0; extra == "tests"
15
23
  Requires-Dist: coverage==7.5; extra == "tests"
@@ -153,3 +161,4 @@ Read the [CONTRIBUTING.md](CONTRIBUTING.md) file.
153
161
  The runem mission is to improve developer velocity at
154
162
  [Lursight Ltd.](https://lursight.com), read more about the runem
155
163
  [mission](https://lursight.github.io/runem/docs/mission.html).
164
+
@@ -1,18 +1,18 @@
1
- runem/VERSION,sha256=QLjrQACpE6d5EJBTXykdPTaYdBYqie88nj1OiHobnnk,6
1
+ runem/VERSION,sha256=oK1QZAE5pST4ZZEVcUW_HUZ06pwGW_6iFVjw97BEMMg,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
5
  runem/blocking_print.py,sha256=R3c3HSnRMPCf7ykDXdKG2hKH4CzbBaxmOfP3xEU_wEI,1919
6
6
  runem/cli.py,sha256=wEt_Jnumhl8SiOdKdSJzLkJpWv6n3_Odhi_HeIixr1k,134
7
- runem/command_line.py,sha256=HsZjxJsVQZLfAeSRI6cpx6P1foeuqCYhHmeQ8Xg9RHY,13774
7
+ runem/command_line.py,sha256=qkZFCCq9hUl6RO398SJzoigv8di5jGw2sdNwgTVBdd8,14474
8
8
  runem/config.py,sha256=UiEU0Jyg5qjrNStvasWYjMOABQHhpZjbPiX3-sH_CMg,5969
9
9
  runem/config_metadata.py,sha256=krDomUcADsAeUQrxwNmOS58eeaNIlqmhWIKWv8mUH4A,3300
10
- runem/config_parse.py,sha256=sXPMA8HSUcaJoq0dUfphK181waZqnIrq20mpYOQ_XCo,13498
10
+ runem/config_parse.py,sha256=zXQ4rpj-igQufB5JtTsI1mOE_gBTdBcI2hI6HWU28gg,13830
11
11
  runem/files.py,sha256=59boeFvUANYOS-PllIjeKIht6lNINZ43WxahDg90oAc,4392
12
12
  runem/hook_manager.py,sha256=H0TL3HCqU2mgKm_-dgCD7TsK5T1bLT4g7x6kpytMPhU,4350
13
13
  runem/informative_dict.py,sha256=U7p9z78UwOT4TAfng1iDXCEyeYz6C-XZlx9Z1pWNVrI,1548
14
14
  runem/job.py,sha256=NOdRQnGePPyYdmIR_6JKVFzp9nbgNGetpE13bHEHaf4,3442
15
- runem/job_execute.py,sha256=BPkeTpeTGJs3QWa0-07DZvF1f0uKO79e4yMsTxq1UHk,4656
15
+ runem/job_execute.py,sha256=-76IJI0PDU_XdQiDxTKUfOHEno9pixxQb_zi58rFumo,4702
16
16
  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
@@ -21,9 +21,10 @@ runem/log.py,sha256=dIrocigvIJs1ZGkAzTogXkAK-0ZW3q5FkjpDgLdeW-E,630
21
21
  runem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  runem/report.py,sha256=IedwW9mmJfGC6vKQdKDHQH5eoiYzTWHaaD4a3J4e_Pc,9020
23
23
  runem/run_command.py,sha256=Egl_j4bJ9mwi2JEFCsl0W6WH2IRgIdpMN7qdj8voClQ,6386
24
- runem/runem.py,sha256=tQg-4e--GmIBJrX3gP8dAUGQTCP6QOPiSSGwKxVmBac,13336
24
+ runem/runem.py,sha256=9QZgmXNPXOscRRLWDgOcPZ0lYRcG7jOog1M_o3MXZbs,13667
25
25
  runem/runem_version.py,sha256=MbETwZO2Tb1Y3hX_OYZjKepEMKA1cjNvr-7Cqhz6e3s,271
26
26
  runem/utils.py,sha256=3N_kel9LsriiMq7kOjT14XhfxUOgz4hdDg97wlLKm3U,221
27
+ runem/cli/initialise_options.py,sha256=zx_EduWQk7yGBr3XUNrffHCSInPv05edFItHLnlo9dk,918
27
28
  runem/types/__init__.py,sha256=6TzF4KV9tDGuDTr2qAXmWWkfDU52WuVlQ8Hcz48aYDk,286
28
29
  runem/types/common.py,sha256=gPMSoJ3yRUYjHnoviRrpSg0gRwsGLFGWGpbTWkq4jX0,279
29
30
  runem/types/errors.py,sha256=rbM5BA6UhY1X7Q0OZLUNsG7JXAjgNFTG5KQuqPNuZm8,103
@@ -32,11 +33,20 @@ runem/types/hooks.py,sha256=lgrv5QAuHCEzr5dXDj4-azNcs63addY9zdrGWj5zv_s,292
32
33
  runem/types/options.py,sha256=y8_hyWYvhalC9-kZbvoDtxm0trZgyyGcswQqfuQy_pM,265
33
34
  runem/types/runem_config.py,sha256=qG_bghm5Nr-ZTbaZbf1v8Fx447V-hgEvvRy5NZ3t-Io,5141
34
35
  runem/types/types_jobs.py,sha256=wqiiBmRIJDbGlKcfOqewHGKx350w0p4_7pysMm7xGmo,4906
36
+ scripts/test_hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ scripts/test_hooks/json_validators.py,sha256=N2FyWcpjWzfFGycXLo-ecNLJkxTFPPbqPfVBcJLBlb4,967
38
+ scripts/test_hooks/py.py,sha256=YUbwNny7NPmv2bY7k7YcbJ-jRcnNfjQajE9Hn1MLaBc,8821
39
+ scripts/test_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ scripts/test_hooks/runem_hooks.py,sha256=FJMuDBEOz3dr9gBW3WW6yKbUJs_LFXb3klpqSzCAZRk,628
41
+ scripts/test_hooks/yarn.py,sha256=1QsG1rKAclpZoqp86ntkuvzYaYN4UkEvO0JhO2Kf5C8,1082
42
+ tests/cli/test_initialise_options.py,sha256=Ogwfqz39r2o1waXMqCC22OJsgZoLF2stwGVO5AZUc4s,3148
43
+ tests/data/help_output.3.10.txt,sha256=5TUpNITVL6pD5BpFAl-Orh3vkOpStveijZzvgJuI_sA,4280
44
+ tests/data/help_output.3.11.txt,sha256=ycrF-xKgdQ8qrWzkkR-vbHe7NulUTsCsS0_Gda8xYDs,4162
35
45
  tests/test_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
46
  tests/test_types/test_public_api.py,sha256=QHiwt7CetQur65JSbFRnOzQxhCJkX5MVLymHHVd_6yc,160
37
- runem-0.4.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
38
- runem-0.4.0.dist-info/METADATA,sha256=EaVqmVWBeMkJI4tLfI7Nt0A5L3i_OkGCtD_Y8zTJ9kY,5842
39
- runem-0.4.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
40
- runem-0.4.0.dist-info/entry_points.txt,sha256=nu0g_vBeuPihYtimbtlNusxWovylMppvJ8UxdJlJfvM,46
41
- runem-0.4.0.dist-info/top_level.txt,sha256=Zu65aVeDPh8WbChU4Mi7-Md4S3XJDPuqdQjEE3DSQno,12
42
- runem-0.4.0.dist-info/RECORD,,
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,,
@@ -1,2 +1,3 @@
1
1
  runem
2
+ scripts
2
3
  tests
File without changes
@@ -0,0 +1,32 @@
1
+ import pathlib
2
+
3
+ from typing_extensions import Unpack
4
+
5
+ from runem.run_command import run_command
6
+ from runem.types import FilePathList, JobKwargs
7
+
8
+
9
+ def _json_validate(
10
+ **kwargs: Unpack[JobKwargs],
11
+ ) -> None:
12
+ label = kwargs["label"]
13
+ json_files: FilePathList = kwargs["file_list"]
14
+ json_with_comments = (
15
+ "cspell.json",
16
+ "tsconfig.spec.json",
17
+ "launch.json",
18
+ "settings.json",
19
+ )
20
+ for json_file in json_files:
21
+ json_path = pathlib.Path(json_file)
22
+ if not json_path.exists():
23
+ raise RuntimeError(
24
+ f"could not find '{str(json_path)}, in {pathlib.Path('.').absolute()}"
25
+ )
26
+ if json_path.name in json_with_comments:
27
+ # until we use a validator that allows comments in json, skip these
28
+ continue
29
+
30
+ cmd = ["python", "-m", "json.tool", f"{json_file}"]
31
+ kwargs["label"] = f"{label} {json_file}"
32
+ run_command(cmd=cmd, **kwargs)
@@ -0,0 +1,292 @@
1
+ import pathlib
2
+ import shutil
3
+ import typing
4
+
5
+ from typing_extensions import Unpack
6
+
7
+ from runem.log import log
8
+ from runem.run_command import RunCommandUnhandledError, run_command
9
+ from runem.types import FilePathList, JobKwargs, JobName, JobReturnData, Options
10
+
11
+
12
+ def _job_py_code_reformat(
13
+ **kwargs: Unpack[JobKwargs],
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
+ docformatter_extra_args = [
23
+ "--in-place",
24
+ ]
25
+ if options["check-only"]:
26
+ extra_args.append("--check")
27
+ docformatter_extra_args = [] # --inplace is not compatible with --check
28
+
29
+ if options["isort"]:
30
+ isort_cmd = [
31
+ "python3",
32
+ "-m",
33
+ "isort",
34
+ "--profile",
35
+ "black",
36
+ "--treat-all-comment-as-code",
37
+ *extra_args,
38
+ *python_files,
39
+ ]
40
+ kwargs["label"] = f"{label} isort"
41
+ run_command(cmd=isort_cmd, **kwargs)
42
+
43
+ if options["black"]:
44
+ black_cmd = [
45
+ "python3",
46
+ "-m",
47
+ "black",
48
+ *extra_args,
49
+ *python_files,
50
+ ]
51
+ kwargs["label"] = f"{label} black"
52
+ run_command(cmd=black_cmd, **kwargs)
53
+
54
+ if options["docformatter"]:
55
+ docformatter_cmd = [
56
+ "python3",
57
+ "-m",
58
+ "docformatter",
59
+ "--wrap-summaries",
60
+ "88",
61
+ "--wrap-descriptions",
62
+ "88",
63
+ *docformatter_extra_args,
64
+ *extra_args,
65
+ *python_files,
66
+ ]
67
+ allowed_exits: typing.Tuple[int, ...] = (
68
+ 0, # no work/change required
69
+ 3, # no errors, but code was reformatted
70
+ )
71
+ if options["check-only"]:
72
+ # in check it is ONLY ok if no work/change was required
73
+ allowed_exits = (0,)
74
+ kwargs["label"] = f"{label} docformatter"
75
+ run_command(
76
+ cmd=docformatter_cmd,
77
+ ignore_fails=False,
78
+ valid_exit_ids=allowed_exits,
79
+ **kwargs,
80
+ )
81
+
82
+
83
+ def _job_py_pylint(
84
+ **kwargs: Unpack[JobKwargs],
85
+ ) -> None:
86
+ python_files: FilePathList = kwargs["file_list"]
87
+ root_path: pathlib.Path = kwargs["root_path"]
88
+
89
+ pylint_cfg = root_path / ".pylint.rc"
90
+ if not pylint_cfg.exists():
91
+ raise RuntimeError(f"PYLINT Config not found at '{pylint_cfg}'")
92
+
93
+ pylint_cmd = [
94
+ "python3",
95
+ "-m",
96
+ "pylint",
97
+ "-j1",
98
+ "--score=n",
99
+ f"--rcfile={pylint_cfg}",
100
+ *python_files,
101
+ ]
102
+ run_command(cmd=pylint_cmd, **kwargs)
103
+
104
+
105
+ def _job_py_flake8(
106
+ **kwargs: Unpack[JobKwargs],
107
+ ) -> None:
108
+ python_files: FilePathList = kwargs["file_list"]
109
+ root_path: pathlib.Path = kwargs["root_path"]
110
+ flake8_rc = root_path / ".flake8"
111
+ if not flake8_rc.exists():
112
+ raise RuntimeError(f"flake8 config not found at '{flake8_rc}'")
113
+
114
+ flake8_cmd = [
115
+ "python3",
116
+ "-m",
117
+ "flake8",
118
+ *python_files,
119
+ ]
120
+ run_command(cmd=flake8_cmd, **kwargs)
121
+
122
+
123
+ def _job_py_mypy(
124
+ **kwargs: Unpack[JobKwargs],
125
+ ) -> None:
126
+ python_files: FilePathList = kwargs["file_list"]
127
+ mypy_cmd = ["python3", "-m", "mypy", *python_files]
128
+ output = run_command(cmd=mypy_cmd, **kwargs)
129
+ if "mypy.ini" in output or "Not a boolean:" in output:
130
+ raise RunCommandUnhandledError(f"runem: mypy mis-config detected: {output}")
131
+
132
+
133
+ def _delete_old_coverage_reports(root_path: pathlib.Path) -> None:
134
+ """To avoid false-positives on coverage we delete the coverage report files."""
135
+ old_coverage_report_files: typing.List[pathlib.Path] = list(
136
+ root_path.glob(".coverage_report*")
137
+ )
138
+ for old_coverage_report in old_coverage_report_files:
139
+ old_coverage_report.unlink()
140
+
141
+
142
+ def _job_py_pytest( # noqa: C901 # pylint: disable=too-many-branches,too-many-statements
143
+ **kwargs: Unpack[JobKwargs],
144
+ ) -> JobReturnData:
145
+ label: JobName = kwargs["label"]
146
+ options: Options = kwargs["options"]
147
+ procs: int = kwargs["procs"]
148
+ root_path: pathlib.Path = kwargs["root_path"]
149
+
150
+ reports: JobReturnData = {"reportUrls": []}
151
+ # TODO: use pytest.ini config pytest
152
+ # pytest_cfg = root_path / ".pytest.ini"
153
+ # assert pytest_cfg.exists()
154
+
155
+ if not options["unit-test"]:
156
+ # we've disabled unit-testing on the cli
157
+ return reports
158
+
159
+ if options["profile"]:
160
+ raise RuntimeError("not implemented - see run_test.sh for how to implement")
161
+
162
+ pytest_path = root_path / "tests"
163
+ assert pytest_path.exists()
164
+
165
+ coverage_switches: typing.List[str] = []
166
+ coverage_cfg = root_path / ".coveragerc"
167
+ if options["coverage"]:
168
+ _delete_old_coverage_reports(root_path)
169
+ assert coverage_cfg.exists()
170
+ coverage_switches = [
171
+ "--cov=.",
172
+ f"--cov-config={str(coverage_cfg)}",
173
+ "--cov-append",
174
+ "--no-cov-on-fail", # do not show coverage terminal report when we fail
175
+ "--cov-fail-under=0", # we do coverage filing later
176
+ ]
177
+
178
+ # TODO: do we want to disable logs on pytest runs?
179
+ # "PYTEST_LOG":"--no-print-logs --log-level=CRITICAL" ;
180
+
181
+ threading_switches: typing.List[str] = []
182
+ if procs == -1:
183
+ threading_switches = ["-n", "auto"]
184
+
185
+ verbose_switches: typing.List[str] = []
186
+ if "verbose" in kwargs and kwargs["verbose"]:
187
+ verbose_switches = ["-vvv"]
188
+
189
+ profile_switches: typing.List[str] = []
190
+ cmd_pytest = [
191
+ "python3",
192
+ "-m",
193
+ "pytest",
194
+ "--color=yes",
195
+ *threading_switches,
196
+ # "-c",
197
+ # str(pytest_cfg),
198
+ *coverage_switches,
199
+ "--failed-first",
200
+ "--exitfirst",
201
+ *profile_switches,
202
+ *verbose_switches,
203
+ str(pytest_path),
204
+ ]
205
+
206
+ env_overrides: typing.Dict[str, str] = {}
207
+
208
+ kwargs["label"] = f"{label} pytest"
209
+ run_command(
210
+ cmd=cmd_pytest,
211
+ env_overrides=env_overrides,
212
+ **kwargs,
213
+ )
214
+
215
+ if options["coverage"]:
216
+ reports_dir: pathlib.Path = root_path / "reports"
217
+ reports_dir.mkdir(parents=False, exist_ok=True)
218
+ coverage_output_dir: pathlib.Path = reports_dir / "coverage_python"
219
+ if coverage_output_dir.exists():
220
+ shutil.rmtree(coverage_output_dir)
221
+ coverage_output_dir.mkdir(exist_ok=True)
222
+ if kwargs["verbose"]:
223
+ log("COVERAGE: Collating coverage")
224
+ # first generate the coverage report for our gitlab cicd
225
+ gen_cobertura_coverage_report_cmd = [
226
+ "python3",
227
+ "-m",
228
+ "coverage",
229
+ "xml",
230
+ "-o",
231
+ str(coverage_output_dir / "cobertura.xml"),
232
+ f"--rcfile={str(coverage_cfg)}",
233
+ ]
234
+ kwargs["label"] = f"{label} coverage cobertura"
235
+ run_command(cmd=gen_cobertura_coverage_report_cmd, **kwargs)
236
+
237
+ # then a html report
238
+ gen_html_coverage_report_cmd = [
239
+ "python3",
240
+ "-m",
241
+ "coverage",
242
+ "html",
243
+ f"--rcfile={str(coverage_cfg)}",
244
+ ]
245
+ kwargs["label"] = f"{label} coverage html"
246
+ run_command(cmd=gen_html_coverage_report_cmd, **kwargs)
247
+
248
+ # then a standard command-line report that causes the tests to fail.
249
+ gen_cli_coverage_report_cmd = [
250
+ "python3",
251
+ "-m",
252
+ "coverage",
253
+ "report",
254
+ "--fail-under=100",
255
+ f"--rcfile={str(coverage_cfg)}",
256
+ ]
257
+ kwargs["label"] = f"{label} coverage cli"
258
+ report_html = coverage_output_dir / "index.html"
259
+ report_cobertura = coverage_output_dir / "cobertura.xml"
260
+ try:
261
+ run_command(cmd=gen_cli_coverage_report_cmd, **kwargs)
262
+ except BaseException:
263
+ print()
264
+ print(report_html)
265
+ print(report_cobertura)
266
+ raise
267
+ assert coverage_output_dir.exists(), coverage_output_dir
268
+ assert report_html.exists(), report_html
269
+ assert report_cobertura.exists(), report_cobertura
270
+ reports["reportUrls"].append(("coverage html", report_html))
271
+ reports["reportUrls"].append(("coverage cobertura", report_cobertura))
272
+ if kwargs["verbose"]:
273
+ log("COVERAGE: cli output done")
274
+ return reports
275
+
276
+
277
+ def _install_python_requirements(
278
+ **kwargs: Unpack[JobKwargs],
279
+ ) -> None:
280
+ options: Options = kwargs["options"]
281
+ if not (options["install-deps"]):
282
+ # not enabled
283
+ return
284
+ cmd = [
285
+ "python3",
286
+ "-m",
287
+ "pip",
288
+ "install",
289
+ "-e",
290
+ ".[tests]",
291
+ ]
292
+ run_command(cmd=cmd, **kwargs)
File without changes
@@ -0,0 +1,19 @@
1
+ import pathlib
2
+ from datetime import timedelta
3
+
4
+ from typing_extensions import Unpack
5
+
6
+ from runem.types import HookKwargs
7
+
8
+
9
+ def _on_exit_hook(
10
+ **kwargs: Unpack[HookKwargs],
11
+ ) -> None:
12
+ """A noddy hook."""
13
+ assert "wall_clock_time_saved" in kwargs
14
+ wall_clock_time_saved: timedelta = kwargs["wall_clock_time_saved"]
15
+ root_path: pathlib.Path = pathlib.Path(__file__).parent.parent.parent
16
+ assert (root_path / ".runem.yml").exists()
17
+ times_log: pathlib.Path = root_path / ".times.log"
18
+ with times_log.open("a", encoding="utf-8") as file:
19
+ file.write(f"{str(wall_clock_time_saved.total_seconds())}\n")
@@ -0,0 +1,48 @@
1
+ import pathlib
2
+
3
+ from typing_extensions import Unpack
4
+
5
+ from runem.run_command import run_command
6
+ from runem.types import JobKwargs, Options
7
+
8
+
9
+ def _job_yarn_deps(
10
+ **kwargs: Unpack[JobKwargs],
11
+ ) -> None:
12
+ """Installs the yarn deps."""
13
+ options: Options = kwargs["options"]
14
+
15
+ install_requested = options["install-deps"]
16
+ if not (install_requested):
17
+ root_path: pathlib.Path = kwargs["root_path"]
18
+ if (root_path / "node_modules").exists():
19
+ # An install was not requested, nor required.
20
+ return
21
+
22
+ install_cmd = [
23
+ "yarn",
24
+ "install",
25
+ ]
26
+
27
+ run_command(cmd=install_cmd, **kwargs)
28
+
29
+
30
+ def _job_prettier(
31
+ **kwargs: Unpack[JobKwargs],
32
+ ) -> None:
33
+ """Runs prettifier on files, including json and maybe yml file.
34
+
35
+ TODO: connect me up!
36
+ """
37
+ options: Options = kwargs["options"]
38
+ command_variant = "pretty"
39
+ if options["check-only"]:
40
+ command_variant = "prettyCheck"
41
+
42
+ pretty_cmd = [
43
+ "yarn",
44
+ "run",
45
+ command_variant,
46
+ ]
47
+
48
+ run_command(cmd=pretty_cmd, **kwargs)
@@ -0,0 +1,105 @@
1
+ import pathlib
2
+ from argparse import Namespace
3
+ from collections import defaultdict
4
+ from typing import Dict
5
+ from unittest.mock import MagicMock
6
+
7
+ import pytest
8
+
9
+ from runem.command_line import initialise_options
10
+ from runem.config_metadata import ConfigMetadata
11
+ from runem.types.runem_config import JobConfig, PhaseGroupedJobs
12
+
13
+ Options = Dict[str, bool]
14
+
15
+
16
+ @pytest.fixture(name="config_metadata")
17
+ def config_metadata_fixture() -> ConfigMetadata:
18
+ config_file_path = pathlib.Path(__file__).parent / ".runem.yml"
19
+ expected_job: JobConfig = {
20
+ "addr": {
21
+ "file": "test_config_parse.py",
22
+ "function": "test_parse_config",
23
+ },
24
+ "label": "dummy job label",
25
+ "when": {
26
+ "phase": "dummy phase 1",
27
+ "tags": {"dummy tag 1", "dummy tag 2"},
28
+ },
29
+ }
30
+ expected_jobs: PhaseGroupedJobs = defaultdict(list)
31
+ expected_jobs["dummy phase 1"] = [
32
+ expected_job,
33
+ ]
34
+ return ConfigMetadata(
35
+ cfg_filepath=config_file_path,
36
+ phases=("dummy phase 1",),
37
+ options_config=(
38
+ {"name": "option1", "default": True},
39
+ {"name": "option2", "default": False},
40
+ {"name": "option3", "default": True},
41
+ {"name": "option4", "default": False},
42
+ ),
43
+ file_filters={
44
+ # "dummy tag": {
45
+ # "tag": "dummy tag",
46
+ # "regex": ".*1.txt", # should match just one file
47
+ # }
48
+ },
49
+ hook_manager=MagicMock(),
50
+ jobs=expected_jobs,
51
+ all_job_names=set(("dummy job label",)),
52
+ all_job_phases=set(("dummy phase 1",)),
53
+ all_job_tags=set(
54
+ (
55
+ "dummy tag 2",
56
+ "dummy tag 1",
57
+ )
58
+ ),
59
+ )
60
+
61
+
62
+ def test_initialise_options_no_overrides(config_metadata: ConfigMetadata) -> None:
63
+ args = Namespace(overrides_on=[], overrides_off=[])
64
+ options = initialise_options(config_metadata, args)
65
+ assert options == {
66
+ "option1": True,
67
+ "option2": False,
68
+ "option3": True,
69
+ "option4": False,
70
+ }
71
+
72
+
73
+ def test_initialise_options_overrides_on(config_metadata: ConfigMetadata) -> None:
74
+ args = Namespace(overrides_on=["option2", "option4"], overrides_off=[])
75
+ options = initialise_options(config_metadata, args)
76
+ assert options == {
77
+ "option1": True,
78
+ "option2": True,
79
+ "option3": True,
80
+ "option4": True,
81
+ }
82
+
83
+
84
+ def test_initialise_options_overrides_off(config_metadata: ConfigMetadata) -> None:
85
+ args = Namespace(overrides_on=[], overrides_off=["option1", "option3"])
86
+ options = initialise_options(config_metadata, args)
87
+ assert options == {
88
+ "option1": False,
89
+ "option2": False,
90
+ "option3": False,
91
+ "option4": False,
92
+ }
93
+
94
+
95
+ def test_initialise_options_overrides_on_and_off(
96
+ config_metadata: ConfigMetadata,
97
+ ) -> None:
98
+ args = Namespace(overrides_on=["option2"], overrides_off=["option1"])
99
+ options = initialise_options(config_metadata, args)
100
+ assert options == {
101
+ "option1": False,
102
+ "option2": True,
103
+ "option3": True,
104
+ "option4": False,
105
+ }
@@ -0,0 +1,55 @@
1
+ runem: WARNING: no phase found for 'echo "hello world!"', using 'dummy phase 1'
2
+ usage: -c [-H] [--jobs JOBS [JOBS ...]] [--not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]] [--phases PHASES [PHASES ...]] [--not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]] [--tags TAGS [TAGS ...]] [--not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]] [--dummy-option-1---complete-option] [--no-dummy-option-1---complete-option] [--dummy-option-2---minimal] [--no-dummy-option-2---minimal] [--call-graphs | --no-call-graphs] [-f | --modified-files | --no-modified-files] [-h | --git-head-files | --no-git-head-files] [--always-files ALWAYS_FILES [ALWAYS_FILES ...]] [--git-files-since-branch GIT_SINCE_BRANCH] [--procs PROCS] [--root ROOT_DIR] [--root-show | --no-root-show] [--silent | --no-silent | -s] [--spinner | --no-spinner] [--verbose | --no-verbose] [--version | --no-version | -v]
3
+
4
+ Runs the Lursight Lang test-suite
5
+
6
+ [TEST_REPLACED_OPTION_HEADER]
7
+ -H, --help show this help message and exit
8
+ --call-graphs, --no-call-graphs
9
+ -f, --modified-files, --no-modified-files
10
+ only use files that have changed (default: False)
11
+ -h, --git-head-files, --no-git-head-files
12
+ fast run of files (default: False)
13
+ --always-files ALWAYS_FILES [ALWAYS_FILES ...]
14
+ list of paths/files to always check (overriding -f/-h), if the path matches the filter regex and if file-paths exist
15
+ --git-files-since-branch GIT_SINCE_BRANCH
16
+ Get the list of paths/files changed between a branch, e.g., since 'origin/main'. Useful for checking files changed before pushing.
17
+ --procs PROCS, -j PROCS
18
+ the number of concurrent test jobs to run, -1 runs all test jobs at the same time ([TEST_REPLACED_CORES] cores available)
19
+ --root ROOT_DIR which dir to use as the base-dir for testing, defaults to directory containing the config '[TEST_REPLACED_DIR]'
20
+ --root-show, --no-root-show
21
+ show the root-path of runem and exit (default: False)
22
+ --silent, --no-silent, -s
23
+ Whether to show warning messages or not. (default: False)
24
+ --spinner, --no-spinner
25
+ Whether to show the progress spinner or not. Helps reduce log-spam in ci/cd. (default: True)
26
+ --verbose, --no-verbose
27
+ runs runem in in verbose mode, and streams jobs stdout/stderr to console (default: False)
28
+ --version, --no-version, -v
29
+ show the version of runem and exit (default: False)
30
+
31
+ jobs:
32
+ --jobs JOBS [JOBS ...]
33
+ List of job-names to run the given jobs. Other filters will modify this list. Defaults to 'dummy job label 1', 'dummy job label 2', 'echo "hello world!"', 'hello world'
34
+ --not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]
35
+ List of job-names to NOT run. Defaults to empty. Available options are: 'dummy job label 1', 'dummy job label 2', 'echo "hello world!"', 'hello world'
36
+
37
+ phases:
38
+ --phases PHASES [PHASES ...]
39
+ Run only the phases passed in, and can be used to change the phase order. Phases are run in the order given. Defaults to 'dummy phase 1', 'dummy phase 2'.
40
+ --not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]
41
+ List of phases to NOT run. This option does not change the phase run order. Options are '['dummy phase 1', 'dummy phase 2']'.
42
+
43
+ tags:
44
+ --tags TAGS [TAGS ...]
45
+ Only run jobs with the given tags. Defaults to '['dummy tag 1', 'dummy tag 2', 'tag only on job 1', 'tag only on job 2']'.
46
+ --not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]
47
+ Removes one or more tags from the list of job tags to be run. Options are '['dummy tag 1', 'dummy tag 2', 'tag only on job 1', 'tag only on job 2']'.
48
+
49
+ job-param overrides:
50
+ --dummy-option-1---complete-option, --dummy option 1 multi alias 1, --dummy option 1 multi alias 2, -x, --dummy option alias 1
51
+ a dummy option description
52
+ --no-dummy-option-1---complete-option, --no-dummy option 1 multi alias 1, --no-dummy option 1 multi alias 2, --no-x, --no-dummy option alias 1
53
+ turn off a dummy option description
54
+ --dummy-option-2---minimal
55
+ --no-dummy-option-2---minimal
@@ -0,0 +1,55 @@
1
+ runem: WARNING: no phase found for 'echo "hello world!"', using 'dummy phase 1'
2
+ usage: -c [-H] [--jobs JOBS [JOBS ...]] [--not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]] [--phases PHASES [PHASES ...]] [--not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]] [--tags TAGS [TAGS ...]] [--not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]] [--dummy-option-1---complete-option] [--no-dummy-option-1---complete-option] [--dummy-option-2---minimal] [--no-dummy-option-2---minimal] [--call-graphs | --no-call-graphs] [-f | --modified-files | --no-modified-files] [-h | --git-head-files | --no-git-head-files] [--always-files ALWAYS_FILES [ALWAYS_FILES ...]] [--git-files-since-branch GIT_SINCE_BRANCH] [--procs PROCS] [--root ROOT_DIR] [--root-show | --no-root-show] [--silent | --no-silent | -s] [--spinner | --no-spinner] [--verbose | --no-verbose] [--version | --no-version | -v]
3
+
4
+ Runs the Lursight Lang test-suite
5
+
6
+ [TEST_REPLACED_OPTION_HEADER]
7
+ -H, --help show this help message and exit
8
+ --call-graphs, --no-call-graphs
9
+ -f, --modified-files, --no-modified-files
10
+ only use files that have changed
11
+ -h, --git-head-files, --no-git-head-files
12
+ fast run of files
13
+ --always-files ALWAYS_FILES [ALWAYS_FILES ...]
14
+ list of paths/files to always check (overriding -f/-h), if the path matches the filter regex and if file-paths exist
15
+ --git-files-since-branch GIT_SINCE_BRANCH
16
+ Get the list of paths/files changed between a branch, e.g., since 'origin/main'. Useful for checking files changed before pushing.
17
+ --procs PROCS, -j PROCS
18
+ the number of concurrent test jobs to run, -1 runs all test jobs at the same time ([TEST_REPLACED_CORES] cores available)
19
+ --root ROOT_DIR which dir to use as the base-dir for testing, defaults to directory containing the config '[TEST_REPLACED_DIR]'
20
+ --root-show, --no-root-show
21
+ show the root-path of runem and exit
22
+ --silent, --no-silent, -s
23
+ Whether to show warning messages or not.
24
+ --spinner, --no-spinner
25
+ Whether to show the progress spinner or not. Helps reduce log-spam in ci/cd.
26
+ --verbose, --no-verbose
27
+ runs runem in in verbose mode, and streams jobs stdout/stderr to console
28
+ --version, --no-version, -v
29
+ show the version of runem and exit
30
+
31
+ jobs:
32
+ --jobs JOBS [JOBS ...]
33
+ List of job-names to run the given jobs. Other filters will modify this list. Defaults to 'dummy job label 1', 'dummy job label 2', 'echo "hello world!"', 'hello world'
34
+ --not-jobs JOBS_EXCLUDED [JOBS_EXCLUDED ...]
35
+ List of job-names to NOT run. Defaults to empty. Available options are: 'dummy job label 1', 'dummy job label 2', 'echo "hello world!"', 'hello world'
36
+
37
+ phases:
38
+ --phases PHASES [PHASES ...]
39
+ Run only the phases passed in, and can be used to change the phase order. Phases are run in the order given. Defaults to 'dummy phase 1', 'dummy phase 2'.
40
+ --not-phases PHASES_EXCLUDED [PHASES_EXCLUDED ...]
41
+ List of phases to NOT run. This option does not change the phase run order. Options are '['dummy phase 1', 'dummy phase 2']'.
42
+
43
+ tags:
44
+ --tags TAGS [TAGS ...]
45
+ Only run jobs with the given tags. Defaults to '['dummy tag 1', 'dummy tag 2', 'tag only on job 1', 'tag only on job 2']'.
46
+ --not-tags TAGS_EXCLUDED [TAGS_EXCLUDED ...]
47
+ Removes one or more tags from the list of job tags to be run. Options are '['dummy tag 1', 'dummy tag 2', 'tag only on job 1', 'tag only on job 2']'.
48
+
49
+ job-param overrides:
50
+ --dummy-option-1---complete-option, --dummy option 1 multi alias 1, --dummy option 1 multi alias 2, -x, --dummy option alias 1
51
+ a dummy option description
52
+ --no-dummy-option-1---complete-option, --no-dummy option 1 multi alias 1, --no-dummy option 1 multi alias 2, --no-x, --no-dummy option alias 1
53
+ turn off a dummy option description
54
+ --dummy-option-2---minimal
55
+ --no-dummy-option-2---minimal
File without changes
File without changes