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/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
- # Determine Generic Pipeline Interface
361
+ # Create Generic Pipeline Interface
339
362
  generic_pipeline_dict = {
340
- "pipeline_name": "count_lines",
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}/count_lines.sh"},
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"Generic pipeline interface successfully created at: {dest_file}")
375
+ print(f"Pipeline interface successfully created at: {dest_file}")
353
376
  else:
354
377
  print(
355
- f"Generic pipeline interface file already exists `{dest_file}`. Skipping creation.."
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 init_dotfile(
362
- path: str,
363
- cfg_path: str = None,
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 dotfile
437
+ Initialize looper config file
371
438
 
372
- :param str path: absolute path to the file to initialize
373
- :param str cfg_path: path to the config file. Absolute or relative to 'path'
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(path) and not force:
381
- print("Can't initialize, file exists: {}".format(path))
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
- if cfg_path:
384
- if is_registry_path(cfg_path):
450
+
451
+ if pep_path:
452
+ if is_registry_path(pep_path):
385
453
  pass
386
454
  else:
387
- cfg_path = expandpath(cfg_path)
388
- if not os.path.isabs(cfg_path):
389
- cfg_path = os.path.join(os.path.dirname(path), cfg_path)
390
- assert os.path.exists(cfg_path), OSError(
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: {}".format(
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
- cfg_path = "example/pep/path"
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(cfg_path, os.path.dirname(path)),
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(path, "w") as dotfile:
477
+ with open(looper_config_path, "w") as dotfile:
412
478
  yaml.dump(looper_config_dict, dotfile)
413
- print("Initialized looper dotfile: {}".format(path))
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
- if input_string.endswith(".yaml"):
514
- return False
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.5.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.0
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.35.4
26
- Requires-Dist: pipestat >=0.5.1
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=XGDsUX7q8eKekQcTs7ji_5Heubx7kri-dArEPGDROSs,17694
2
- looper/__main__.py,sha256=bHyvKMsHY0CIubHX7kHxJE1E9bQamu27kqF_puTFTyQ,230
3
- looper/_version.py,sha256=8rmxh34XkVT7yu5zgYupipfDrKs2hgGCjOMEIK3E4oI,22
4
- looper/conductor.py,sha256=I7rYEH08B2cUdHbLwtCElSPP2tomWQZxVZhuAqkrU08,35680
5
- looper/const.py,sha256=Almb7Cy_zRhH1Pb02cj8mFVLy8V1xzJst3LfkEyjE6c,7005
6
- looper/divvy.py,sha256=HcQ7p_Wk78jHzWgjM9saZpO8sBteA4EFBeUJYBr5zVw,21185
7
- looper/exceptions.py,sha256=QshRRtBkJ088n09iXn1G8SHfSNSDaGcqt4eISqYX_qg,2880
8
- looper/html_reports.py,sha256=lRsRIbk18otAb1sVcIsS9RD2vJwrv32GyEDRYSp5L8s,43820
9
- looper/html_reports_pipestat.py,sha256=TEWCMdLliP7WzzanwopSoIhTEvThbDJfuCT8n5P5Ij4,35130
10
- looper/html_reports_project_pipestat.py,sha256=U4AdyGrnnDnbKGFk-oh3A9rCjyNj43ZwppiZtXX92S0,10224
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/processed_project.py,sha256=wZGtjmbx3qdMHqI56xyrZc-lmMsWv3mlRkjYYIL7W3o,9867
15
- looper/project.py,sha256=FjSfqaYPzjGmIgPPi6x--ymXhXrzDOI4BvJgjvrkKqA,31760
16
- looper/utils.py,sha256=N-eF0j3RFuGWjr7PRfYuVEvlyv4Nm6zqcpmCiUK-TlU,24519
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.5.1.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
59
- looper-1.5.1.dist-info/METADATA,sha256=bJ8Sgx37O3lxhAUWqNgz3ag1YO4ycs-m9QXaT3HB_Jw,1723
60
- looper-1.5.1.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
61
- looper-1.5.1.dist-info/entry_points.txt,sha256=AEL1eb0gPLYvAEUewM35Ng4scXGZIWJK4Mxdj3Hm8Fw,83
62
- looper-1.5.1.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
63
- looper-1.5.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.1)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5