looper 1.7.1__py3-none-any.whl → 1.8.0__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/__main__.py +1 -1
- looper/_version.py +1 -1
- looper/cli_pydantic.py +385 -0
- looper/command_models/DEVELOPER.md +85 -0
- looper/command_models/README.md +4 -0
- looper/command_models/__init__.py +6 -0
- looper/command_models/arguments.py +283 -0
- looper/command_models/commands.py +332 -0
- looper/conductor.py +32 -18
- looper/const.py +8 -0
- looper/divvy.py +33 -35
- looper/looper.py +161 -166
- looper/project.py +150 -111
- looper/utils.py +82 -30
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/METADATA +6 -5
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/RECORD +20 -15
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/entry_points.txt +1 -1
- looper/cli_looper.py +0 -796
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/LICENSE.txt +0 -0
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/WHEEL +0 -0
- {looper-1.7.1.dist-info → looper-1.8.0.dist-info}/top_level.txt +0 -0
looper/conductor.py
CHANGED
@@ -21,12 +21,13 @@ from peppy.exceptions import RemoteYAMLError
|
|
21
21
|
from pipestat import PipestatError
|
22
22
|
from ubiquerg import expandpath, is_command_callable
|
23
23
|
from yaml import dump
|
24
|
-
from yacman import YAMLConfigManager
|
24
|
+
from yacman import FutureYAMLConfigManager as YAMLConfigManager
|
25
25
|
|
26
26
|
from .const import *
|
27
27
|
from .exceptions import JobSubmissionException, SampleFailedException
|
28
28
|
from .processed_project import populate_sample_paths
|
29
29
|
from .utils import fetch_sample_flags, jinja_render_template_strictly
|
30
|
+
from .const import PipelineLevel
|
30
31
|
|
31
32
|
|
32
33
|
_LOGGER = logging.getLogger(__name__)
|
@@ -85,11 +86,23 @@ def _get_yaml_path(namespaces, template_key, default_name_appendix="", filename=
|
|
85
86
|
|
86
87
|
def write_pipestat_config(looper_pipestat_config_path, pipestat_config_dict):
|
87
88
|
"""
|
88
|
-
This
|
89
|
+
This writes a combined configuration file to be passed to a PipestatManager.
|
90
|
+
:param str looper_pipestat_config_path: path to the created pipestat configuration file
|
91
|
+
:param dict pipestat_config_dict: the dict containing key value pairs to be written to the pipestat configutation
|
92
|
+
return bool
|
89
93
|
"""
|
94
|
+
|
95
|
+
if not os.path.exists(os.path.dirname(looper_pipestat_config_path)):
|
96
|
+
try:
|
97
|
+
os.makedirs(os.path.dirname(looper_pipestat_config_path))
|
98
|
+
except FileExistsError:
|
99
|
+
pass
|
100
|
+
|
90
101
|
with open(looper_pipestat_config_path, "w") as f:
|
91
102
|
yaml.dump(pipestat_config_dict, f)
|
92
|
-
|
103
|
+
_LOGGER.debug(
|
104
|
+
msg=f"Initialized pipestat config file: {looper_pipestat_config_path}"
|
105
|
+
)
|
93
106
|
|
94
107
|
return True
|
95
108
|
|
@@ -261,8 +274,12 @@ class SubmissionConductor(object):
|
|
261
274
|
|
262
275
|
:param bool frorce: whether to force the project submission (ignore status/flags)
|
263
276
|
"""
|
277
|
+
psms = {}
|
264
278
|
if self.prj.pipestat_configured_project:
|
265
|
-
|
279
|
+
for piface in self.prj.project_pipeline_interfaces:
|
280
|
+
if piface.psm.pipeline_type == PipelineLevel.PROJECT.value:
|
281
|
+
psms[piface.psm.pipeline_name] = piface.psm
|
282
|
+
psm = psms[self.pl_name]
|
266
283
|
status = psm.get_status()
|
267
284
|
if not force and status is not None:
|
268
285
|
_LOGGER.info(f"> Skipping project. Determined status: {status}")
|
@@ -288,12 +305,11 @@ class SubmissionConductor(object):
|
|
288
305
|
)
|
289
306
|
)
|
290
307
|
if self.prj.pipestat_configured:
|
291
|
-
|
292
|
-
sample_statuses = psms[self.pl_name].get_status(
|
308
|
+
sample_statuses = self.pl_iface.psm.get_status(
|
293
309
|
record_identifier=sample.sample_name
|
294
310
|
)
|
295
311
|
if sample_statuses == "failed" and rerun is True:
|
296
|
-
|
312
|
+
self.pl_iface.psm.set_status(
|
297
313
|
record_identifier=sample.sample_name, status_identifier="waiting"
|
298
314
|
)
|
299
315
|
sample_statuses = "waiting"
|
@@ -303,23 +319,27 @@ class SubmissionConductor(object):
|
|
303
319
|
|
304
320
|
use_this_sample = True # default to running this sample
|
305
321
|
msg = None
|
322
|
+
if rerun and sample_statuses == []:
|
323
|
+
msg = f"> Skipping sample because rerun requested, but no failed or waiting flag found."
|
324
|
+
use_this_sample = False
|
306
325
|
if sample_statuses:
|
307
326
|
status_str = ", ".join(sample_statuses)
|
308
327
|
failed_flag = any("failed" in x for x in sample_statuses)
|
328
|
+
waiting_flag = any("waiting" in x for x in sample_statuses)
|
309
329
|
if self.ignore_flags:
|
310
330
|
msg = f"> Found existing status: {status_str}. Ignoring."
|
311
331
|
else: # this pipeline already has a status
|
312
332
|
msg = f"> Found existing status: {status_str}. Skipping sample."
|
313
|
-
if failed_flag:
|
333
|
+
if failed_flag and not rerun:
|
314
334
|
msg += " Use rerun to ignore failed status." # help guidance
|
315
335
|
use_this_sample = False
|
316
336
|
if rerun:
|
317
337
|
# Rescue the sample if rerun requested, and failed flag is found
|
318
|
-
if failed_flag:
|
319
|
-
msg = f"> Re-running
|
338
|
+
if failed_flag or waiting_flag:
|
339
|
+
msg = f"> Re-running sample. Status: {status_str}"
|
320
340
|
use_this_sample = True
|
321
341
|
else:
|
322
|
-
msg = f"> Skipping sample because rerun requested, but no failed flag found. Status: {status_str}"
|
342
|
+
msg = f"> Skipping sample because rerun requested, but no failed or waiting flag found. Status: {status_str}"
|
323
343
|
use_this_sample = False
|
324
344
|
if msg:
|
325
345
|
_LOGGER.info(msg)
|
@@ -528,12 +548,7 @@ class SubmissionConductor(object):
|
|
528
548
|
:return yacman.YAMLConfigManager: pipestat namespace
|
529
549
|
"""
|
530
550
|
try:
|
531
|
-
|
532
|
-
self.prj.get_pipestat_managers(sample_name)
|
533
|
-
if sample_name
|
534
|
-
else self.prj.get_pipestat_managers(project_level=True)
|
535
|
-
)
|
536
|
-
psm = psms[self.pl_iface.pipeline_name]
|
551
|
+
psm = self.pl_iface.psm
|
537
552
|
except (PipestatError, AttributeError) as e:
|
538
553
|
# pipestat section faulty or not found in project.looper or sample
|
539
554
|
# or project is missing required pipestat attributes
|
@@ -621,7 +636,6 @@ class SubmissionConductor(object):
|
|
621
636
|
argstring = jinja_render_template_strictly(
|
622
637
|
template=templ, namespaces=namespaces
|
623
638
|
)
|
624
|
-
print(argstring)
|
625
639
|
except UndefinedError as jinja_exception:
|
626
640
|
_LOGGER.warning(NOT_SUB_MSG.format(str(jinja_exception)))
|
627
641
|
except KeyError as e:
|
looper/const.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
""" Shared project constants """
|
2
2
|
|
3
3
|
import os
|
4
|
+
from enum import Enum
|
4
5
|
|
5
6
|
__author__ = "Databio lab"
|
6
7
|
__email__ = "nathan@code.databio.org"
|
@@ -268,3 +269,10 @@ MESSAGE_BY_SUBCOMMAND = {
|
|
268
269
|
"init-piface": "Initialize generic pipeline interface.",
|
269
270
|
"link": "Create directory of symlinks for reported results.",
|
270
271
|
}
|
272
|
+
|
273
|
+
# Add project/sample enum
|
274
|
+
|
275
|
+
|
276
|
+
class PipelineLevel(Enum):
|
277
|
+
SAMPLE = "sample"
|
278
|
+
PROJECT = "project"
|
looper/divvy.py
CHANGED
@@ -6,11 +6,14 @@ import os
|
|
6
6
|
import sys
|
7
7
|
import shutil
|
8
8
|
import yaml
|
9
|
-
from yaml import SafeLoader
|
10
|
-
from shutil import copytree
|
11
9
|
|
10
|
+
|
11
|
+
from shutil import copytree
|
12
|
+
from yacman import FutureYAMLConfigManager as YAMLConfigManager
|
13
|
+
from yacman import write_lock, FILEPATH_KEY, load_yaml, select_config
|
14
|
+
from yaml import SafeLoader
|
12
15
|
from ubiquerg import is_writable, VersionInHelpParser
|
13
|
-
|
16
|
+
|
14
17
|
|
15
18
|
from .const import (
|
16
19
|
COMPUTE_SETTINGS_VARNAME,
|
@@ -28,7 +31,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
28
31
|
# This is the divvy.py submodule from divvy
|
29
32
|
|
30
33
|
|
31
|
-
class ComputingConfiguration(
|
34
|
+
class ComputingConfiguration(YAMLConfigManager):
|
32
35
|
"""
|
33
36
|
Represents computing configuration objects.
|
34
37
|
|
@@ -44,36 +47,31 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
44
47
|
`DIVCFG` file)
|
45
48
|
"""
|
46
49
|
|
47
|
-
def __init__(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
validate_on_write
|
50
|
+
def __init__(
|
51
|
+
self,
|
52
|
+
entries=None,
|
53
|
+
wait_max=None,
|
54
|
+
strict_ro_locks=False,
|
55
|
+
schema_source=None,
|
56
|
+
validate_on_write=False,
|
57
|
+
):
|
58
|
+
super().__init__(
|
59
|
+
entries, wait_max, strict_ro_locks, schema_source, validate_on_write
|
57
60
|
)
|
58
61
|
|
59
|
-
if
|
60
|
-
raise Exception(
|
61
|
-
"Your divvy config file is not in divvy config format "
|
62
|
-
"(it lacks a compute_packages section): '{}'".format(filepath)
|
63
|
-
)
|
64
|
-
# We require that compute_packages be present, even if empty
|
62
|
+
if "compute_packages" not in self:
|
65
63
|
self["compute_packages"] = {}
|
66
|
-
|
67
64
|
# Initialize default compute settings.
|
68
65
|
_LOGGER.debug("Establishing project compute settings")
|
69
66
|
self.compute = None
|
70
67
|
self.setdefault("adapters", None)
|
71
68
|
self.activate_package(DEFAULT_COMPUTE_RESOURCES_NAME)
|
72
|
-
self.config_file = self.filepath
|
73
69
|
|
74
70
|
def write(self, filename=None):
|
75
|
-
|
76
|
-
|
71
|
+
with write_lock(self) as locked_ym:
|
72
|
+
locked_ym.rebase()
|
73
|
+
locked_ym.write()
|
74
|
+
filename = filename or getattr(self, FILEPATH_KEY)
|
77
75
|
filedir = os.path.dirname(filename)
|
78
76
|
# For this object, we *also* have to write the template files
|
79
77
|
for pkg_name, pkg in self["compute_packages"].items():
|
@@ -151,12 +149,12 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
151
149
|
# Augment compute, creating it if needed.
|
152
150
|
if self.compute is None:
|
153
151
|
_LOGGER.debug("Creating Project compute")
|
154
|
-
self.compute =
|
152
|
+
self.compute = YAMLConfigManager()
|
155
153
|
_LOGGER.debug(
|
156
154
|
"Adding entries for package_name '{}'".format(package_name)
|
157
155
|
)
|
158
156
|
|
159
|
-
self.compute.
|
157
|
+
self.compute.update_from_obj(self["compute_packages"][package_name])
|
160
158
|
|
161
159
|
# Ensure submission template is absolute. This *used to be* handled
|
162
160
|
# at update (so the paths were stored as absolutes in the packages),
|
@@ -165,7 +163,7 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
165
163
|
if not os.path.isabs(self.compute["submission_template"]):
|
166
164
|
try:
|
167
165
|
self.compute["submission_template"] = os.path.join(
|
168
|
-
os.path.dirname(self.
|
166
|
+
os.path.dirname(self.default_config_file),
|
169
167
|
self.compute["submission_template"],
|
170
168
|
)
|
171
169
|
except AttributeError as e:
|
@@ -200,11 +198,11 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
200
198
|
self.reset_active_settings()
|
201
199
|
return self.activate_package(package_name)
|
202
200
|
|
203
|
-
def get_active_package(self):
|
201
|
+
def get_active_package(self) -> YAMLConfigManager:
|
204
202
|
"""
|
205
203
|
Returns settings for the currently active compute package
|
206
204
|
|
207
|
-
:return
|
205
|
+
:return YAMLConfigManager: data defining the active compute package
|
208
206
|
"""
|
209
207
|
return self.compute
|
210
208
|
|
@@ -222,7 +220,7 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
222
220
|
|
223
221
|
:return bool: success flag
|
224
222
|
"""
|
225
|
-
self.compute =
|
223
|
+
self.compute = YAMLConfigManager()
|
226
224
|
return True
|
227
225
|
|
228
226
|
def update_packages(self, config_file):
|
@@ -235,11 +233,11 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
235
233
|
|
236
234
|
:param str config_file: path to file with new divvy configuration data
|
237
235
|
"""
|
238
|
-
entries =
|
236
|
+
entries = load_yaml(config_file)
|
239
237
|
self.update(entries)
|
240
238
|
return True
|
241
239
|
|
242
|
-
def get_adapters(self):
|
240
|
+
def get_adapters(self) -> YAMLConfigManager:
|
243
241
|
"""
|
244
242
|
Get current adapters, if defined.
|
245
243
|
|
@@ -248,9 +246,9 @@ class ComputingConfiguration(yacman.YAMLConfigManager):
|
|
248
246
|
package-specific set of adapters, if any defined in 'adapters' section
|
249
247
|
under currently active compute package.
|
250
248
|
|
251
|
-
:return
|
249
|
+
:return YAMLConfigManager: current adapters mapping
|
252
250
|
"""
|
253
|
-
adapters =
|
251
|
+
adapters = YAMLConfigManager()
|
254
252
|
if "adapters" in self and self["adapters"] is not None:
|
255
253
|
adapters.update(self["adapters"])
|
256
254
|
if "compute" in self and "adapters" in self.compute:
|
@@ -376,7 +374,7 @@ def select_divvy_config(filepath):
|
|
376
374
|
:param str | NoneType filepath: direct file path specification
|
377
375
|
:return str: path to the config file to read
|
378
376
|
"""
|
379
|
-
divcfg =
|
377
|
+
divcfg = select_config(
|
380
378
|
config_filepath=filepath,
|
381
379
|
config_env_vars=COMPUTE_SETTINGS_VARNAME,
|
382
380
|
default_config_filepath=DEFAULT_CONFIG_FILEPATH,
|