looper 1.5.0__py3-none-any.whl → 1.6.0a1__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/__init__.py +3 -498
- looper/__main__.py +2 -2
- looper/_version.py +1 -1
- looper/cli_divvy.py +182 -0
- looper/cli_looper.py +776 -0
- looper/conductor.py +53 -206
- looper/const.py +51 -3
- looper/divvy.py +28 -196
- looper/exceptions.py +18 -0
- looper/looper.py +177 -612
- looper/plugins.py +160 -0
- looper/processed_project.py +1 -1
- looper/project.py +229 -117
- looper/utils.py +119 -43
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/METADATA +6 -6
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/RECORD +20 -20
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/WHEEL +1 -1
- looper/html_reports.py +0 -1057
- looper/html_reports_pipestat.py +0 -924
- looper/html_reports_project_pipestat.py +0 -269
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/LICENSE.txt +0 -0
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/entry_points.txt +0 -0
- {looper-1.5.0.dist-info → looper-1.6.0a1.dist-info}/top_level.txt +0 -0
looper/utils.py
CHANGED
@@ -19,7 +19,7 @@ from pephubclient.constants import RegistryPath
|
|
19
19
|
from pydantic.error_wrappers import ValidationError
|
20
20
|
|
21
21
|
from .const import *
|
22
|
-
from .exceptions import MisconfigurationException
|
22
|
+
from .exceptions import MisconfigurationException, RegistryPathException
|
23
23
|
|
24
24
|
_LOGGER = getLogger(__name__)
|
25
25
|
|
@@ -72,7 +72,7 @@ def fetch_flag_files(prj=None, results_folder="", flags=FLAGS):
|
|
72
72
|
return files_by_flag
|
73
73
|
|
74
74
|
|
75
|
-
def fetch_sample_flags(prj, sample, pl_name):
|
75
|
+
def fetch_sample_flags(prj, sample, pl_name, flag_dir=None):
|
76
76
|
"""
|
77
77
|
Find any flag files present for a sample associated with a project
|
78
78
|
|
@@ -82,7 +82,7 @@ def fetch_sample_flags(prj, sample, pl_name):
|
|
82
82
|
:return Iterable[str]: collection of flag file path(s) associated with the
|
83
83
|
given sample for the given project
|
84
84
|
"""
|
85
|
-
sfolder = sample_folder(prj=prj, sample=sample)
|
85
|
+
sfolder = flag_dir or sample_folder(prj=prj, sample=sample)
|
86
86
|
if not os.path.isdir(sfolder):
|
87
87
|
_LOGGER.debug(
|
88
88
|
"Results folder ({}) doesn't exist for sample {}".format(
|
@@ -98,6 +98,29 @@ def fetch_sample_flags(prj, sample, pl_name):
|
|
98
98
|
]
|
99
99
|
|
100
100
|
|
101
|
+
def get_sample_status(sample, flags):
|
102
|
+
"""
|
103
|
+
get a sample status
|
104
|
+
|
105
|
+
"""
|
106
|
+
|
107
|
+
statuses = []
|
108
|
+
|
109
|
+
for f in flags:
|
110
|
+
basename = os.path.basename(f)
|
111
|
+
status = os.path.splitext(basename)[0].split("_")[-1]
|
112
|
+
if sample in basename:
|
113
|
+
statuses.append(status.upper())
|
114
|
+
|
115
|
+
if len(statuses) > 1:
|
116
|
+
_LOGGER.warning(f"Multiple status flags found for {sample}")
|
117
|
+
|
118
|
+
if statuses == []:
|
119
|
+
return None
|
120
|
+
|
121
|
+
return statuses[0]
|
122
|
+
|
123
|
+
|
101
124
|
def grab_project_data(prj):
|
102
125
|
"""
|
103
126
|
From the given Project, grab Sample-independent data.
|
@@ -335,12 +358,12 @@ def init_generic_pipeline():
|
|
335
358
|
# Destination one level down from CWD in pipeline folder
|
336
359
|
dest_file = os.path.join(os.getcwd(), "pipeline", LOOPER_GENERIC_PIPELINE)
|
337
360
|
|
338
|
-
#
|
361
|
+
# Create Generic Pipeline Interface
|
339
362
|
generic_pipeline_dict = {
|
340
|
-
"pipeline_name": "
|
363
|
+
"pipeline_name": "default_pipeline_name",
|
341
364
|
"pipeline_type": "sample",
|
342
365
|
"output_schema": "output_schema.yaml",
|
343
|
-
"var_templates": {"pipeline": "{looper.piface_dir}/
|
366
|
+
"var_templates": {"pipeline": "{looper.piface_dir}/pipeline.sh"},
|
344
367
|
"command_template": "{pipeline.var_templates.pipeline} {sample.file} "
|
345
368
|
"--output-parent {looper.sample_output_folder}",
|
346
369
|
}
|
@@ -349,58 +372,101 @@ def init_generic_pipeline():
|
|
349
372
|
if not os.path.exists(dest_file):
|
350
373
|
with open(dest_file, "w") as file:
|
351
374
|
yaml.dump(generic_pipeline_dict, file)
|
352
|
-
print(f"
|
375
|
+
print(f"Pipeline interface successfully created at: {dest_file}")
|
353
376
|
else:
|
354
377
|
print(
|
355
|
-
f"
|
378
|
+
f"Pipeline interface file already exists `{dest_file}`. Skipping creation.."
|
356
379
|
)
|
357
380
|
|
381
|
+
# Create Generic Output Schema
|
382
|
+
dest_file = os.path.join(os.getcwd(), "pipeline", LOOPER_GENERIC_OUTPUT_SCHEMA)
|
383
|
+
generic_output_schema_dict = {
|
384
|
+
"pipeline_name": "default_pipeline_name",
|
385
|
+
"samples": {
|
386
|
+
"number_of_lines": {
|
387
|
+
"type": "integer",
|
388
|
+
"description": "Number of lines in the input file.",
|
389
|
+
}
|
390
|
+
},
|
391
|
+
}
|
392
|
+
# Write file
|
393
|
+
if not os.path.exists(dest_file):
|
394
|
+
with open(dest_file, "w") as file:
|
395
|
+
yaml.dump(generic_output_schema_dict, file)
|
396
|
+
print(f"Output schema successfully created at: {dest_file}")
|
397
|
+
else:
|
398
|
+
print(f"Output schema file already exists `{dest_file}`. Skipping creation..")
|
399
|
+
|
400
|
+
# Create Generic countlines.sh
|
401
|
+
dest_file = os.path.join(os.getcwd(), "pipeline", LOOPER_GENERIC_COUNT_LINES)
|
402
|
+
shell_code = """#!/bin/bash
|
403
|
+
linecount=`wc -l $1 | sed -E 's/^[[:space:]]+//' | cut -f1 -d' '`
|
404
|
+
pipestat report -r $2 -i 'number_of_lines' -v $linecount -c $3
|
405
|
+
echo "Number of lines: $linecount"
|
406
|
+
"""
|
407
|
+
if not os.path.exists(dest_file):
|
408
|
+
with open(dest_file, "w") as file:
|
409
|
+
file.write(shell_code)
|
410
|
+
print(f"count_lines.sh successfully created at: {dest_file}")
|
411
|
+
else:
|
412
|
+
print(f"count_lines.sh file already exists `{dest_file}`. Skipping creation..")
|
413
|
+
|
358
414
|
return True
|
359
415
|
|
360
416
|
|
361
|
-
def
|
362
|
-
|
363
|
-
|
417
|
+
def read_looper_dotfile():
|
418
|
+
"""
|
419
|
+
Read looper config file
|
420
|
+
:return str: path to the config file read from the dotfile
|
421
|
+
:raise MisconfigurationException: if the dotfile does not consist of the
|
422
|
+
required key pointing to the PEP
|
423
|
+
"""
|
424
|
+
dot_file_path = dotfile_path(must_exist=True)
|
425
|
+
return read_looper_config_file(looper_config_path=dot_file_path)
|
426
|
+
|
427
|
+
|
428
|
+
def initiate_looper_config(
|
429
|
+
looper_config_path: str,
|
430
|
+
pep_path: str = None,
|
364
431
|
output_dir: str = None,
|
365
432
|
sample_pipeline_interfaces: Union[List[str], str] = None,
|
366
433
|
project_pipeline_interfaces: Union[List[str], str] = None,
|
367
434
|
force=False,
|
368
435
|
):
|
369
436
|
"""
|
370
|
-
Initialize looper
|
437
|
+
Initialize looper config file
|
371
438
|
|
372
|
-
:param str
|
373
|
-
:param str
|
439
|
+
:param str looper_config_path: absolute path to the file to initialize
|
440
|
+
:param str pep_path: path to the PEP to be used in pipeline
|
374
441
|
:param str output_dir: path to the output directory
|
375
442
|
:param str|list sample_pipeline_interfaces: path or list of paths to sample pipeline interfaces
|
376
443
|
:param str|list project_pipeline_interfaces: path or list of paths to project pipeline interfaces
|
377
444
|
:param bool force: whether the existing file should be overwritten
|
378
445
|
:return bool: whether the file was initialized
|
379
446
|
"""
|
380
|
-
if os.path.exists(
|
381
|
-
print("Can't initialize, file exists: {}"
|
447
|
+
if os.path.exists(looper_config_path) and not force:
|
448
|
+
print(f"Can't initialize, file exists: {looper_config_path}")
|
382
449
|
return False
|
383
|
-
|
384
|
-
|
450
|
+
|
451
|
+
if pep_path:
|
452
|
+
if is_registry_path(pep_path):
|
385
453
|
pass
|
386
454
|
else:
|
387
|
-
|
388
|
-
if not os.path.isabs(
|
389
|
-
|
390
|
-
assert os.path.exists(
|
455
|
+
pep_path = expandpath(pep_path)
|
456
|
+
if not os.path.isabs(pep_path):
|
457
|
+
pep_path = os.path.join(os.path.dirname(looper_config_path), pep_path)
|
458
|
+
assert os.path.exists(pep_path), OSError(
|
391
459
|
"Provided config path is invalid. You must provide path "
|
392
|
-
"that is either absolute or relative to: {}"
|
393
|
-
os.path.dirname(path)
|
394
|
-
)
|
460
|
+
f"that is either absolute or relative to: {os.path.dirname(looper_config_path)}"
|
395
461
|
)
|
396
462
|
else:
|
397
|
-
|
463
|
+
pep_path = "example/pep/path"
|
398
464
|
|
399
465
|
if not output_dir:
|
400
466
|
output_dir = "."
|
401
467
|
|
402
468
|
looper_config_dict = {
|
403
|
-
"pep_config": os.path.relpath(
|
469
|
+
"pep_config": os.path.relpath(pep_path),
|
404
470
|
"output_dir": output_dir,
|
405
471
|
"pipeline_interfaces": {
|
406
472
|
"sample": sample_pipeline_interfaces,
|
@@ -408,24 +474,12 @@ def init_dotfile(
|
|
408
474
|
},
|
409
475
|
}
|
410
476
|
|
411
|
-
with open(
|
477
|
+
with open(looper_config_path, "w") as dotfile:
|
412
478
|
yaml.dump(looper_config_dict, dotfile)
|
413
|
-
print("Initialized looper
|
479
|
+
print(f"Initialized looper config file: {looper_config_path}")
|
414
480
|
return True
|
415
481
|
|
416
482
|
|
417
|
-
def read_looper_dotfile():
|
418
|
-
"""
|
419
|
-
Read looper config file
|
420
|
-
|
421
|
-
:return str: path to the config file read from the dotfile
|
422
|
-
:raise MisconfigurationException: if the dotfile does not consist of the
|
423
|
-
required key pointing to the PEP
|
424
|
-
"""
|
425
|
-
dot_file_path = dotfile_path(must_exist=True)
|
426
|
-
return read_looper_config_file(looper_config_path=dot_file_path)
|
427
|
-
|
428
|
-
|
429
483
|
def read_looper_config_file(looper_config_path: str) -> dict:
|
430
484
|
"""
|
431
485
|
Read Looper config file which includes:
|
@@ -442,7 +496,10 @@ def read_looper_config_file(looper_config_path: str) -> dict:
|
|
442
496
|
dp_data = yaml.safe_load(dotfile)
|
443
497
|
|
444
498
|
if PEP_CONFIG_KEY in dp_data:
|
499
|
+
# Looper expects the config path to live at looper.config_file
|
500
|
+
# However, user may wish to access the pep at looper.pep_config
|
445
501
|
return_dict[PEP_CONFIG_FILE_KEY] = dp_data[PEP_CONFIG_KEY]
|
502
|
+
return_dict[PEP_CONFIG_KEY] = dp_data[PEP_CONFIG_KEY]
|
446
503
|
|
447
504
|
# TODO: delete it in looper 2.0
|
448
505
|
elif DOTFILE_CFG_PTH_KEY in dp_data:
|
@@ -460,6 +517,9 @@ def read_looper_config_file(looper_config_path: str) -> dict:
|
|
460
517
|
f"{OUTDIR_KEY} is not defined in looper config file ({looper_config_path})"
|
461
518
|
)
|
462
519
|
|
520
|
+
if PIPESTAT_KEY in dp_data:
|
521
|
+
return_dict[PIPESTAT_KEY] = dp_data[PIPESTAT_KEY]
|
522
|
+
|
463
523
|
if PIPELINE_INTERFACES_KEY in dp_data:
|
464
524
|
dp_data.setdefault(PIPELINE_INTERFACES_KEY, {})
|
465
525
|
return_dict[SAMPLE_PL_ARG] = dp_data.get(PIPELINE_INTERFACES_KEY).get("sample")
|
@@ -473,6 +533,17 @@ def read_looper_config_file(looper_config_path: str) -> dict:
|
|
473
533
|
)
|
474
534
|
dp_data.setdefault(PIPELINE_INTERFACES_KEY, {})
|
475
535
|
|
536
|
+
config_dir_path = os.path.dirname(os.path.abspath(looper_config_path))
|
537
|
+
|
538
|
+
# Expand paths in case ENV variables are used
|
539
|
+
for k, v in return_dict.items():
|
540
|
+
if isinstance(v, str):
|
541
|
+
v = expandpath(v)
|
542
|
+
if not os.path.isabs(v) and not is_registry_path(v):
|
543
|
+
return_dict[k] = os.path.join(config_dir_path, v)
|
544
|
+
else:
|
545
|
+
return_dict[k] = v
|
546
|
+
|
476
547
|
return return_dict
|
477
548
|
|
478
549
|
|
@@ -510,8 +581,13 @@ def is_registry_path(input_string: str) -> bool:
|
|
510
581
|
:param str input_string: path to the PEP (or registry path)
|
511
582
|
:return bool: True if input is a registry path
|
512
583
|
"""
|
513
|
-
|
514
|
-
|
584
|
+
try:
|
585
|
+
if input_string.endswith(".yaml"):
|
586
|
+
return False
|
587
|
+
except AttributeError:
|
588
|
+
raise RegistryPathException(
|
589
|
+
msg=f"Malformed registry path. Unable to parse {input_string} as a registry path."
|
590
|
+
)
|
515
591
|
try:
|
516
592
|
registry_path = RegistryPath(**parse_registry_path(input_string))
|
517
593
|
except (ValidationError, TypeError):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: looper
|
3
|
-
Version: 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
|
@@ -17,17 +17,17 @@ Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE.txt
|
18
18
|
Requires-Dist: colorama >=0.3.9
|
19
19
|
Requires-Dist: divvy >=0.5.0
|
20
|
-
Requires-Dist: eido >=0.2.
|
20
|
+
Requires-Dist: eido >=0.2.1
|
21
21
|
Requires-Dist: jinja2
|
22
22
|
Requires-Dist: logmuse >=0.2.0
|
23
23
|
Requires-Dist: pandas >=2.0.2
|
24
|
-
Requires-Dist: pephubclient
|
25
|
-
Requires-Dist: peppy >=0.
|
26
|
-
Requires-Dist: pipestat >=
|
24
|
+
Requires-Dist: pephubclient >=0.1.2
|
25
|
+
Requires-Dist: peppy >=0.40.0.a4
|
26
|
+
Requires-Dist: pipestat >=v0.6.0a9
|
27
27
|
Requires-Dist: pyyaml >=3.12
|
28
28
|
Requires-Dist: rich >=9.10.0
|
29
29
|
Requires-Dist: ubiquerg >=0.5.2
|
30
|
-
Requires-Dist: yacman >=0.9
|
30
|
+
Requires-Dist: yacman >=0.9.2
|
31
31
|
|
32
32
|
# <img src="docs/img/looper_logo.svg" alt="looper logo" height="70">
|
33
33
|
|
@@ -1,19 +1,19 @@
|
|
1
|
-
looper/__init__.py,sha256=
|
2
|
-
looper/__main__.py,sha256=
|
3
|
-
looper/_version.py,sha256=
|
4
|
-
looper/
|
5
|
-
looper/
|
6
|
-
looper/
|
7
|
-
looper/
|
8
|
-
looper/
|
9
|
-
looper/
|
10
|
-
looper/
|
11
|
-
looper/looper.py,sha256=noBb0b2gHawCSyZlx5EF0zdnrm75IjVM_K7c2deORzk,46346
|
1
|
+
looper/__init__.py,sha256=f_z9YY4ibOk7eyWoaViH_VaCXMlPQeiftbnibSFj-3E,1333
|
2
|
+
looper/__main__.py,sha256=8CX2ae8mUQNI_Z8pdBT4i5UFqROFX1awyFnuYCKuYXg,238
|
3
|
+
looper/_version.py,sha256=UG64J_OfEJF5nBy02cxP-9wbnmfesoV9F5GvRWXpSfM,24
|
4
|
+
looper/cli_divvy.py,sha256=J07x83sqC4jJeu3_yS6KOARPWmwKGAV7JvN33T5zDac,5907
|
5
|
+
looper/cli_looper.py,sha256=0SYyPAPg8MEGOnp_vjn0Mxohe7Annr56hwDBX_DqM5U,25769
|
6
|
+
looper/conductor.py,sha256=BmMATwtkHoPSsffYopCQ1WxK83O3UjOTFRrakgD6DzA,30231
|
7
|
+
looper/const.py,sha256=bPj4lTuj2l6gwHROWqj16iHfJFo9ghZAz8THNREWW4U,8558
|
8
|
+
looper/divvy.py,sha256=qa1ebbQTfNupAyDfhfEJ6mbZ_V3zk-D_E-Tck7miJ38,15688
|
9
|
+
looper/exceptions.py,sha256=r6SKKt-m8CXQnXGDnuiwoA6zBJhIZflygBKjX4RCloI,3419
|
10
|
+
looper/looper.py,sha256=f__lSwDgzTcFpv5kUIrR-XdC49_YPvdVZTBfBdkm2J8,30251
|
12
11
|
looper/parser_types.py,sha256=d3FHt54f9jo9VZMr5SQkbghcAdABqiYZW2JBGO5EBnw,2327
|
13
12
|
looper/pipeline_interface.py,sha256=y46tB1_73d1FX8N1w4-GGvRBJ7rqhenuUYVtUfIhK5s,14974
|
14
|
-
looper/
|
15
|
-
looper/
|
16
|
-
looper/
|
13
|
+
looper/plugins.py,sha256=MaMdPmK9U_4FkNJE5kccohBbY1i2qj1NTEucubFOJek,5747
|
14
|
+
looper/processed_project.py,sha256=jZxoMYafvr-OHFxylc5ivGty1VwXBZhl0kgoFkY-174,9837
|
15
|
+
looper/project.py,sha256=Ldsa__wLNnDpKFfN0cOCWQnL-pTuVrPbbHJPHFt9fcU,36196
|
16
|
+
looper/utils.py,sha256=i7srIXPEnQjtNaoP0ziRpdYfB7HNY5_3rW5LoKIM15I,27257
|
17
17
|
looper/default_config/divvy_config.yaml,sha256=wK5kLDGBV2wwoyqg2rl3X8SXjds4x0mwBUjUzF1Ln7g,1705
|
18
18
|
looper/default_config/divvy_templates/localhost_bulker_template.sub,sha256=yn5VB9Brt7Hck9LT17hD2o8Kn-76gYJQk_A-8C1Gr4k,164
|
19
19
|
looper/default_config/divvy_templates/localhost_docker_template.sub,sha256=XRr7AlR7-TP1L3hyBMfka_RgWRL9vzOlS5Kd1xSNwT0,183
|
@@ -55,9 +55,9 @@ looper/schemas/divvy_config_schema.yaml,sha256=7GJfKLc3VX4RGjHnOE1zxwsHXhj_ur9za
|
|
55
55
|
looper/schemas/pipeline_interface_schema_generic.yaml,sha256=D16Rkpj03H9WnvA_N18iNU-hH_HwOuyESJ8Hk5hZSXc,1518
|
56
56
|
looper/schemas/pipeline_interface_schema_project.yaml,sha256=-ZWyA0lKXWik3obuLNVk3IsAZYfbLVbCDvJnD-Fcluo,1567
|
57
57
|
looper/schemas/pipeline_interface_schema_sample.yaml,sha256=x0OwVnijJpvm50DscvvJujdK4UAI7d71pqVemQS-D-0,1564
|
58
|
-
looper-1.
|
59
|
-
looper-1.
|
60
|
-
looper-1.
|
61
|
-
looper-1.
|
62
|
-
looper-1.
|
63
|
-
looper-1.
|
58
|
+
looper-1.6.0a1.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
|
59
|
+
looper-1.6.0a1.dist-info/METADATA,sha256=apZ1c5_AxHNy1MxlLtOxGrXRZzG3tYlvWFzZQKMw3kU,1741
|
60
|
+
looper-1.6.0a1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
61
|
+
looper-1.6.0a1.dist-info/entry_points.txt,sha256=AEL1eb0gPLYvAEUewM35Ng4scXGZIWJK4Mxdj3Hm8Fw,83
|
62
|
+
looper-1.6.0a1.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
|
63
|
+
looper-1.6.0a1.dist-info/RECORD,,
|