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 +1 -1
- looper/cli_divvy.py +10 -6
- looper/cli_pydantic.py +40 -33
- looper/command_models/arguments.py +15 -0
- looper/command_models/commands.py +5 -1
- looper/conductor.py +1 -0
- looper/divvy.py +26 -9
- looper/exceptions.py +9 -1
- looper/looper.py +50 -16
- looper/utils.py +82 -76
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/METADATA +13 -14
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/RECORD +16 -16
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/WHEEL +1 -1
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/entry_points.txt +1 -1
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/LICENSE.txt +0 -0
- {looper-2.0.0a1.dist-info → looper-2.0.0a3.dist-info}/top_level.txt +0 -0
looper/_version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
__version__ = "2.0.
|
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
|
147
|
+
with open(pkg["submission_template"], "r") as f:
|
146
148
|
print(f.read())
|
147
|
-
_LOGGER.info(
|
149
|
+
_LOGGER.info(
|
150
|
+
"Submission command is: " + pkg["submission_command"] + "\n"
|
151
|
+
)
|
148
152
|
if pkg_name == "docker":
|
149
|
-
print("Docker args are: " + pkg
|
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
|
-
|
132
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
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().
|
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) ->
|
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.
|
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
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
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
|
-
|
115
|
-
os.path.dirname(
|
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.
|
160
|
-
os.path.
|
161
|
-
|
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
|
-
"""
|
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
|
-
|
98
|
-
|
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][
|
101
|
-
_LOGGER.debug(
|
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
|
-
|
569
|
-
|
570
|
-
|
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
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
583
|
-
|
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
|
-
|
627
|
-
|
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
|
-
|
632
|
-
|
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
|
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": "{
|
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
|
-
|
644
|
+
cfg["project_name"] = (
|
645
|
+
console.input(f"Project name: [yellow]({DEFAULTS['project_name']})[/yellow] >")
|
646
|
+
or DEFAULTS["project_name"]
|
647
|
+
)
|
640
648
|
|
641
|
-
|
642
|
-
|
643
|
-
|
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
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
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
|
-
|
657
|
-
|
658
|
-
|
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
|
-
|
670
|
+
add_more_pifaces = True
|
671
|
+
piface_paths = []
|
672
|
+
while add_more_pifaces:
|
673
|
+
piface_path = (
|
662
674
|
console.input(
|
663
|
-
|
675
|
+
"Add each path to a pipeline interface: [yellow](pipeline_interface.yaml)[/yellow] >"
|
664
676
|
)
|
665
|
-
or
|
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
|
-
|
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
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
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"] =
|
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
|
-
|
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
|
-
|
787
|
-
|
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.
|
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
|
19
|
-
Requires-Dist:
|
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
|
23
|
-
Requires-Dist: pandas
|
24
|
-
Requires-Dist: pephubclient
|
25
|
-
Requires-Dist: pipestat
|
26
|
-
Requires-Dist: peppy
|
27
|
-
Requires-Dist: pyyaml
|
28
|
-
Requires-Dist: rich
|
29
|
-
Requires-Dist: ubiquerg
|
30
|
-
Requires-Dist: yacman
|
31
|
-
Requires-Dist: pydantic-argparse
|
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=
|
4
|
-
looper/cli_divvy.py,sha256=
|
5
|
-
looper/cli_pydantic.py,sha256=
|
6
|
-
looper/conductor.py,sha256=
|
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=
|
9
|
-
looper/exceptions.py,sha256=
|
10
|
-
looper/looper.py,sha256=
|
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
|
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=
|
21
|
-
looper/command_models/commands.py,sha256=
|
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.
|
64
|
-
looper-2.0.
|
65
|
-
looper-2.0.
|
66
|
-
looper-2.0.
|
67
|
-
looper-2.0.
|
68
|
-
looper-2.0.
|
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,,
|
File without changes
|
File without changes
|