looper 2.0.0a2__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
looper/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2.0.0a2"
1
+ __version__ = "2.0.1"
2
2
  # You must change the version in parser = pydantic_argparse.ArgumentParser in cli_pydantic.py!!!
looper/cli_pydantic.py CHANGED
@@ -340,7 +340,7 @@ def main(test_args=None) -> dict:
340
340
  prog="looper",
341
341
  description="Looper: A job submitter for Portable Encapsulated Projects",
342
342
  add_help=True,
343
- version="2.0.0a2",
343
+ version="2.0.1",
344
344
  )
345
345
 
346
346
  parser = add_short_arguments(parser, ArgumentEnum)
@@ -1,6 +1,6 @@
1
1
  """
2
2
  This package holds `pydantic` models that describe commands and their arguments.
3
3
 
4
- These can be used either by an HTTP API or with the `pydantic-argparse`
4
+ These can be used either by an HTTP API or with the `pydantic-argparse`
5
5
  library to build a CLI.
6
6
  """
@@ -184,6 +184,12 @@ 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
+ )
187
193
 
188
194
  GENERIC = Argument(
189
195
  name="generic",
@@ -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
 
looper/conductor.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Pipeline job submission orchestration """
1
+ """Pipeline job submission orchestration"""
2
2
 
3
3
  import importlib
4
4
  import logging
@@ -18,7 +18,7 @@ from eido import read_schema, get_input_files_size
18
18
  from eido.const import INPUT_FILE_SIZE_KEY, MISSING_KEY
19
19
  from jinja2.exceptions import UndefinedError
20
20
 
21
- from peppy.const import CONFIG_KEY, SAMPLE_NAME_ATTR, SAMPLE_YAML_EXT
21
+ from peppy.const import CONFIG_KEY, SAMPLE_YAML_EXT
22
22
  from peppy.exceptions import RemoteYAMLError
23
23
  from pipestat import PipestatError
24
24
  from ubiquerg import expandpath
@@ -77,7 +77,7 @@ def _get_yaml_path(namespaces, template_key, default_name_appendix="", filename=
77
77
  # default YAML location
78
78
  f = (
79
79
  filename
80
- or f"{namespaces['sample'][SAMPLE_NAME_ATTR]}"
80
+ or f"{namespaces['sample'][namespaces['sample']['_project'].sample_table_index]}"
81
81
  f"{default_name_appendix}"
82
82
  f"{SAMPLE_YAML_EXT[0]}"
83
83
  )
looper/const.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Shared project constants """
1
+ """Shared project constants"""
2
2
 
3
3
  import os
4
4
  from enum import Enum
looper/divvy.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Computing configuration representation """
1
+ """Computing configuration representation"""
2
2
 
3
3
  import logging
4
4
  import os
@@ -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.
looper/exceptions.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Exceptions for specific looper issues. """
1
+ """Exceptions for specific looper issues."""
2
2
 
3
3
  from abc import ABCMeta
4
4
  from collections.abc import Iterable
@@ -15,6 +15,7 @@ _all__ = [
15
15
  "PipelineInterfaceConfigError",
16
16
  "PipelineInterfaceRequirementsError",
17
17
  "MisconfigurationException",
18
+ "LooperReportError",
18
19
  ]
19
20
 
20
21
 
@@ -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/parser_types.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Definitions of the parser argument types """
1
+ """Definitions of the parser argument types"""
2
2
 
3
3
  from yacman import YAMLConfigManager
4
4
 
@@ -1,4 +1,4 @@
1
- """ Model the connection between a pipeline and a project or executor. """
1
+ """Model the connection between a pipeline and a project or executor."""
2
2
 
3
3
  import os
4
4
  from collections.abc import Mapping
looper/project.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Looper version of NGS project model. """
1
+ """Looper version of NGS project model."""
2
2
 
3
3
  import itertools
4
4
  import os
looper/utils.py CHANGED
@@ -1,4 +1,4 @@
1
- """ Helpers without an obvious logical home. """
1
+ """Helpers without an obvious logical home."""
2
2
 
3
3
  import argparse
4
4
  from collections import defaultdict
@@ -1112,7 +1112,6 @@ def inspect_looper_config_file(looper_config_dict) -> None:
1112
1112
 
1113
1113
 
1114
1114
  def expand_nested_var_templates(var_templates_dict, namespaces):
1115
-
1116
1115
  "Takes all var_templates as a dict and recursively expands any paths."
1117
1116
 
1118
1117
  result = {}
@@ -1127,7 +1126,6 @@ def expand_nested_var_templates(var_templates_dict, namespaces):
1127
1126
 
1128
1127
 
1129
1128
  def render_nested_var_templates(var_templates_dict, namespaces):
1130
-
1131
1129
  "Takes all var_templates as a dict and recursively renders the jinja templates."
1132
1130
 
1133
1131
  result = {}
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: looper
3
- Version: 2.0.0a2
3
+ Version: 2.0.1
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
@@ -16,19 +16,28 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.txt
18
18
  Requires-Dist: colorama>=0.3.9
19
- Requires-Dist: eido>=0.2.3
19
+ Requires-Dist: eido>=0.2.4
20
20
  Requires-Dist: jinja2
21
21
  Requires-Dist: logmuse>=0.2.0
22
22
  Requires-Dist: pandas>=2.0.2
23
23
  Requires-Dist: pephubclient>=0.4.0
24
- Requires-Dist: pipestat>=0.10.2
24
+ Requires-Dist: pipestat>=0.12.0a1
25
25
  Requires-Dist: peppy>=0.40.6
26
26
  Requires-Dist: pyyaml>=3.12
27
27
  Requires-Dist: rich>=9.10.0
28
- Requires-Dist: ubiquerg>=0.8.1a1
28
+ Requires-Dist: ubiquerg>=0.8.1
29
29
  Requires-Dist: yacman==0.9.3
30
30
  Requires-Dist: pydantic-argparse>=0.9.0
31
31
  Requires-Dist: psutil
32
+ Dynamic: author
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: keywords
38
+ Dynamic: license
39
+ Dynamic: requires-dist
40
+ Dynamic: summary
32
41
 
33
42
  # <img src="docs/img/looper_logo.svg" alt="looper logo" height="70">
34
43
 
@@ -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=mX3fglKqsJRTjP938X3Xm2eAJT9fve28fdydFnJFimc,121
3
+ looper/_version.py,sha256=DW9L8zOJPcNY9ZbgS_iQmVE_rIc1YpAHFt8cZRRXFZU,119
4
4
  looper/cli_divvy.py,sha256=_VGbOFLkXtKdkZA6omlzgXbXkuUM5aLQ50aTTtbTrVI,5975
5
- looper/cli_pydantic.py,sha256=uysPJfTrfG7-k7u6oDpaLG-3hD6U8YVrDiOW54ySniI,14425
6
- looper/conductor.py,sha256=lzY6Gzsb8oX-KLzLkRa0XrYWSxLeiy6jRmmD15WNAkw,35116
7
- looper/const.py,sha256=OscEELQsyLKlSrmwuXfyLRwpAUJUEpGD2UxBeLJDXgw,8703
8
- looper/divvy.py,sha256=eiYhcp8ZgQ0uzlk0c5yJp0QaiVrQYfSeEtmQBJiScOM,15417
9
- looper/exceptions.py,sha256=AxYCTLxKb_fJFgU9VBnPYaRX2lGhmMEYaCbJOi-27Wk,3384
10
- looper/looper.py,sha256=ZWTulMz6NobnYFUjev513TJwXqknrb4_gZrV-a_fT9g,30041
11
- looper/parser_types.py,sha256=d3FHt54f9jo9VZMr5SQkbghcAdABqiYZW2JBGO5EBnw,2327
12
- looper/pipeline_interface.py,sha256=mN4-XICyZzuVLTOq3b0ijppYe6ib_Ljlyf6KxZCJh2A,14537
5
+ looper/cli_pydantic.py,sha256=KS26gh_vF9fxjVpuBbI1K4sgokT1PWme4klwwcphLWQ,14423
6
+ looper/conductor.py,sha256=EvVSZ9czG8-cRDLl2aXHZPeQ02J4xM0T_Y6mYO_2jTQ,35131
7
+ looper/const.py,sha256=eClmwc0c1r7zjQKrqBPa_OUOnoCqNNe0e64xElvLQHA,8701
8
+ looper/divvy.py,sha256=KQSIxgeiFJf73c7xpAV8CoxUIuXjcfMphNWKSZXmuP0,15858
9
+ looper/exceptions.py,sha256=-VEExHqjAtOSacBx45DA_9c-2i4EqxqGRLttTKxXXgg,3570
10
+ looper/looper.py,sha256=zNszsV50ZkC5ioaLHgMg9H3hSTEHAAV-jBpLra3OIKw,31624
11
+ looper/parser_types.py,sha256=PgAdrtCGvXhXcX7UbW8E3oC414uv1mKV4sTnXVHtIzE,2325
12
+ looper/pipeline_interface.py,sha256=NSAHx1ITmODy--Eh_b2oO4hP4mSHvs_7lkwuFLXbjsE,14535
13
13
  looper/plugins.py,sha256=MaMdPmK9U_4FkNJE5kccohBbY1i2qj1NTEucubFOJek,5747
14
14
  looper/processed_project.py,sha256=jZxoMYafvr-OHFxylc5ivGty1VwXBZhl0kgoFkY-174,9837
15
- looper/project.py,sha256=SFHdi58eRBWtye5lUFhwzBcG7ejrMurmDzmkrC3XAic,34339
16
- looper/utils.py,sha256=-4QlScIB7eewIbmEJdAv2d0ZE0qr_q9acm2XUOiMEek,39769
15
+ looper/project.py,sha256=uoh8zGP4pdo630pcxLW5hdB5a2cP-YK8-CFd6F26Yus,34337
16
+ looper/utils.py,sha256=42YP5Qz41TDUW45ptDio_2w8f3iEi2judeBo9d5yJh0,39765
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
- looper/command_models/__init__.py,sha256=6QWC2TewowEL7dATli5YpMmFWuXaLEPktofJCXkYUBI,187
20
- looper/command_models/arguments.py,sha256=wGGc5tleHC31F0etbiYaumHwP5xlJsSXapJ10lhltHg,8830
21
- looper/command_models/commands.py,sha256=RMTiHg0txBGDb_fHfhTWujP3VnnB6ROCHqByiwgMS4c,9716
19
+ looper/command_models/__init__.py,sha256=suzlc6tiNFQlv9b--aZdE9uyklTcKQ_z4YGyLfO0Ru0,186
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.0a2.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
64
- looper-2.0.0a2.dist-info/METADATA,sha256=s4IlCgd597EhgR8H0LIB-3Qz02absf63sudpuFG1rKM,1760
65
- looper-2.0.0a2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
66
- looper-2.0.0a2.dist-info/entry_points.txt,sha256=iHltI2_Jdved27vccmWhvmcHWUZ7Mf6CfDV6QkY1Lc8,91
67
- looper-2.0.0a2.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
68
- looper-2.0.0a2.dist-info/RECORD,,
63
+ looper-2.0.1.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
64
+ looper-2.0.1.dist-info/METADATA,sha256=GPVKXrQreBCtmdWPvAiXb1Hci6InnMu3OgawD_IDcF0,1943
65
+ looper-2.0.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
66
+ looper-2.0.1.dist-info/entry_points.txt,sha256=iHltI2_Jdved27vccmWhvmcHWUZ7Mf6CfDV6QkY1Lc8,91
67
+ looper-2.0.1.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
68
+ looper-2.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5