looper 2.0.0a1__py3-none-any.whl → 2.0.0a3__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.
looper/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2.0.0a1"
1
+ __version__ = "2.0.0a3"
2
2
  # You must change the version in parser = pydantic_argparse.ArgumentParser in cli_pydantic.py!!!
looper/cli_divvy.py CHANGED
@@ -53,10 +53,10 @@ def build_argparser():
53
53
 
54
54
  for sp in [sps["list"], sps["write"], sps["submit"], sps["inspect"]]:
55
55
  sp.add_argument(
56
- "config", nargs="?", default=None, help="Divvy configuration file."
56
+ "--config", nargs="?", default=None, help="Divvy configuration file."
57
57
  )
58
58
 
59
- sps["init"].add_argument("config", default=None, help="Divvy configuration file.")
59
+ sps["init"].add_argument("--config", default=None, help="Divvy configuration file.")
60
60
 
61
61
  for sp in [sps["inspect"]]:
62
62
  sp.add_argument(
@@ -124,9 +124,11 @@ def main():
124
124
  sys.exit(0)
125
125
 
126
126
  _LOGGER.debug("Divvy config: {}".format(args.config))
127
+
127
128
  divcfg = select_divvy_config(args.config)
129
+
128
130
  _LOGGER.info("Using divvy config: {}".format(divcfg))
129
- dcc = ComputingConfiguration(filepath=divcfg)
131
+ dcc = ComputingConfiguration.from_yaml_file(filepath=divcfg)
130
132
 
131
133
  if args.command == "list":
132
134
  # Output header via logger and content via print so the user can
@@ -142,11 +144,13 @@ def main():
142
144
  for pkg_name, pkg in dcc.compute_packages.items():
143
145
  if pkg_name == args.package:
144
146
  found = True
145
- with open(pkg.submission_template, "r") as f:
147
+ with open(pkg["submission_template"], "r") as f:
146
148
  print(f.read())
147
- _LOGGER.info("Submission command is: " + pkg.submission_command + "\n")
149
+ _LOGGER.info(
150
+ "Submission command is: " + pkg["submission_command"] + "\n"
151
+ )
148
152
  if pkg_name == "docker":
149
- print("Docker args are: " + pkg.docker_args)
153
+ print("Docker args are: " + pkg["docker_args"])
150
154
 
151
155
  if not found:
152
156
  _LOGGER.info("Package not found. Use 'divvy list' to see list of packages.")
looper/cli_pydantic.py CHANGED
@@ -26,8 +26,6 @@ from eido import inspect_project
26
26
  from pephubclient import PEPHubClient
27
27
  from pydantic_argparse.argparse.parser import ArgumentParser
28
28
 
29
- from divvy import select_divvy_config
30
-
31
29
  from . import __version__
32
30
 
33
31
  from .command_models.arguments import ArgumentEnum
@@ -128,15 +126,8 @@ def run_looper(args: TopLevelParser, parser: ArgumentParser, test_args=None):
128
126
  console = Console()
129
127
  console.clear()
130
128
  console.rule(f"\n[magenta]Looper initialization[/magenta]")
131
- console.print(
132
- "[bold]Would you like to follow a guided tutorial?[/bold] [green]Y[/green] / [red]n[/red]..."
133
- )
134
-
135
- selection = None
136
- while selection not in ["y", "n"]:
137
- selection = console.input("\nSelection: ").lower().strip()
138
-
139
- if selection == "n":
129
+ selection = subcommand_args.generic
130
+ if selection is True:
140
131
  console.clear()
141
132
  return int(
142
133
  not initiate_looper_config(
@@ -172,11 +163,14 @@ def run_looper(args: TopLevelParser, parser: ArgumentParser, test_args=None):
172
163
  else:
173
164
  setattr(subcommand_args, looper_config_key, looper_config_item)
174
165
 
175
- except OSError:
176
- parser.print_help(sys.stderr)
177
- _LOGGER.warning(
178
- f"Looper config file does not exist. Use looper init to create one at {looper_cfg_path}."
179
- )
166
+ except OSError as e:
167
+ if subcommand_args.config:
168
+ _LOGGER.warning(
169
+ f"\nLooper config file does not exist at given path {subcommand_args.config}. Use looper init to create one at {looper_cfg_path}."
170
+ )
171
+ else:
172
+ _LOGGER.warning(e)
173
+
180
174
  sys.exit(1)
181
175
 
182
176
  subcommand_args = enrich_args_via_cfg(
@@ -225,7 +219,7 @@ def run_looper(args: TopLevelParser, parser: ArgumentParser, test_args=None):
225
219
  amendments=subcommand_args.amend,
226
220
  divcfg_path=divcfg,
227
221
  runp=subcommand_name == "runp",
228
- project_dict=PEPHubClient()._load_raw_pep(
222
+ project_dict=PEPHubClient().load_raw_pep(
229
223
  registry_path=subcommand_args.pep_config
230
224
  ),
231
225
  **{
@@ -340,13 +334,13 @@ def run_looper(args: TopLevelParser, parser: ArgumentParser, test_args=None):
340
334
  _LOGGER.warning("No looper configuration was supplied.")
341
335
 
342
336
 
343
- def main(test_args=None) -> None:
337
+ def main(test_args=None) -> dict:
344
338
  parser = pydantic_argparse.ArgumentParser(
345
339
  model=TopLevelParser,
346
340
  prog="looper",
347
341
  description="Looper: A job submitter for Portable Encapsulated Projects",
348
342
  add_help=True,
349
- version="2.0.0a1",
343
+ version="2.0.0a3",
350
344
  )
351
345
 
352
346
  parser = add_short_arguments(parser, ArgumentEnum)
@@ -359,6 +353,10 @@ def main(test_args=None) -> None:
359
353
  return run_looper(args, parser, test_args=test_args)
360
354
 
361
355
 
356
+ def main_cli() -> None:
357
+ main()
358
+
359
+
362
360
  def _proc_resources_spec(args):
363
361
  """
364
362
  Process CLI-sources compute setting specification. There are two sources
@@ -385,20 +383,29 @@ def _proc_resources_spec(args):
385
383
  settings_data = {}
386
384
  if not spec:
387
385
  return settings_data
388
- pairs = [(kv, kv.split("=")) for kv in spec]
389
- bads = []
390
- for orig, pair in pairs:
391
- try:
392
- k, v = pair
393
- except ValueError:
394
- bads.append(orig)
395
- else:
396
- settings_data[k] = v
397
- if bads:
398
- raise ValueError(
399
- "Could not correctly parse itemized compute specification. "
400
- "Correct format: " + EXAMPLE_COMPUTE_SPEC_FMT
401
- )
386
+ if isinstance(
387
+ spec, str
388
+ ): # compute: "partition=standard time='01-00:00:00' cores='32' mem='32000'"
389
+ spec = spec.split(sep=" ")
390
+ if isinstance(spec, list):
391
+ pairs = [(kv, kv.split("=")) for kv in spec]
392
+ bads = []
393
+ for orig, pair in pairs:
394
+ try:
395
+ k, v = pair
396
+ except ValueError:
397
+ bads.append(orig)
398
+ else:
399
+ settings_data[k] = v
400
+ if bads:
401
+ raise ValueError(
402
+ "Could not correctly parse itemized compute specification. "
403
+ "Correct format: " + EXAMPLE_COMPUTE_SPEC_FMT
404
+ )
405
+ elif isinstance(spec, dict):
406
+ for key, value in spec.items():
407
+ settings_data[key] = value
408
+
402
409
  return settings_data
403
410
 
404
411
 
@@ -184,6 +184,20 @@ class ArgumentEnum(enum.Enum):
184
184
  default=(str, None),
185
185
  description="Output directory",
186
186
  )
187
+ REPORT_OUTPUT_DIR = Argument(
188
+ name="report_dir",
189
+ alias="-r",
190
+ default=(str, None),
191
+ description="Set location for looper report and looper table outputs",
192
+ )
193
+
194
+ GENERIC = Argument(
195
+ name="generic",
196
+ alias="-g",
197
+ default=(bool, False),
198
+ description="Use generic looper config?",
199
+ )
200
+
187
201
  SAMPLE_PIPELINE_INTERFACES = Argument(
188
202
  name="sample_pipeline_interfaces",
189
203
  alias="-S",
@@ -228,6 +242,7 @@ class ArgumentEnum(enum.Enum):
228
242
  )
229
243
  PACKAGE = Argument(
230
244
  name="package",
245
+ alias="-p",
231
246
  default=(str, None),
232
247
  description="Name of computing resource package to use",
233
248
  )
@@ -124,7 +124,9 @@ RunProjectParser = Command(
124
124
  TableParser = Command(
125
125
  "table",
126
126
  MESSAGE_BY_SUBCOMMAND["table"],
127
- [],
127
+ [
128
+ ArgumentEnum.REPORT_OUTPUT_DIR.value,
129
+ ],
128
130
  )
129
131
 
130
132
 
@@ -134,6 +136,7 @@ ReportParser = Command(
134
136
  MESSAGE_BY_SUBCOMMAND["report"],
135
137
  [
136
138
  ArgumentEnum.PORTABLE.value,
139
+ ArgumentEnum.REPORT_OUTPUT_DIR.value,
137
140
  ],
138
141
  )
139
142
 
@@ -187,6 +190,7 @@ InitParser = Command(
187
190
  ArgumentEnum.PEP_CONFIG.value,
188
191
  ArgumentEnum.SAMPLE_PIPELINE_INTERFACES.value,
189
192
  ArgumentEnum.PROJECT_PIPELINE_INTERFACES.value,
193
+ ArgumentEnum.GENERIC.value,
190
194
  ],
191
195
  )
192
196
 
looper/conductor.py CHANGED
@@ -661,6 +661,7 @@ class SubmissionConductor(object):
661
661
  "record_identifier": psm.record_identifier,
662
662
  "config_file": psm.config_path,
663
663
  "output_schema": psm.cfg["_schema_path"],
664
+ "pephub_path": psm.cfg["pephub_path"],
664
665
  }
665
666
  filtered_namespace = {k: v for k, v in full_namespace.items() if v}
666
667
  return YAMLConfigManager(filtered_namespace)
looper/divvy.py CHANGED
@@ -111,9 +111,12 @@ class ComputingConfiguration(YAMLConfigManager):
111
111
 
112
112
  :return str: path to folder with default submission templates
113
113
  """
114
- return os.path.join(
115
- os.path.dirname(__file__), "default_config", "divvy_templates"
116
- )
114
+ if self.filepath:
115
+ return os.path.join(os.path.dirname(self.filepath), "divvy_templates")
116
+ else:
117
+ return os.path.join(
118
+ os.path.dirname(__file__), "default_config", "divvy_templates"
119
+ )
117
120
 
118
121
  def activate_package(self, package_name):
119
122
  """
@@ -155,11 +158,18 @@ class ComputingConfiguration(YAMLConfigManager):
155
158
  # but now, it makes more sense to do it here so we can piggyback on
156
159
  # the default update() method and not even have to do that.
157
160
  if not os.path.isabs(self.compute["submission_template"]):
161
+
158
162
  try:
159
- self.compute["submission_template"] = os.path.join(
160
- os.path.dirname(self.default_config_file),
161
- self.compute["submission_template"],
162
- )
163
+ if self.filepath:
164
+ self.compute["submission_template"] = os.path.join(
165
+ os.path.dirname(self.filepath),
166
+ self.compute["submission_template"],
167
+ )
168
+ else:
169
+ self.compute["submission_template"] = os.path.join(
170
+ os.path.dirname(self.default_config_file),
171
+ self.compute["submission_template"],
172
+ )
163
173
  except AttributeError as e:
164
174
  # Environment and environment compute should at least have been
165
175
  # set as null-valued attributes, so execution here is an error.
@@ -200,6 +210,11 @@ class ComputingConfiguration(YAMLConfigManager):
200
210
  """
201
211
  return self.compute
202
212
 
213
+ @property
214
+ def compute_packages(self):
215
+
216
+ return self["compute_packages"]
217
+
203
218
  def list_compute_packages(self):
204
219
  """
205
220
  Returns a list of available compute packages.
@@ -396,11 +411,13 @@ def divvy_init(config_path, template_config_path):
396
411
  _LOGGER.error("You must specify a template config file path.")
397
412
  return
398
413
 
414
+ if not os.path.isabs(config_path):
415
+ config_path = os.path.abspath(config_path)
416
+
399
417
  if config_path and not os.path.exists(config_path):
400
- # dcc.write(config_path)
401
418
  # Init should *also* write the templates.
402
419
  dest_folder = os.path.dirname(config_path)
403
- copytree(os.path.dirname(template_config_path), dest_folder)
420
+ copytree(os.path.dirname(template_config_path), dest_folder, dirs_exist_ok=True)
404
421
  template_subfolder = os.path.join(dest_folder, "divvy_templates")
405
422
  _LOGGER.info("Wrote divvy templates to folder: {}".format(template_subfolder))
406
423
  new_template = os.path.join(
looper/exceptions.py CHANGED
@@ -15,6 +15,7 @@ _all__ = [
15
15
  "PipelineInterfaceConfigError",
16
16
  "PipelineInterfaceRequirementsError",
17
17
  "MisconfigurationException",
18
+ "LooperReportError",
18
19
  ]
19
20
 
20
21
 
@@ -31,7 +32,7 @@ class SampleFailedException(LooperError):
31
32
 
32
33
 
33
34
  class MisconfigurationException(LooperError):
34
- """Duplication of pipeline identifier precludes unique pipeline ref."""
35
+ """Looper not properly configured"""
35
36
 
36
37
  def __init__(self, key):
37
38
  super(MisconfigurationException, self).__init__(key)
@@ -109,3 +110,10 @@ class PipelineInterfaceRequirementsError(LooperError):
109
110
  )
110
111
  )
111
112
  self.error_specs = typename_by_requirement
113
+
114
+
115
+ class LooperReportError(LooperError):
116
+ """Looper reporting errors"""
117
+
118
+ def __init__(self, reason):
119
+ super(LooperReportError, self).__init__(reason)
looper/looper.py CHANGED
@@ -46,6 +46,7 @@ from .utils import (
46
46
  sample_folder,
47
47
  )
48
48
  from pipestat.reports import get_file_for_table
49
+ from pipestat.exceptions import PipestatSummarizeError
49
50
 
50
51
  _PKGNAME = "looper"
51
52
  _LOGGER = logging.getLogger(_PKGNAME)
@@ -94,11 +95,19 @@ class Checker(Executor):
94
95
 
95
96
  for piface in self.prj.project_pipeline_interfaces:
96
97
  if piface.psm.pipeline_type == PipelineLevel.PROJECT.value:
97
- psms[piface.psm.pipeline_name] = piface.psm
98
- s = piface.psm.get_status() or "unknown"
98
+ if piface.psm.pipeline_name not in psms:
99
+ psms[piface.psm.pipeline_name] = piface.psm
100
+ for pl_name, psm in psms.items():
101
+ all_project_level_records = psm.select_records()
102
+ for record in all_project_level_records["records"]:
103
+ s = piface.psm.get_status(
104
+ record_identifier=record["record_identifier"]
105
+ )
99
106
  status.setdefault(piface.psm.pipeline_name, {})
100
- status[piface.psm.pipeline_name][self.prj.name] = s
101
- _LOGGER.debug(f"{self.prj.name} ({piface.psm.pipeline_name}): {s}")
107
+ status[piface.psm.pipeline_name][record["record_identifier"]] = s
108
+ _LOGGER.debug(
109
+ f"{self.prj.name} ({record['record_identifier']}): {s}"
110
+ )
102
111
 
103
112
  else:
104
113
  for sample in self.prj.samples:
@@ -559,15 +568,26 @@ class Reporter(Executor):
559
568
 
560
569
  portable = args.portable
561
570
 
571
+ report_dir = getattr(args, "report_dir", None)
572
+
562
573
  psms = {}
563
574
 
564
575
  if project_level:
565
576
 
566
577
  for piface in self.prj.project_pipeline_interfaces:
567
578
  if piface.psm.pipeline_type == PipelineLevel.PROJECT.value:
568
- psms[piface.psm.pipeline_name] = piface.psm
569
- report_directory = piface.psm.summarize(
570
- looper_samples=self.prj.samples, portable=portable
579
+ if piface.psm.pipeline_name not in psms:
580
+ psms[piface.psm.pipeline_name] = piface.psm
581
+ for pl_name, psm in psms.items():
582
+ try:
583
+ report_directory = psm.summarize(
584
+ looper_samples=self.prj.samples,
585
+ portable=portable,
586
+ output_dir=report_dir,
587
+ )
588
+ except PipestatSummarizeError as e:
589
+ raise LooperReportError(
590
+ f"Looper report error due to the following exception: {e}"
571
591
  )
572
592
  print(f"Report directory: {report_directory}")
573
593
  self.debug["report_directory"] = report_directory
@@ -575,12 +595,21 @@ class Reporter(Executor):
575
595
  else:
576
596
  for piface in self.prj.pipeline_interfaces:
577
597
  if piface.psm.pipeline_type == PipelineLevel.SAMPLE.value:
578
- psms[piface.psm.pipeline_name] = piface.psm
579
- report_directory = piface.psm.summarize(
580
- looper_samples=self.prj.samples, portable=portable
598
+ if piface.psm.pipeline_name not in psms:
599
+ psms[piface.psm.pipeline_name] = piface.psm
600
+ for pl_name, psm in psms.items():
601
+ try:
602
+ report_directory = psm.summarize(
603
+ looper_samples=self.prj.samples,
604
+ portable=portable,
605
+ output_dir=report_dir,
606
+ )
607
+ except PipestatSummarizeError as e:
608
+ raise LooperReportError(
609
+ f"Looper report error due to the following exception: {e}"
581
610
  )
582
- print(f"Report directory: {report_directory}")
583
- self.debug["report_directory"] = report_directory
611
+ print(f"Report directory: {report_directory}")
612
+ self.debug["report_directory"] = report_directory
584
613
  return self.debug
585
614
 
586
615
 
@@ -618,18 +647,23 @@ class Tabulator(Executor):
618
647
  def __call__(self, args):
619
648
  # p = self.prj
620
649
  project_level = getattr(args, "project", None)
650
+ report_dir = getattr(args, "report_dir", None)
621
651
  results = []
622
652
  psms = {}
623
653
  if project_level:
624
654
  for piface in self.prj.project_pipeline_interfaces:
625
655
  if piface.psm.pipeline_type == PipelineLevel.PROJECT.value:
626
- psms[piface.psm.pipeline_name] = piface.psm
627
- results = piface.psm.table()
656
+ if piface.psm.pipeline_name not in psms:
657
+ psms[piface.psm.pipeline_name] = piface.psm
658
+ for pl_name, psm in psms.items():
659
+ results = psm.table(output_dir=report_dir)
628
660
  else:
629
661
  for piface in self.prj.pipeline_interfaces:
630
662
  if piface.psm.pipeline_type == PipelineLevel.SAMPLE.value:
631
- psms[piface.psm.pipeline_name] = piface.psm
632
- results = piface.psm.table()
663
+ if piface.psm.pipeline_name not in psms:
664
+ psms[piface.psm.pipeline_name] = piface.psm
665
+ for pl_name, psm in psms.items():
666
+ results = psm.table(output_dir=report_dir)
633
667
  # Results contains paths to stats and object summaries.
634
668
  return results
635
669
 
looper/utils.py CHANGED
@@ -17,6 +17,7 @@ from ubiquerg import convert_value, expandpath, parse_registry_path, deep_update
17
17
  from pephubclient.constants import RegistryPath
18
18
  from pydantic import ValidationError
19
19
  from yacman import load_yaml
20
+ from yaml.parser import ParserError
20
21
 
21
22
  from .const import *
22
23
  from .command_models.commands import SUPPORTED_COMMANDS
@@ -263,31 +264,29 @@ def enrich_args_via_cfg(
263
264
  cli_modifiers=None,
264
265
  ):
265
266
  """
266
- Read in a looper dotfile and set arguments.
267
+ Read in a looper dotfile, pep config and set arguments.
267
268
 
268
- Priority order: CLI > dotfile/config > parser default
269
+ Priority order: CLI > dotfile/config > pep_config > parser default
269
270
 
270
271
  :param subcommand name: the name of the command used
271
272
  :param argparse.Namespace parser_args: parsed args by the original parser
272
- :param argparse.Namespace aux_parser: parsed args by the a parser
273
+ :param argparse.Namespace aux_parser: parsed args by the argument parser
273
274
  with defaults suppressed
275
+ :param dict test_args: dict of args used for pytesting
276
+ :param dict cli_modifiers: dict of args existing if user supplied cli args in looper config file
274
277
  :return argparse.Namespace: selected argument values
275
278
  """
279
+
280
+ # Did the user provide arguments in the PEP config?
276
281
  cfg_args_all = (
277
282
  _get_subcommand_args(subcommand_name, parser_args)
278
283
  if os.path.exists(parser_args.pep_config)
279
284
  else dict()
280
285
  )
281
-
282
- # If user provided project-level modifiers in the looper config, they are prioritized
283
- if cfg_args_all:
284
- for key, value in cfg_args_all.items():
285
- if getattr(parser_args, key, None):
286
- new_value = getattr(parser_args, key)
287
- cfg_args_all[key] = new_value
288
- else:
286
+ if not cfg_args_all:
289
287
  cfg_args_all = {}
290
288
 
289
+ # Did the user provide arguments/modifiers in the looper config file?
291
290
  looper_config_cli_modifiers = None
292
291
  if cli_modifiers:
293
292
  if str(subcommand_name) in cli_modifiers:
@@ -312,6 +311,13 @@ def enrich_args_via_cfg(
312
311
  else:
313
312
  cli_args, _ = aux_parser.parse_known_args()
314
313
 
314
+ # If any CLI args were provided, make sure they take priority
315
+ if cli_args:
316
+ r = getattr(cli_args, subcommand_name)
317
+ for k, v in cfg_args_all.items():
318
+ if k in r:
319
+ cfg_args_all[k] = getattr(r, k)
320
+
315
321
  def set_single_arg(argname, default_source_namespace, result_namespace):
316
322
  if argname not in POSITIONAL or not hasattr(result, argname):
317
323
  if argname in cli_args:
@@ -324,6 +330,8 @@ def enrich_args_via_cfg(
324
330
  elif cfg_args_all is not None and argname in cfg_args_all:
325
331
  if isinstance(cfg_args_all[argname], list):
326
332
  r = [convert_value(i) for i in cfg_args_all[argname]]
333
+ elif isinstance(cfg_args_all[argname], dict):
334
+ r = cfg_args_all[argname]
327
335
  else:
328
336
  r = convert_value(cfg_args_all[argname])
329
337
  else:
@@ -432,9 +440,8 @@ def init_generic_pipeline(pipelinepath: Optional[str] = None):
432
440
  generic_pipeline_dict = {
433
441
  "pipeline_name": "default_pipeline_name",
434
442
  "output_schema": "output_schema.yaml",
435
- "var_templates": {"pipeline": "{looper.piface_dir}/count_lines.sh"},
436
443
  "sample_interface": {
437
- "command_template": "{pipeline.var_templates.pipeline} {sample.file} "
444
+ "command_template": "{looper.piface_dir}/count_lines.sh {sample.file} "
438
445
  "--output-parent {looper.sample_output_folder}"
439
446
  },
440
447
  }
@@ -627,8 +634,6 @@ def looper_config_tutorial():
627
634
  console.print("Use [yellow]`looper run`[/yellow] afterwards to run the pipeline.")
628
635
  console.print("Press [yellow]^C[/yellow] at any time to quit.\n")
629
636
 
630
- console.input("> ... ")
631
-
632
637
  DEFAULTS = { # What you get if you just press enter
633
638
  "pep_config": "databio/example",
634
639
  "output_dir": "results",
@@ -636,73 +641,58 @@ def looper_config_tutorial():
636
641
  "project_name": os.path.basename(os.getcwd()),
637
642
  }
638
643
 
639
- creating = True
644
+ cfg["project_name"] = (
645
+ console.input(f"Project name: [yellow]({DEFAULTS['project_name']})[/yellow] >")
646
+ or DEFAULTS["project_name"]
647
+ )
640
648
 
641
- while creating:
642
- cfg["project_name"] = (
643
- console.input(
644
- f"Project name: [yellow]({DEFAULTS['project_name']})[/yellow] >"
645
- )
646
- or DEFAULTS["project_name"]
649
+ cfg["pep_config"] = (
650
+ console.input(
651
+ f"Registry path or file path to PEP: [yellow]({DEFAULTS['pep_config']})[/yellow] >"
647
652
  )
653
+ or DEFAULTS["pep_config"]
654
+ )
648
655
 
649
- cfg["pep_config"] = (
650
- console.input(
651
- f"Registry path or file path to PEP: [yellow]({DEFAULTS['pep_config']})[/yellow] >"
652
- )
653
- or DEFAULTS["pep_config"]
656
+ if not os.path.exists(cfg["pep_config"]) and not is_pephub_registry_path(
657
+ cfg["pep_config"]
658
+ ):
659
+ console.print(
660
+ f"Warning: PEP file does not exist at [yellow]'{cfg['pep_config']}[/yellow]'"
654
661
  )
655
662
 
656
- if not os.path.exists(cfg["pep_config"]):
657
- console.print(
658
- f"Warning: PEP file does not exist at [yellow]'{cfg['pep_config']}[/yellow]'"
659
- )
663
+ cfg["output_dir"] = (
664
+ console.input(
665
+ f"Path to output directory: [yellow]({DEFAULTS['output_dir']})[/yellow] >"
666
+ )
667
+ or DEFAULTS["output_dir"]
668
+ )
660
669
 
661
- cfg["output_dir"] = (
670
+ add_more_pifaces = True
671
+ piface_paths = []
672
+ while add_more_pifaces:
673
+ piface_path = (
662
674
  console.input(
663
- f"Path to output directory: [yellow]({DEFAULTS['output_dir']})[/yellow] >"
675
+ "Add each path to a pipeline interface: [yellow](pipeline_interface.yaml)[/yellow] >"
664
676
  )
665
- or DEFAULTS["output_dir"]
677
+ or None
666
678
  )
679
+ if piface_path is None:
680
+ if piface_paths == []:
681
+ piface_paths.append(DEFAULTS["piface_path"])
682
+ add_more_pifaces = False
683
+ else:
684
+ piface_paths.append(piface_path)
667
685
 
668
- add_more_pifaces = True
669
- piface_paths = []
670
- while add_more_pifaces:
671
- piface_path = (
672
- console.input(
673
- "Add each path to a pipeline interface: [yellow](pipeline_interface.yaml)[/yellow] >"
674
- )
675
- or None
676
- )
677
- if piface_path is None:
678
- if piface_paths == []:
679
- piface_paths.append(DEFAULTS["piface_path"])
680
- add_more_pifaces = False
681
- else:
682
- piface_paths.append(piface_path)
683
-
684
- console.print("\n")
685
-
686
- console.print(
687
- f"""\
688
- [yellow]pep_config:[/yellow] {cfg['pep_config']}
689
- [yellow]output_dir:[/yellow] {cfg['output_dir']}
690
- [yellow]pipeline_interfaces:[/yellow]
691
- - {piface_paths}
692
- """
693
- )
686
+ console.print("\n")
694
687
 
695
- console.print(
696
- "[bold]Does this look good?[/bold] [bold green]Y[/bold green]/[red]n[/red]..."
697
- )
698
- selection = None
699
- while selection not in ["y", "n"]:
700
- selection = console.input("\nSelection: ").lower().strip()
701
- if selection == "n":
702
- console.print("Starting over...")
703
- pass
704
- if selection == "y":
705
- creating = False
688
+ console.print(
689
+ f"""\
690
+ [yellow]pep_config:[/yellow] {cfg['pep_config']}
691
+ [yellow]output_dir:[/yellow] {cfg['output_dir']}
692
+ [yellow]pipeline_interfaces:[/yellow]
693
+ - {piface_paths}
694
+ """
695
+ )
706
696
 
707
697
  for piface_path in piface_paths:
708
698
  if not os.path.exists(piface_path):
@@ -727,7 +717,7 @@ def looper_config_tutorial():
727
717
  looper_config_dict = {}
728
718
  looper_config_dict["pep_config"] = cfg["pep_config"]
729
719
  looper_config_dict["output_dir"] = cfg["output_dir"]
730
- looper_config_dict["pipeline_interfaces"] = [piface_paths]
720
+ looper_config_dict["pipeline_interfaces"] = piface_paths
731
721
 
732
722
  with open(looper_cfg_path, "w") as fp:
733
723
  yaml.dump(looper_config_dict, fp)
@@ -747,7 +737,15 @@ def determine_pipeline_type(piface_path: str, looper_config_path: str):
747
737
 
748
738
  if piface_path is None:
749
739
  return None, None
750
- piface_path = expandpath(piface_path)
740
+ try:
741
+ piface_path = expandpath(piface_path)
742
+ except TypeError as e:
743
+ _LOGGER.warning(
744
+ f"Pipeline interface not found at given path: {piface_path}. Type Error: "
745
+ + str(e)
746
+ )
747
+ return None, None
748
+
751
749
  if not os.path.isabs(piface_path):
752
750
  piface_path = os.path.realpath(
753
751
  os.path.join(os.path.dirname(looper_config_path), piface_path)
@@ -755,6 +753,7 @@ def determine_pipeline_type(piface_path: str, looper_config_path: str):
755
753
  try:
756
754
  piface_dict = load_yaml(piface_path)
757
755
  except FileNotFoundError:
756
+ _LOGGER.warning(f"Pipeline interface not found at given path: {piface_path}")
758
757
  return None, None
759
758
 
760
759
  pipeline_types = []
@@ -783,12 +782,18 @@ def read_looper_config_file(looper_config_path: str) -> dict:
783
782
  :raise MisconfigurationException: incorrect configuration.
784
783
  """
785
784
  return_dict = {}
786
- with open(looper_config_path, "r") as dotfile:
787
- dp_data = yaml.safe_load(dotfile)
785
+
786
+ try:
787
+ with open(looper_config_path, "r") as dotfile:
788
+ dp_data = yaml.safe_load(dotfile)
789
+ except ParserError as e:
790
+ _LOGGER.warning(
791
+ "Could not load looper config file due to the following exception"
792
+ )
793
+ raise ParserError(context=str(e))
788
794
 
789
795
  if PEP_CONFIG_KEY in dp_data:
790
796
  return_dict[PEP_CONFIG_KEY] = dp_data[PEP_CONFIG_KEY]
791
-
792
797
  else:
793
798
  raise MisconfigurationException(
794
799
  f"Looper dotfile ({looper_config_path}) is missing '{PEP_CONFIG_KEY}' key"
@@ -846,6 +851,7 @@ def read_looper_config_file(looper_config_path: str) -> dict:
846
851
  for k, v in return_dict.items():
847
852
  if k == SAMPLE_PL_ARG or k == PROJECT_PL_ARG:
848
853
  # Pipeline interfaces are resolved at a later point. Do it there only to maintain consistency. #474
854
+
849
855
  pass
850
856
  if isinstance(v, str):
851
857
  v = expandpath(v)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: looper
3
- Version: 2.0.0a1
3
+ Version: 2.0.0a3
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,20 +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
18
+ Requires-Dist: colorama>=0.3.9
19
+ Requires-Dist: eido>=0.2.3
21
20
  Requires-Dist: jinja2
22
- Requires-Dist: logmuse >=0.2.0
23
- Requires-Dist: pandas >=2.0.2
24
- Requires-Dist: pephubclient >=0.4.0
25
- Requires-Dist: pipestat >=0.9.2
26
- Requires-Dist: peppy >=0.40.2
27
- Requires-Dist: pyyaml >=3.12
28
- Requires-Dist: rich >=9.10.0
29
- Requires-Dist: ubiquerg >=0.8.1a1
30
- Requires-Dist: yacman ==0.9.3
31
- Requires-Dist: pydantic-argparse >=0.9.0
21
+ Requires-Dist: logmuse>=0.2.0
22
+ Requires-Dist: pandas>=2.0.2
23
+ Requires-Dist: pephubclient>=0.4.0
24
+ Requires-Dist: pipestat>=0.12.0a1
25
+ Requires-Dist: peppy>=0.40.6
26
+ Requires-Dist: pyyaml>=3.12
27
+ Requires-Dist: rich>=9.10.0
28
+ Requires-Dist: ubiquerg>=0.8.1a1
29
+ Requires-Dist: yacman==0.9.3
30
+ Requires-Dist: pydantic-argparse>=0.9.0
32
31
  Requires-Dist: psutil
33
32
 
34
33
  # <img src="docs/img/looper_logo.svg" alt="looper logo" height="70">
@@ -1,24 +1,24 @@
1
1
  looper/__init__.py,sha256=f_z9YY4ibOk7eyWoaViH_VaCXMlPQeiftbnibSFj-3E,1333
2
2
  looper/__main__.py,sha256=OOCmI-dPUvInnJHkHNMf54cblNJ3Yl9ELOwZcfOXmD8,240
3
- looper/_version.py,sha256=wJ2KiFRb6QXaKu1tfxwKmo9CE2xP4f3BkEUYoIJeUkQ,121
4
- looper/cli_divvy.py,sha256=J07x83sqC4jJeu3_yS6KOARPWmwKGAV7JvN33T5zDac,5907
5
- looper/cli_pydantic.py,sha256=saAD0dwizLi_VajVKElQmYRqtn0Yyl_r712znxZMxRQ,14209
6
- looper/conductor.py,sha256=oNfqANA2tIhQTAQFxd-rQ4ccPA60D933EGo3rfbJGFo,35061
3
+ looper/_version.py,sha256=MwJwmoXPcF0oLwdO3af_PLuTBNXWHfGxew5Q7p37mTQ,121
4
+ looper/cli_divvy.py,sha256=_VGbOFLkXtKdkZA6omlzgXbXkuUM5aLQ50aTTtbTrVI,5975
5
+ looper/cli_pydantic.py,sha256=BA-jq6lyFnGoNNLCKAFrfqMQ7qRR3qxYK62PfMEoKA4,14425
6
+ looper/conductor.py,sha256=lzY6Gzsb8oX-KLzLkRa0XrYWSxLeiy6jRmmD15WNAkw,35116
7
7
  looper/const.py,sha256=OscEELQsyLKlSrmwuXfyLRwpAUJUEpGD2UxBeLJDXgw,8703
8
- looper/divvy.py,sha256=5x8hV1lT5tEQdAUtVjn0rNwYnJroNij0RyDn-wHf4QE,15251
9
- looper/exceptions.py,sha256=r6SKKt-m8CXQnXGDnuiwoA6zBJhIZflygBKjX4RCloI,3419
10
- looper/looper.py,sha256=ZWTulMz6NobnYFUjev513TJwXqknrb4_gZrV-a_fT9g,30041
8
+ looper/divvy.py,sha256=yokD0--xN0kaxPuPRZrRPgKinl0Sqt0cvkmNMvwe94A,15860
9
+ looper/exceptions.py,sha256=weSXsPadikAHVJvUUOT1uE56C_nKqgC3iBOvDebPDVE,3572
10
+ looper/looper.py,sha256=zNszsV50ZkC5ioaLHgMg9H3hSTEHAAV-jBpLra3OIKw,31624
11
11
  looper/parser_types.py,sha256=d3FHt54f9jo9VZMr5SQkbghcAdABqiYZW2JBGO5EBnw,2327
12
12
  looper/pipeline_interface.py,sha256=mN4-XICyZzuVLTOq3b0ijppYe6ib_Ljlyf6KxZCJh2A,14537
13
13
  looper/plugins.py,sha256=MaMdPmK9U_4FkNJE5kccohBbY1i2qj1NTEucubFOJek,5747
14
14
  looper/processed_project.py,sha256=jZxoMYafvr-OHFxylc5ivGty1VwXBZhl0kgoFkY-174,9837
15
15
  looper/project.py,sha256=SFHdi58eRBWtye5lUFhwzBcG7ejrMurmDzmkrC3XAic,34339
16
- looper/utils.py,sha256=VXKaEYXW0XEQNy__hNBv42i-sz5qB7QiwpXzhXs_3Wk,39556
16
+ looper/utils.py,sha256=-4QlScIB7eewIbmEJdAv2d0ZE0qr_q9acm2XUOiMEek,39769
17
17
  looper/command_models/DEVELOPER.md,sha256=eRxnrO-vqNJjExzamXKEq5wr_-Zw6PQEwkS9RPinYrk,2775
18
18
  looper/command_models/README.md,sha256=3RGegeZlTZYnhcHXRu6bdI_81WZom2q7QYMV-KGYY7U,588
19
19
  looper/command_models/__init__.py,sha256=6QWC2TewowEL7dATli5YpMmFWuXaLEPktofJCXkYUBI,187
20
- looper/command_models/arguments.py,sha256=sRrJWCSQmnjGLnOo-Wl6_PnvTZz8VIWII79svI3BqFk,8653
21
- looper/command_models/commands.py,sha256=EvKyjNdUBspXnOUMprxIY0C4VPka2PBj8CJgtd5Ya9w,9680
20
+ looper/command_models/arguments.py,sha256=I9IUdDcklfctA9gdYvxyy29WawSCYPO5FwozgisCO94,9025
21
+ looper/command_models/commands.py,sha256=nBD8uOxpjby2G0n-PuMKvRBM6m5tra_Mz_nlZALwmRE,9813
22
22
  looper/default_config/divvy_config.yaml,sha256=wK5kLDGBV2wwoyqg2rl3X8SXjds4x0mwBUjUzF1Ln7g,1705
23
23
  looper/default_config/divvy_templates/localhost_bulker_template.sub,sha256=yn5VB9Brt7Hck9LT17hD2o8Kn-76gYJQk_A-8C1Gr4k,164
24
24
  looper/default_config/divvy_templates/localhost_docker_template.sub,sha256=XRr7AlR7-TP1L3hyBMfka_RgWRL9vzOlS5Kd1xSNwT0,183
@@ -60,9 +60,9 @@ looper/schemas/divvy_config_schema.yaml,sha256=7GJfKLc3VX4RGjHnOE1zxwsHXhj_ur9za
60
60
  looper/schemas/pipeline_interface_schema_generic.yaml,sha256=3YfKFyRUIwxG41FEidR1dXe9IU6ye51LSUBfSpmMuss,1773
61
61
  looper/schemas/pipeline_interface_schema_project.yaml,sha256=-ZWyA0lKXWik3obuLNVk3IsAZYfbLVbCDvJnD-Fcluo,1567
62
62
  looper/schemas/pipeline_interface_schema_sample.yaml,sha256=x0OwVnijJpvm50DscvvJujdK4UAI7d71pqVemQS-D-0,1564
63
- looper-2.0.0a1.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
64
- looper-2.0.0a1.dist-info/METADATA,sha256=OZPEqy9PPCjzJ110u-HazaRkaUIiuesCZMC3jpfl2vQ,1800
65
- looper-2.0.0a1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
66
- looper-2.0.0a1.dist-info/entry_points.txt,sha256=ejZpghZG3OoTK69u9rTW-yLyI6SC63bBTUb-Vw26HG4,87
67
- looper-2.0.0a1.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
68
- looper-2.0.0a1.dist-info/RECORD,,
63
+ looper-2.0.0a3.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
64
+ looper-2.0.0a3.dist-info/METADATA,sha256=tP0InSt3Xn7098v19ZDWawfATd2Ez2zoMsecZKv6SCM,1762
65
+ looper-2.0.0a3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
66
+ looper-2.0.0a3.dist-info/entry_points.txt,sha256=iHltI2_Jdved27vccmWhvmcHWUZ7Mf6CfDV6QkY1Lc8,91
67
+ looper-2.0.0a3.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
68
+ looper-2.0.0a3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
2
  divvy = looper.__main__:divvy_main
3
- looper = looper.cli_pydantic:main
3
+ looper = looper.cli_pydantic:main_cli