looper 1.5.1__py3-none-any.whl → 1.6.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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.1.dist-info → looper-1.6.0a1.dist-info}/METADATA +6 -6
- {looper-1.5.1.dist-info → looper-1.6.0a1.dist-info}/RECORD +20 -20
- {looper-1.5.1.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.1.dist-info → looper-1.6.0a1.dist-info}/LICENSE.txt +0 -0
- {looper-1.5.1.dist-info → looper-1.6.0a1.dist-info}/entry_points.txt +0 -0
- {looper-1.5.1.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=e87oiGaWjXTlCZoXolCGy5eV8MS0nKl6MhfzHQJEWz4,46350
|
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,,
|