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/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.0
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.0
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=95w7PAAZNB0joKSDxBtTle_4D0e-MKSds4P7KxtDGSc,17694
2
- looper/__main__.py,sha256=bHyvKMsHY0CIubHX7kHxJE1E9bQamu27kqF_puTFTyQ,230
3
- looper/_version.py,sha256=X9pTeGHIYZtDOmfb9pnn2DEqWZMAyaPlqdFwEhsuSvk,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=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/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.0.dist-info/LICENSE.txt,sha256=oB6ZGDa4kcznznJKJsLLFFcOZyi8Y6e2Jv0rJozgp-I,1269
59
- looper-1.5.0.dist-info/METADATA,sha256=ntxJtLZVlA5TbM0do1D3Eu0USBjydhas7quM74RTKMM,1723
60
- looper-1.5.0.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
61
- looper-1.5.0.dist-info/entry_points.txt,sha256=AEL1eb0gPLYvAEUewM35Ng4scXGZIWJK4Mxdj3Hm8Fw,83
62
- looper-1.5.0.dist-info/top_level.txt,sha256=I0Yf7djsoQAMzwHBbDiQi9hGtq4Z41_Ma5CX8qXG8Y8,7
63
- looper-1.5.0.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