looper 1.5.1__tar.gz → 1.6.0a1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. {looper-1.5.1/looper.egg-info → looper-1.6.0a1}/PKG-INFO +14 -1
  2. looper-1.6.0a1/looper/__init__.py +44 -0
  3. {looper-1.5.1 → looper-1.6.0a1}/looper/__main__.py +2 -2
  4. looper-1.6.0a1/looper/_version.py +1 -0
  5. looper-1.6.0a1/looper/cli_divvy.py +182 -0
  6. looper-1.5.1/looper/__init__.py → looper-1.6.0a1/looper/cli_looper.py +301 -64
  7. {looper-1.5.1 → looper-1.6.0a1}/looper/conductor.py +53 -206
  8. {looper-1.5.1 → looper-1.6.0a1}/looper/const.py +51 -3
  9. {looper-1.5.1 → looper-1.6.0a1}/looper/divvy.py +28 -196
  10. {looper-1.5.1 → looper-1.6.0a1}/looper/exceptions.py +18 -0
  11. looper-1.6.0a1/looper/looper.py +777 -0
  12. looper-1.6.0a1/looper/plugins.py +160 -0
  13. {looper-1.5.1 → looper-1.6.0a1}/looper/processed_project.py +1 -1
  14. {looper-1.5.1 → looper-1.6.0a1}/looper/project.py +229 -117
  15. {looper-1.5.1 → looper-1.6.0a1}/looper/utils.py +119 -43
  16. {looper-1.5.1 → looper-1.6.0a1/looper.egg-info}/PKG-INFO +14 -1
  17. {looper-1.5.1 → looper-1.6.0a1}/looper.egg-info/SOURCES.txt +7 -4
  18. {looper-1.5.1 → looper-1.6.0a1}/looper.egg-info/requires.txt +5 -5
  19. {looper-1.5.1 → looper-1.6.0a1}/requirements/requirements-all.txt +5 -5
  20. looper-1.6.0a1/tests/test_clean.py +37 -0
  21. looper-1.6.0a1/tests/test_desired_sample_range.py +61 -0
  22. looper-1.6.0a1/tests/test_natural_range.py +196 -0
  23. looper-1.5.1/looper/_version.py +0 -1
  24. looper-1.5.1/looper/html_reports.py +0 -1057
  25. looper-1.5.1/looper/html_reports_pipestat.py +0 -924
  26. looper-1.5.1/looper/html_reports_project_pipestat.py +0 -269
  27. looper-1.5.1/looper/looper.py +0 -1212
  28. {looper-1.5.1 → looper-1.6.0a1}/LICENSE.txt +0 -0
  29. {looper-1.5.1 → looper-1.6.0a1}/MANIFEST.in +0 -0
  30. {looper-1.5.1 → looper-1.6.0a1}/README.md +0 -0
  31. {looper-1.5.1 → looper-1.6.0a1}/logo_looper.svg +0 -0
  32. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_config.yaml +0 -0
  33. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/localhost_bulker_template.sub +0 -0
  34. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/localhost_docker_template.sub +0 -0
  35. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/localhost_singularity_template.sub +0 -0
  36. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/localhost_template.sub +0 -0
  37. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/lsf_template.sub +0 -0
  38. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/sge_template.sub +0 -0
  39. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/slurm_singularity_template.sub +0 -0
  40. {looper-1.5.1 → looper-1.6.0a1}/looper/default_config/divvy_templates/slurm_template.sub +0 -0
  41. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/footer.html +0 -0
  42. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/footer_index.html +0 -0
  43. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/head.html +0 -0
  44. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/index.html +0 -0
  45. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/logo.html +0 -0
  46. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/navbar.html +0 -0
  47. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/navbar_links.html +0 -0
  48. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/navbar_list_parent.html +0 -0
  49. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/object.html +0 -0
  50. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/project_object.html +0 -0
  51. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/sample.html +0 -0
  52. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/status.html +0 -0
  53. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/status_table.html +0 -0
  54. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates/status_table_no_links.html +0 -0
  55. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/footer.html +0 -0
  56. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/footer_index.html +0 -0
  57. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/head.html +0 -0
  58. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/index.html +0 -0
  59. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/logo.html +0 -0
  60. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/navbar.html +0 -0
  61. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/navbar_links.html +0 -0
  62. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/navbar_list_parent.html +0 -0
  63. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/object.html +0 -0
  64. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/project_object.html +0 -0
  65. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/sample.html +0 -0
  66. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/status.html +0 -0
  67. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/status_table.html +0 -0
  68. {looper-1.5.1 → looper-1.6.0a1}/looper/jinja_templates_old/status_table_no_links.html +0 -0
  69. {looper-1.5.1 → looper-1.6.0a1}/looper/parser_types.py +0 -0
  70. {looper-1.5.1 → looper-1.6.0a1}/looper/pipeline_interface.py +0 -0
  71. {looper-1.5.1 → looper-1.6.0a1}/looper/schemas/divvy_config_schema.yaml +0 -0
  72. {looper-1.5.1 → looper-1.6.0a1}/looper/schemas/pipeline_interface_schema_generic.yaml +0 -0
  73. {looper-1.5.1 → looper-1.6.0a1}/looper/schemas/pipeline_interface_schema_project.yaml +0 -0
  74. {looper-1.5.1 → looper-1.6.0a1}/looper/schemas/pipeline_interface_schema_sample.yaml +0 -0
  75. {looper-1.5.1 → looper-1.6.0a1}/looper.egg-info/dependency_links.txt +0 -0
  76. {looper-1.5.1 → looper-1.6.0a1}/looper.egg-info/entry_points.txt +0 -0
  77. {looper-1.5.1 → looper-1.6.0a1}/looper.egg-info/top_level.txt +0 -0
  78. {looper-1.5.1 → looper-1.6.0a1}/requirements/requirements-doc.txt +0 -0
  79. {looper-1.5.1 → looper-1.6.0a1}/requirements/requirements-test.txt +0 -0
  80. {looper-1.5.1 → looper-1.6.0a1}/setup.cfg +0 -0
  81. {looper-1.5.1 → looper-1.6.0a1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: looper
3
- Version: 1.5.1
3
+ Version: 1.6.0a1
4
4
  Summary: A pipeline submission engine that parses sample inputs and submits pipelines for each sample.
5
5
  Home-page: https://github.com/pepkit/looper
6
6
  Author: Nathan Sheffield, Vince Reuter, Michal Stolarczyk, Johanna Klughammer, Andre Rendeiro
@@ -15,6 +15,19 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.txt
18
+ Requires-Dist: colorama>=0.3.9
19
+ Requires-Dist: divvy>=0.5.0
20
+ Requires-Dist: eido>=0.2.1
21
+ Requires-Dist: jinja2
22
+ Requires-Dist: logmuse>=0.2.0
23
+ Requires-Dist: pandas>=2.0.2
24
+ Requires-Dist: pephubclient>=0.1.2
25
+ Requires-Dist: peppy>=0.40.0.a4
26
+ Requires-Dist: pipestat>=v0.6.0a9
27
+ Requires-Dist: pyyaml>=3.12
28
+ Requires-Dist: rich>=9.10.0
29
+ Requires-Dist: ubiquerg>=0.5.2
30
+ Requires-Dist: yacman>=0.9.2
18
31
 
19
32
  # <img src="docs/img/looper_logo.svg" alt="looper logo" height="70">
20
33
 
@@ -0,0 +1,44 @@
1
+ """Project configuration, particularly for logging.
2
+
3
+ Project-scope constants may reside here, but more importantly, some setup here
4
+ will provide a logging infrastructure for all of the project's modules.
5
+ Individual modules and classes may provide separate configuration on a more
6
+ local level, but this will at least provide a foundation.
7
+
8
+ """
9
+
10
+ import logmuse
11
+
12
+ logmuse.init_logger("looper")
13
+
14
+ from .divvy import ComputingConfiguration, select_divvy_config
15
+ from .divvy import DEFAULT_COMPUTE_RESOURCES_NAME
16
+ from .divvy import NEW_COMPUTE_KEY as COMPUTE_KEY
17
+
18
+ from ._version import __version__
19
+ from .conductor import (
20
+ SubmissionConductor,
21
+ write_submission_yaml,
22
+ )
23
+ from .plugins import (
24
+ write_sample_yaml,
25
+ write_sample_yaml_cwl,
26
+ write_sample_yaml_prj,
27
+ write_custom_template,
28
+ )
29
+ from .const import *
30
+ from .pipeline_interface import PipelineInterface
31
+ from .project import Project
32
+
33
+ # Not used here, but make this the main import interface between peppy and
34
+ # looper, so that other modules within this package need not worry about
35
+ # the locations of some of the peppy declarations. Effectively, concentrate
36
+ # the connection between peppy and looper here, to the extent possible.
37
+
38
+ __all__ = [
39
+ "Project",
40
+ "PipelineInterface",
41
+ "SubmissionConductor",
42
+ "ComputingConfiguration",
43
+ "select_divvy_config",
44
+ ]
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
 
3
- from .looper import main
4
- from .divvy import main as divvy_main
3
+ from .cli_looper import main
4
+ from .cli_divvy import main as divvy_main
5
5
 
6
6
  if __name__ == "__main__":
7
7
  try:
@@ -0,0 +1 @@
1
+ __version__ = "1.6.0a1"
@@ -0,0 +1,182 @@
1
+ import logmuse
2
+ import os
3
+ import sys
4
+ import yaml
5
+ from yaml import SafeLoader
6
+ from ubiquerg import is_writable, VersionInHelpParser
7
+ from .const import (
8
+ DEFAULT_COMPUTE_RESOURCES_NAME,
9
+ DEFAULT_CONFIG_FILEPATH,
10
+ )
11
+ from .divvy import select_divvy_config, ComputingConfiguration, divvy_init
12
+
13
+
14
+ def build_argparser():
15
+ """
16
+ Builds argument parser.
17
+
18
+ :return argparse.ArgumentParser
19
+ """
20
+
21
+ banner = (
22
+ "%(prog)s - write compute job scripts that can be submitted to "
23
+ "any computing resource"
24
+ )
25
+ additional_description = "\nhttps://divvy.databio.org"
26
+
27
+ parser = VersionInHelpParser(
28
+ prog="divvy",
29
+ description=banner,
30
+ epilog=additional_description,
31
+ # version=__version__,
32
+ )
33
+
34
+ subparsers = parser.add_subparsers(dest="command")
35
+
36
+ def add_subparser(cmd, description):
37
+ return subparsers.add_parser(cmd, description=description, help=description)
38
+
39
+ subparser_messages = {
40
+ "init": "Initialize a new divvy config file",
41
+ "list": "List available compute packages",
42
+ "write": "Write a job script",
43
+ "submit": "Write and then submit a job script",
44
+ "inspect": "Inspect compute package",
45
+ }
46
+
47
+ sps = {}
48
+ for cmd, desc in subparser_messages.items():
49
+ sps[cmd] = add_subparser(cmd, desc)
50
+ # sps[cmd].add_argument(
51
+ # "config", nargs="?", default=None,
52
+ # help="Divvy configuration file.")
53
+
54
+ for sp in [sps["list"], sps["write"], sps["submit"], sps["inspect"]]:
55
+ sp.add_argument(
56
+ "config", nargs="?", default=None, help="Divvy configuration file."
57
+ )
58
+
59
+ sps["init"].add_argument("config", default=None, help="Divvy configuration file.")
60
+
61
+ for sp in [sps["inspect"]]:
62
+ sp.add_argument(
63
+ "-p",
64
+ "--package",
65
+ default=DEFAULT_COMPUTE_RESOURCES_NAME,
66
+ help="Select from available compute packages",
67
+ )
68
+
69
+ for sp in [sps["write"], sps["submit"]]:
70
+ sp.add_argument(
71
+ "-s",
72
+ "--settings",
73
+ help="YAML file with job settings to populate the template",
74
+ )
75
+
76
+ sp.add_argument(
77
+ "-p",
78
+ "--package",
79
+ default=DEFAULT_COMPUTE_RESOURCES_NAME,
80
+ help="Select from available compute packages",
81
+ )
82
+
83
+ sp.add_argument(
84
+ "-c",
85
+ "--compute",
86
+ nargs="+",
87
+ default=None,
88
+ help="Extra key=value variable pairs",
89
+ )
90
+
91
+ # sp.add_argument(
92
+ # "-t", "--template",
93
+ # help="Provide a template file (not yet implemented).")
94
+
95
+ sp.add_argument(
96
+ "-o", "--outfile", required=False, default=None, help="Output filepath"
97
+ )
98
+
99
+ return parser
100
+
101
+
102
+ def main():
103
+ """Primary workflow for divvy CLI"""
104
+
105
+ parser = logmuse.add_logging_options(build_argparser())
106
+ # args, remaining_args = parser.parse_known_args()
107
+ args = parser.parse_args()
108
+
109
+ logger_kwargs = {"level": args.verbosity, "devmode": args.logdev}
110
+ logmuse.init_logger("yacman", **logger_kwargs)
111
+ global _LOGGER
112
+ _LOGGER = logmuse.logger_via_cli(args)
113
+
114
+ if not args.command:
115
+ parser.print_help()
116
+ _LOGGER.error("No command given")
117
+ sys.exit(1)
118
+
119
+ if args.command == "init":
120
+ divcfg = args.config
121
+ _LOGGER.debug("Initializing divvy configuration")
122
+ is_writable(os.path.dirname(divcfg), check_exist=False)
123
+ divvy_init(divcfg, DEFAULT_CONFIG_FILEPATH)
124
+ sys.exit(0)
125
+
126
+ _LOGGER.debug("Divvy config: {}".format(args.config))
127
+ divcfg = select_divvy_config(args.config)
128
+ _LOGGER.info("Using divvy config: {}".format(divcfg))
129
+ dcc = ComputingConfiguration(filepath=divcfg)
130
+
131
+ if args.command == "list":
132
+ # Output header via logger and content via print so the user can
133
+ # redirect the list from stdout if desired without the header as clutter
134
+ _LOGGER.info("Available compute packages:\n")
135
+ print("{}".format("\n".join(dcc.list_compute_packages())))
136
+ sys.exit(1)
137
+
138
+ if args.command == "inspect":
139
+ # Output contents of selected compute package
140
+ _LOGGER.info("Your compute package template for: " + args.package + "\n")
141
+ found = False
142
+ for pkg_name, pkg in dcc.compute_packages.items():
143
+ if pkg_name == args.package:
144
+ found = True
145
+ with open(pkg.submission_template, "r") as f:
146
+ print(f.read())
147
+ _LOGGER.info("Submission command is: " + pkg.submission_command + "\n")
148
+ if pkg_name == "docker":
149
+ print("Docker args are: " + pkg.docker_args)
150
+
151
+ if not found:
152
+ _LOGGER.info("Package not found. Use 'divvy list' to see list of packages.")
153
+ sys.exit(1)
154
+
155
+ # Any non-divvy arguments will be passed along as key-value pairs
156
+ # that can be used to populate the template.
157
+ # keys = [str.replace(x, "--", "") for x in remaining_args[::2]]
158
+ # cli_vars = dict(zip(keys, remaining_args[1::2]))
159
+ if args.compute:
160
+ cli_vars = {y[0]: y[1] for y in [x.split("=") for x in args.compute]}
161
+ else:
162
+ cli_vars = {}
163
+
164
+ if args.command == "write" or args.command == "submit":
165
+ try:
166
+ dcc.activate_package(args.package)
167
+ except AttributeError:
168
+ parser.print_help(sys.stderr)
169
+ sys.exit(1)
170
+
171
+ if args.settings:
172
+ _LOGGER.info("Loading settings file: %s", args.settings)
173
+ with open(args.settings, "r") as f:
174
+ vars_groups = [cli_vars, yaml.load(f, SafeLoader)]
175
+ else:
176
+ vars_groups = [cli_vars]
177
+
178
+ _LOGGER.debug(vars_groups)
179
+ if args.command == "write":
180
+ dcc.write_script(args.outfile, vars_groups)
181
+ elif args.command == "submit":
182
+ dcc.submit(args.outfile, vars_groups)
@@ -1,56 +1,31 @@
1
- """Project configuration, particularly for logging.
2
-
3
- Project-scope constants may reside here, but more importantly, some setup here
4
- will provide a logging infrastructure for all of the project's modules.
5
- Individual modules and classes may provide separate configuration on a more
6
- local level, but this will at least provide a foundation.
7
-
8
- """
9
-
10
- import logmuse
11
-
12
- logmuse.init_logger("looper")
13
-
14
1
  import argparse
15
- import logging
2
+ import logmuse
16
3
  import os
17
- from typing import *
18
- from .divvy import ComputingConfiguration, select_divvy_config
19
- from .divvy import DEFAULT_COMPUTE_RESOURCES_NAME
20
- from .divvy import NEW_COMPUTE_KEY as COMPUTE_KEY
4
+ import sys
5
+ import yaml
6
+
7
+ from eido import inspect_project
8
+ from pephubclient import PEPHubClient
9
+ from typing import Tuple, List
21
10
  from ubiquerg import VersionInHelpParser
22
11
 
23
- from ._version import __version__
24
- from .conductor import (
25
- SubmissionConductor,
26
- write_sample_yaml,
27
- write_sample_yaml_cwl,
28
- write_sample_yaml_prj,
29
- write_submission_yaml,
30
- write_custom_template,
31
- )
12
+ from . import __version__
32
13
  from .const import *
14
+ from .divvy import DEFAULT_COMPUTE_RESOURCES_NAME, select_divvy_config
15
+ from .exceptions import *
16
+ from .looper import *
33
17
  from .parser_types import *
34
- from .pipeline_interface import PipelineInterface
35
- from .project import Project
36
-
37
- # Not used here, but make this the main import interface between peppy and
38
- # looper, so that other modules within this package need not worry about
39
- # the locations of some of the peppy declarations. Effectively, concentrate
40
- # the connection between peppy and looper here, to the extent possible.
41
-
42
- __all__ = [
43
- "Project",
44
- "PipelineInterface",
45
- "SubmissionConductor",
46
- "ComputingConfiguration",
47
- "select_divvy_config",
48
- ]
49
-
50
-
51
- SAMPLE_SELECTION_ATTRIBUTE_OPTNAME = "sel-attr"
52
- SAMPLE_EXCLUSION_OPTNAME = "sel-excl"
53
- SAMPLE_INCLUSION_OPTNAME = "sel-incl"
18
+ from .project import Project, ProjectContext
19
+ from .utils import (
20
+ dotfile_path,
21
+ enrich_args_via_cfg,
22
+ is_registry_path,
23
+ read_looper_dotfile,
24
+ read_looper_config_file,
25
+ read_yaml_file,
26
+ initiate_looper_config,
27
+ init_generic_pipeline,
28
+ )
54
29
 
55
30
 
56
31
  class _StoreBoolActionType(argparse.Action):
@@ -77,21 +52,6 @@ class _StoreBoolActionType(argparse.Action):
77
52
  setattr(namespace, self.dest, self.const)
78
53
 
79
54
 
80
- MESSAGE_BY_SUBCOMMAND = {
81
- "run": "Run or submit sample jobs.",
82
- "rerun": "Resubmit sample jobs with failed flags.",
83
- "runp": "Run or submit project jobs.",
84
- "table": "Write summary stats table for project samples.",
85
- "report": "Create browsable HTML report of project results.",
86
- "destroy": "Remove output files of the project.",
87
- "check": "Check flag status of current runs.",
88
- "clean": "Run clean scripts of already processed jobs.",
89
- "inspect": "Print information about a project.",
90
- "init": "Initialize looper dotfile.",
91
- "init-piface": "Initialize generic pipeline interface.",
92
- }
93
-
94
-
95
55
  def build_parser():
96
56
  """
97
57
  Building argument parser.
@@ -158,6 +118,7 @@ def build_parser():
158
118
  inspect_subparser = add_subparser("inspect")
159
119
  init_subparser = add_subparser("init")
160
120
  init_piface = add_subparser("init-piface")
121
+ link_subparser = add_subparser("link")
161
122
 
162
123
  # Flag arguments
163
124
  ####################################################################
@@ -308,7 +269,7 @@ def build_parser():
308
269
  )
309
270
 
310
271
  init_subparser.add_argument(
311
- "config_file", help="Project configuration file (YAML)"
272
+ "pep_config", help="Project configuration file (PEP)"
312
273
  )
313
274
 
314
275
  init_subparser.add_argument(
@@ -366,6 +327,7 @@ def build_parser():
366
327
  clean_subparser,
367
328
  collate_subparser,
368
329
  inspect_subparser,
330
+ link_subparser,
369
331
  ]:
370
332
  subparser.add_argument(
371
333
  "config_file",
@@ -442,6 +404,7 @@ def build_parser():
442
404
  clean_subparser,
443
405
  collate_subparser,
444
406
  inspect_subparser,
407
+ link_subparser,
445
408
  ]:
446
409
  fetch_samples_group = subparser.add_argument_group(
447
410
  "sample selection arguments",
@@ -470,6 +433,7 @@ def build_parser():
470
433
  metavar="ATTR",
471
434
  help="Attribute for sample exclusion OR inclusion",
472
435
  )
436
+
473
437
  protocols = fetch_samples_group.add_mutually_exclusive_group()
474
438
  protocols.add_argument(
475
439
  f"--{SAMPLE_EXCLUSION_OPTNAME}",
@@ -483,6 +447,22 @@ def build_parser():
483
447
  metavar="I",
484
448
  help="Include only samples with these values",
485
449
  )
450
+ fetch_samples_group.add_argument(
451
+ f"--{SAMPLE_SELECTION_FLAG_OPTNAME}",
452
+ default=None,
453
+ nargs="*",
454
+ metavar="SELFLAG",
455
+ help="Include samples with this flag status, e.g. completed",
456
+ )
457
+
458
+ fetch_samples_group.add_argument(
459
+ f"--{SAMPLE_EXCLUSION_FLAG_OPTNAME}",
460
+ default=None,
461
+ nargs="*",
462
+ metavar="EXCFLAG",
463
+ help="Exclude samples with this flag status, e.g. completed",
464
+ )
465
+
486
466
  subparser.add_argument(
487
467
  "-a",
488
468
  "--amend",
@@ -490,7 +470,13 @@ def build_parser():
490
470
  metavar="A",
491
471
  help="List of amendments to activate",
492
472
  )
493
- for subparser in [report_subparser, table_subparser, check_subparser]:
473
+ for subparser in [
474
+ report_subparser,
475
+ table_subparser,
476
+ check_subparser,
477
+ destroy_subparser,
478
+ link_subparser,
479
+ ]:
494
480
  subparser.add_argument(
495
481
  "--project",
496
482
  help="Process project-level pipelines",
@@ -537,3 +523,254 @@ def validate_post_parse(args: argparse.Namespace) -> List[str]:
537
523
  f"Used multiple mutually exclusive options: {', '.join(used_exclusives)}"
538
524
  )
539
525
  return problems
526
+
527
+
528
+ def _proc_resources_spec(args):
529
+ """
530
+ Process CLI-sources compute setting specification. There are two sources
531
+ of compute settings in the CLI alone:
532
+ * YAML file (--settings argument)
533
+ * itemized compute settings (--compute argument)
534
+
535
+ The itemized compute specification is given priority
536
+
537
+ :param argparse.Namespace: arguments namespace
538
+ :return Mapping[str, str]: binding between resource setting name and value
539
+ :raise ValueError: if interpretation of the given specification as encoding
540
+ of key-value pairs fails
541
+ """
542
+ spec = getattr(args, "compute", None)
543
+ try:
544
+ settings_data = read_yaml_file(args.settings) or {}
545
+ except yaml.YAMLError:
546
+ _LOGGER.warning(
547
+ "Settings file ({}) does not follow YAML format,"
548
+ " disregarding".format(args.settings)
549
+ )
550
+ settings_data = {}
551
+ if not spec:
552
+ return settings_data
553
+ pairs = [(kv, kv.split("=")) for kv in spec]
554
+ bads = []
555
+ for orig, pair in pairs:
556
+ try:
557
+ k, v = pair
558
+ except ValueError:
559
+ bads.append(orig)
560
+ else:
561
+ settings_data[k] = v
562
+ if bads:
563
+ raise ValueError(
564
+ "Could not correctly parse itemized compute specification. "
565
+ "Correct format: " + EXAMPLE_COMPUTE_SPEC_FMT
566
+ )
567
+ return settings_data
568
+
569
+
570
+ def main(test_args=None):
571
+ """Primary workflow"""
572
+ global _LOGGER
573
+
574
+ parser, aux_parser = build_parser()
575
+ aux_parser.suppress_defaults()
576
+
577
+ if test_args:
578
+ args, remaining_args = parser.parse_known_args(args=test_args)
579
+ else:
580
+ args, remaining_args = parser.parse_known_args()
581
+
582
+ cli_use_errors = validate_post_parse(args)
583
+ if cli_use_errors:
584
+ parser.print_help(sys.stderr)
585
+ parser.error(
586
+ f"{len(cli_use_errors)} CLI use problem(s): {', '.join(cli_use_errors)}"
587
+ )
588
+ if args.command is None:
589
+ parser.print_help(sys.stderr)
590
+ sys.exit(1)
591
+
592
+ if args.command == "init":
593
+ return int(
594
+ not initiate_looper_config(
595
+ dotfile_path(),
596
+ args.pep_config,
597
+ args.output_dir,
598
+ args.sample_pipeline_interfaces,
599
+ args.project_pipeline_interfaces,
600
+ args.force,
601
+ )
602
+ )
603
+
604
+ if args.command == "init-piface":
605
+ sys.exit(int(not init_generic_pipeline()))
606
+
607
+ _LOGGER = logmuse.logger_via_cli(args, make_root=True)
608
+ _LOGGER.info("Looper version: {}\nCommand: {}".format(__version__, args.command))
609
+
610
+ if "config_file" in vars(args):
611
+ if args.config_file is None:
612
+ looper_cfg_path = os.path.relpath(dotfile_path(), start=os.curdir)
613
+ try:
614
+ if args.looper_config:
615
+ looper_config_dict = read_looper_config_file(args.looper_config)
616
+ else:
617
+ looper_config_dict = read_looper_dotfile()
618
+ _LOGGER.info(f"Using looper config ({looper_cfg_path}).")
619
+
620
+ for looper_config_key, looper_config_item in looper_config_dict.items():
621
+ setattr(args, looper_config_key, looper_config_item)
622
+
623
+ except OSError:
624
+ parser.print_help(sys.stderr)
625
+ _LOGGER.warning(
626
+ f"Looper config file does not exist. Use looper init to create one at {looper_cfg_path}."
627
+ )
628
+ sys.exit(1)
629
+ else:
630
+ _LOGGER.warning(
631
+ "This PEP configures looper through the project config. This approach is deprecated and will "
632
+ "be removed in future versions. Please use a looper config file. For more information see "
633
+ "looper.databio.org/en/latest/looper-config"
634
+ )
635
+
636
+ args = enrich_args_via_cfg(args, aux_parser, test_args)
637
+
638
+ # If project pipeline interface defined in the cli, change name to: "pipeline_interface"
639
+ if vars(args)[PROJECT_PL_ARG]:
640
+ args.pipeline_interfaces = vars(args)[PROJECT_PL_ARG]
641
+
642
+ if len(remaining_args) > 0:
643
+ _LOGGER.warning(
644
+ "Unrecognized arguments: {}".format(
645
+ " ".join([str(x) for x in remaining_args])
646
+ )
647
+ )
648
+
649
+ divcfg = (
650
+ select_divvy_config(filepath=args.divvy) if hasattr(args, "divvy") else None
651
+ )
652
+
653
+ # Ignore flags if user is selecting or excluding on flags:
654
+ if args.sel_flag or args.exc_flag:
655
+ args.ignore_flags = True
656
+
657
+ # Initialize project
658
+ if is_registry_path(args.config_file):
659
+ if vars(args)[SAMPLE_PL_ARG]:
660
+ p = Project(
661
+ amendments=args.amend,
662
+ divcfg_path=divcfg,
663
+ runp=args.command == "runp",
664
+ project_dict=PEPHubClient()._load_raw_pep(
665
+ registry_path=args.config_file
666
+ ),
667
+ **{
668
+ attr: getattr(args, attr) for attr in CLI_PROJ_ATTRS if attr in args
669
+ },
670
+ )
671
+ else:
672
+ raise MisconfigurationException(
673
+ f"`sample_pipeline_interface` is missing. Provide it in the parameters."
674
+ )
675
+ else:
676
+ try:
677
+ p = Project(
678
+ cfg=args.config_file,
679
+ amendments=args.amend,
680
+ divcfg_path=divcfg,
681
+ runp=args.command == "runp",
682
+ **{
683
+ attr: getattr(args, attr) for attr in CLI_PROJ_ATTRS if attr in args
684
+ },
685
+ )
686
+ except yaml.parser.ParserError as e:
687
+ _LOGGER.error(f"Project config parse failed -- {e}")
688
+ sys.exit(1)
689
+
690
+ selected_compute_pkg = p.selected_compute_package or DEFAULT_COMPUTE_RESOURCES_NAME
691
+ if p.dcc is not None and not p.dcc.activate_package(selected_compute_pkg):
692
+ _LOGGER.info(
693
+ "Failed to activate '{}' computing package. "
694
+ "Using the default one".format(selected_compute_pkg)
695
+ )
696
+
697
+ with ProjectContext(
698
+ prj=p,
699
+ selector_attribute=args.sel_attr,
700
+ selector_include=args.sel_incl,
701
+ selector_exclude=args.sel_excl,
702
+ selector_flag=args.sel_flag,
703
+ exclusion_flag=args.exc_flag,
704
+ ) as prj:
705
+ if args.command in ["run", "rerun"]:
706
+ run = Runner(prj)
707
+ try:
708
+ compute_kwargs = _proc_resources_spec(args)
709
+ return run(args, rerun=(args.command == "rerun"), **compute_kwargs)
710
+ except SampleFailedException:
711
+ sys.exit(1)
712
+ except IOError:
713
+ _LOGGER.error(
714
+ "{} pipeline_interfaces: '{}'".format(
715
+ prj.__class__.__name__, prj.pipeline_interface_sources
716
+ )
717
+ )
718
+ raise
719
+
720
+ if args.command == "runp":
721
+ compute_kwargs = _proc_resources_spec(args)
722
+ collate = Collator(prj)
723
+ collate(args, **compute_kwargs)
724
+ return collate.debug
725
+
726
+ if args.command == "destroy":
727
+ return Destroyer(prj)(args)
728
+
729
+ # pipestat support introduces breaking changes and pipelines run
730
+ # with no pipestat reporting would not be compatible with
731
+ # commands: table, report and check. Therefore we plan maintain
732
+ # the old implementations for a couple of releases.
733
+ # if hasattr(args, "project"):
734
+ # use_pipestat = (
735
+ # prj.pipestat_configured_project
736
+ # if args.project
737
+ # else prj.pipestat_configured
738
+ # )
739
+ use_pipestat = (
740
+ prj.pipestat_configured_project if args.project else prj.pipestat_configured
741
+ )
742
+ if args.command == "table":
743
+ if use_pipestat:
744
+ Tabulator(prj)(args)
745
+ else:
746
+ raise PipestatConfigurationException("table")
747
+
748
+ if args.command == "report":
749
+ if use_pipestat:
750
+ Reporter(prj)(args)
751
+ else:
752
+ raise PipestatConfigurationException("report")
753
+
754
+ if args.command == "link":
755
+ if use_pipestat:
756
+ Linker(prj)(args)
757
+ else:
758
+ raise PipestatConfigurationException("link")
759
+
760
+ if args.command == "check":
761
+ if use_pipestat:
762
+ return Checker(prj)(args)
763
+ else:
764
+ raise PipestatConfigurationException("check")
765
+
766
+ if args.command == "clean":
767
+ return Cleaner(prj)(args)
768
+
769
+ if args.command == "inspect":
770
+ inspect_project(p, args.sample_names, args.attr_limit)
771
+ from warnings import warn
772
+
773
+ warn(
774
+ "The inspect feature has moved to eido and will be removed in the future release of looper. "
775
+ "Use `eido inspect` from now on.",
776
+ )