4
4
import attr
5
5
import cloudpickle as cp
6
6
from pathlib import Path
7
+ from filelock import SoftFileLock
7
8
import os
8
9
import sys
9
10
from hashlib import sha256
10
11
import subprocess as sp
12
+ import getpass
13
+ import uuid
14
+ from time import strftime
15
+ from traceback import format_exception
11
16
12
- from .specs import Runtime , File , Directory , attr_fields
17
+
18
+ from .specs import Runtime , File , Directory , attr_fields , Result
13
19
from .helpers_file import hash_file , hash_dir , copyfile , is_existing_file
14
20
15
21
@@ -94,7 +100,7 @@ def load_result(checksum, cache_locations):
94
100
return None
95
101
96
102
97
- def save (task_path : Path , result = None , task = None ):
103
+ def save (task_path : Path , result = None , task = None , name_prefix = None ):
98
104
"""
99
105
Save a :class:`~pydra.engine.core.TaskBase` object and/or results.
100
106
@@ -106,20 +112,28 @@ def save(task_path: Path, result=None, task=None):
106
112
Result to pickle and write
107
113
task : :class:`~pydra.engine.core.TaskBase`
108
114
Task to pickle and write
109
-
110
115
"""
116
+
111
117
if task is None and result is None :
112
118
raise ValueError ("Nothing to be saved" )
119
+
120
+ if not isinstance (task_path , Path ):
121
+ task_path = Path (task_path )
113
122
task_path .mkdir (parents = True , exist_ok = True )
114
- if result :
115
- if Path (task_path ).name .startswith ("Workflow" ):
116
- # copy files to the workflow directory
117
- result = copyfile_workflow (wf_path = task_path , result = result )
118
- with (task_path / "_result.pklz" ).open ("wb" ) as fp :
119
- cp .dump (result , fp )
120
- if task :
121
- with (task_path / "_task.pklz" ).open ("wb" ) as fp :
122
- cp .dump (task , fp )
123
+ if name_prefix is None :
124
+ name_prefix = ""
125
+
126
+ lockfile = task_path .parent / (task_path .name + "_save.lock" )
127
+ with SoftFileLock (lockfile ):
128
+ if result :
129
+ if task_path .name .startswith ("Workflow" ):
130
+ # copy files to the workflow directory
131
+ result = copyfile_workflow (wf_path = task_path , result = result )
132
+ with (task_path / f"{ name_prefix } _result.pklz" ).open ("wb" ) as fp :
133
+ cp .dump (result , fp )
134
+ if task :
135
+ with (task_path / f"{ name_prefix } _task.pklz" ).open ("wb" ) as fp :
136
+ cp .dump (task , fp )
123
137
124
138
125
139
def copyfile_workflow (wf_path , result ):
@@ -221,7 +235,7 @@ def make_klass(spec):
221
235
if isinstance (item [1 ], attr ._make ._CountingAttr ):
222
236
newfields [item [0 ]] = item [1 ]
223
237
else :
224
- newfields [item [0 ]] = attr .ib (repr = False , type = item [1 ])
238
+ newfields [item [0 ]] = attr .ib (type = item [1 ])
225
239
else :
226
240
if (
227
241
any ([isinstance (ii , attr ._make ._CountingAttr ) for ii in item ])
@@ -369,8 +383,33 @@ def create_checksum(name, inputs):
369
383
370
384
def record_error (error_path , error ):
371
385
"""Write an error file."""
386
+
387
+ error_message = str (error )
388
+
389
+ resultfile = error_path / "_result.pklz"
390
+ if not resultfile .exists ():
391
+ error_message += """\n
392
+ When creating this error file, the results file corresponding
393
+ to the task could not be found."""
394
+
395
+ name_checksum = str (error_path .name )
396
+ timeofcrash = strftime ("%Y%m%d-%H%M%S" )
397
+ try :
398
+ login_name = getpass .getuser ()
399
+ except KeyError :
400
+ login_name = "UID{:d}" .format (os .getuid ())
401
+
402
+ full_error = {
403
+ "time of crash" : timeofcrash ,
404
+ "login name" : login_name ,
405
+ "name with checksum" : name_checksum ,
406
+ "error message" : error ,
407
+ }
408
+
372
409
with (error_path / "_error.pklz" ).open ("wb" ) as fp :
373
- cp .dump (error , fp )
410
+ cp .dump (full_error , fp )
411
+
412
+ return error_path / "_error.pklz"
374
413
375
414
376
415
def get_open_loop ():
@@ -397,49 +436,6 @@ def get_open_loop():
397
436
return loop
398
437
399
438
400
- def create_pyscript (script_path , checksum , rerun = False ):
401
- """
402
- Create standalone script for task execution in a different environment.
403
-
404
- Parameters
405
- ----------
406
- script_path : :obj:`os.pathlike`
407
- Path to the script.
408
- checksum : str
409
- Task checksum.
410
-
411
- Returns
412
- -------
413
- pyscript : :obj:`File`
414
- Execution script
415
-
416
- """
417
- task_pkl = script_path / "_task.pklz"
418
- if not task_pkl .exists () or not task_pkl .stat ().st_size :
419
- raise Exception ("Missing or empty task!" )
420
-
421
- content = f"""import cloudpickle as cp
422
- from pathlib import Path
423
-
424
-
425
- cache_path = Path("{ str (script_path )} ")
426
- task_pkl = (cache_path / "_task.pklz")
427
- task = cp.loads(task_pkl.read_bytes())
428
-
429
- # submit task
430
- task(rerun={ rerun } )
431
-
432
- if not task.result():
433
- raise Exception("Something went wrong")
434
- print("Completed", task.checksum, task)
435
- task_pkl.unlink()
436
- """
437
- pyscript = script_path / f"pyscript_{ checksum } .py"
438
- with pyscript .open ("wt" ) as fp :
439
- fp .writelines (content )
440
- return pyscript
441
-
442
-
443
439
def hash_function (obj ):
444
440
"""Generate hash of object."""
445
441
return sha256 (str (obj ).encode ()).hexdigest ()
@@ -544,3 +540,61 @@ def get_available_cpus():
544
540
545
541
# Last resort
546
542
return os .cpu_count ()
543
+
544
+
545
+ def load_and_run (
546
+ task_pkl , ind = None , rerun = False , submitter = None , plugin = None , ** kwargs
547
+ ):
548
+ """
549
+ loading a task from a pickle file, settings proper input
550
+ and running the task
551
+ """
552
+ try :
553
+ task = load_task (task_pkl = task_pkl , ind = ind )
554
+ except Exception as excinfo :
555
+ if task_pkl .parent .exists ():
556
+ etype , eval , etr = sys .exc_info ()
557
+ traceback = format_exception (etype , eval , etr )
558
+ errorfile = record_error (task_pkl .parent , error = traceback )
559
+ result = Result (output = None , runtime = None , errored = True )
560
+ save (task_pkl .parent , result = result )
561
+ raise
562
+
563
+ resultfile = task .output_dir / "_result.pklz"
564
+ try :
565
+ task (rerun = rerun , plugin = plugin , submitter = submitter , ** kwargs )
566
+ except Exception as excinfo :
567
+ # creating result and error files if missing
568
+ errorfile = task .output_dir / "_error.pklz"
569
+ if not resultfile .exists ():
570
+ etype , eval , etr = sys .exc_info ()
571
+ traceback = format_exception (etype , eval , etr )
572
+ errorfile = record_error (task .output_dir , error = traceback )
573
+ result = Result (output = None , runtime = None , errored = True )
574
+ save (task .output_dir , result = result )
575
+ raise type (excinfo )(
576
+ str (excinfo .with_traceback (None )),
577
+ f" full crash report is here: { errorfile } " ,
578
+ )
579
+ return resultfile
580
+
581
+
582
+ async def load_and_run_async (task_pkl , ind = None , submitter = None , rerun = False , ** kwargs ):
583
+ """
584
+ loading a task from a pickle file, settings proper input
585
+ and running the workflow
586
+ """
587
+ task = load_task (task_pkl = task_pkl , ind = ind )
588
+ await task ._run (submitter = submitter , rerun = rerun , ** kwargs )
589
+
590
+
591
+ def load_task (task_pkl , ind = None ):
592
+ """ loading a task from a pickle file, settings proper input for the specific ind"""
593
+ if isinstance (task_pkl , str ):
594
+ task_pkl = Path (task_pkl )
595
+ task = cp .loads (task_pkl .read_bytes ())
596
+ if ind is not None :
597
+ _ , inputs_dict = task .get_input_el (ind )
598
+ task .inputs = attr .evolve (task .inputs , ** inputs_dict )
599
+ task .state = None
600
+ return task
0 commit comments