executorlib 1.10.2__py3-none-any.whl → 1.10.4__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.
- executorlib/__init__.py +29 -0
- executorlib/_version.py +2 -2
- executorlib/standalone/hdf.py +14 -10
- executorlib/standalone/interactive/communication.py +4 -2
- executorlib/standalone/queue.py +17 -0
- executorlib/standalone/select.py +2 -1
- executorlib/standalone/serialize.py +16 -11
- executorlib/task_scheduler/file/shared.py +159 -113
- executorlib/task_scheduler/file/spawner_pysqa.py +56 -0
- executorlib/task_scheduler/interactive/blockallocation.py +33 -23
- executorlib/task_scheduler/interactive/shared.py +33 -22
- {executorlib-1.10.2.dist-info → executorlib-1.10.4.dist-info}/METADATA +10 -10
- {executorlib-1.10.2.dist-info → executorlib-1.10.4.dist-info}/RECORD +15 -15
- {executorlib-1.10.2.dist-info → executorlib-1.10.4.dist-info}/WHEEL +1 -1
- {executorlib-1.10.2.dist-info → executorlib-1.10.4.dist-info}/licenses/LICENSE +0 -0
executorlib/__init__.py
CHANGED
|
@@ -44,6 +44,35 @@ def get_cache_data(cache_directory: str) -> list[dict]:
|
|
|
44
44
|
return get_cache_data(cache_directory=cache_directory)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def get_cache_data_queue(
|
|
48
|
+
cache_directory: str,
|
|
49
|
+
queue_type: Optional[str] = None,
|
|
50
|
+
config_directory: Optional[str] = None,
|
|
51
|
+
) -> list[dict]:
|
|
52
|
+
"""
|
|
53
|
+
Collect all HDF5 files in the cache directory and check their status in the queue system
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
cache_directory (str): The directory to store cache files.
|
|
57
|
+
queue_type (str, optional): The type of the queue system ["slurm", "flux"].
|
|
58
|
+
config_directory (str, optional): The directory containing the configuration for the queue system.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
list[dict]: List of dictionaries each representing on of the HDF5 files in the
|
|
62
|
+
cache directory with their status in the queue system.
|
|
63
|
+
"""
|
|
64
|
+
from executorlib.standalone.hdf import get_cache_data
|
|
65
|
+
from executorlib.task_scheduler.file.spawner_pysqa import (
|
|
66
|
+
get_queue_system_cache_data,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return get_queue_system_cache_data(
|
|
70
|
+
cache_dict=get_cache_data(cache_directory=cache_directory),
|
|
71
|
+
queue_type=queue_type,
|
|
72
|
+
config_directory=config_directory,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
47
76
|
def get_future_from_cache(
|
|
48
77
|
cache_directory: str,
|
|
49
78
|
cache_key: str,
|
executorlib/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.10.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 10,
|
|
21
|
+
__version__ = version = '1.10.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 10, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
executorlib/standalone/hdf.py
CHANGED
|
@@ -31,18 +31,22 @@ def dump(file_name: Optional[str], data_dict: dict) -> None:
|
|
|
31
31
|
"""
|
|
32
32
|
if file_name is not None:
|
|
33
33
|
file_name_abs = os.path.abspath(file_name)
|
|
34
|
+
# Serialize all values before opening the file, so a serialization error does
|
|
35
|
+
# not leave an incomplete HDF5 file behind.
|
|
36
|
+
binary_dict = {
|
|
37
|
+
group_dict[data_key]: cloudpickle.dumps(data_value)
|
|
38
|
+
for data_key, data_value in data_dict.items()
|
|
39
|
+
if data_key in group_dict
|
|
40
|
+
}
|
|
34
41
|
os.makedirs(os.path.dirname(file_name_abs), exist_ok=True)
|
|
35
42
|
with h5py.File(file_name_abs, "a") as fname:
|
|
36
|
-
for
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
),
|
|
44
|
-
compression="gzip",
|
|
45
|
-
)
|
|
43
|
+
for group_name, binary in binary_dict.items():
|
|
44
|
+
with contextlib.suppress(ValueError):
|
|
45
|
+
fname.create_dataset(
|
|
46
|
+
name="/" + group_name,
|
|
47
|
+
data=np.frombuffer(binary, dtype=np.uint8),
|
|
48
|
+
compression="gzip",
|
|
49
|
+
)
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def load(file_name: str) -> dict:
|
|
@@ -150,9 +150,11 @@ class SocketInterface:
|
|
|
150
150
|
"""
|
|
151
151
|
result = None
|
|
152
152
|
if self._spawner.poll():
|
|
153
|
-
|
|
153
|
+
output = self.send_and_receive_dict(
|
|
154
154
|
input_dict={"shutdown": True, "wait": wait}
|
|
155
|
-
)
|
|
155
|
+
)
|
|
156
|
+
if "result" in output:
|
|
157
|
+
result = output["result"]
|
|
156
158
|
self._spawner.shutdown(wait=wait)
|
|
157
159
|
self._reset_socket()
|
|
158
160
|
return result
|
executorlib/standalone/queue.py
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import queue
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def put_front(que: queue.Queue, item: Any):
|
|
6
|
+
"""
|
|
7
|
+
Insert an item at the front of the queue, ahead of any items already waiting, and wake a consumer blocked in
|
|
8
|
+
get(). Equivalent to queue.Queue.put() except the item is placed at the front rather than the back; mutating
|
|
9
|
+
que.queue directly instead would silently skip the notification, leaving a waiting consumer asleep forever.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
que (queue.Queue): Queue with task objects which should be executed
|
|
13
|
+
item (Any): item to place at the front of the queue
|
|
14
|
+
"""
|
|
15
|
+
with que.not_full:
|
|
16
|
+
que.queue.insert(0, item)
|
|
17
|
+
que.unfinished_tasks += 1
|
|
18
|
+
que.not_empty.notify()
|
|
2
19
|
|
|
3
20
|
|
|
4
21
|
def cancel_items_in_queue(que: queue.Queue):
|
executorlib/standalone/select.py
CHANGED
|
@@ -21,9 +21,10 @@ class FutureSelector(Future):
|
|
|
21
21
|
future (Future): The underlying future whose result is a collection.
|
|
22
22
|
selector (int | str): Index or key used to extract an element from the result.
|
|
23
23
|
"""
|
|
24
|
+
# Future.__init__() is not called, as its attributes would be forwarded to the
|
|
25
|
+
# wrapped future by __setattr__() and reset the state of an already completed future.
|
|
24
26
|
self._future = future
|
|
25
27
|
self._selector = selector
|
|
26
|
-
super().__init__()
|
|
27
28
|
|
|
28
29
|
def __getattr__(self, attr: str) -> Any:
|
|
29
30
|
"""Delegate attribute access to the wrapped future."""
|
|
@@ -34,7 +34,7 @@ def serialize_funct(
|
|
|
34
34
|
fn_kwargs: Optional[dict] = None,
|
|
35
35
|
resource_dict: Optional[dict] = None,
|
|
36
36
|
cache_key: Optional[str] = None,
|
|
37
|
-
) -> tuple[str, dict]:
|
|
37
|
+
) -> tuple[str, dict, Optional[Exception]]:
|
|
38
38
|
"""
|
|
39
39
|
Serialize a function and its arguments and keyword arguments into an HDF5 file.
|
|
40
40
|
|
|
@@ -56,7 +56,8 @@ def serialize_funct(
|
|
|
56
56
|
overwritten by setting the cache_key.
|
|
57
57
|
|
|
58
58
|
Returns:
|
|
59
|
-
Tuple[str, dict]: A tuple containing the task key
|
|
59
|
+
Tuple[str, dict, Optional[Exception]]: A tuple containing the task key, the serialized data and an
|
|
60
|
+
exception if the serialization failed.
|
|
60
61
|
|
|
61
62
|
"""
|
|
62
63
|
if fn_args is None:
|
|
@@ -68,21 +69,25 @@ def serialize_funct(
|
|
|
68
69
|
if cache_key is not None:
|
|
69
70
|
task_key = cache_key
|
|
70
71
|
else:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
try:
|
|
73
|
+
binary_all = cloudpickle.dumps(
|
|
74
|
+
{
|
|
75
|
+
"fn": fn,
|
|
76
|
+
"args": fn_args,
|
|
77
|
+
"kwargs": fn_kwargs,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return "", {}, e
|
|
82
|
+
else:
|
|
83
|
+
task_key = _get_function_name(fn=fn) + _get_hash(binary=binary_all)
|
|
79
84
|
data = {
|
|
80
85
|
"fn": fn,
|
|
81
86
|
"args": fn_args,
|
|
82
87
|
"kwargs": fn_kwargs,
|
|
83
88
|
"resource_dict": resource_dict,
|
|
84
89
|
}
|
|
85
|
-
return task_key, data
|
|
90
|
+
return task_key, data, None
|
|
86
91
|
|
|
87
92
|
|
|
88
93
|
def _get_hash(binary: bytes) -> str:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import os
|
|
3
3
|
import queue
|
|
4
|
-
from concurrent.futures import Future
|
|
4
|
+
from concurrent.futures import CancelledError, Future
|
|
5
5
|
from time import sleep
|
|
6
6
|
from typing import Any, Callable, Optional
|
|
7
7
|
|
|
@@ -118,75 +118,93 @@ def execute_tasks_h5(
|
|
|
118
118
|
future_queue.join()
|
|
119
119
|
break
|
|
120
120
|
elif task_dict is not None:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_get_task_input(
|
|
128
|
-
task_resource_dict=task_dict["resource_dict"].copy(),
|
|
129
|
-
executor_kwargs=executor_kwargs,
|
|
121
|
+
try:
|
|
122
|
+
task_args, task_kwargs, future_wait_key_lst = _convert_args_and_kwargs(
|
|
123
|
+
task_dict=task_dict,
|
|
124
|
+
memory_dict=memory_dict,
|
|
125
|
+
file_name_dict=file_name_dict,
|
|
126
|
+
duplicate_dict=duplicate_dict,
|
|
130
127
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
128
|
+
task_resource_dict, cache_key, cache_directory, error_log_file = (
|
|
129
|
+
_get_task_input(
|
|
130
|
+
task_resource_dict=task_dict["resource_dict"].copy(),
|
|
131
|
+
executor_kwargs=executor_kwargs,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
task_key, data_dict, serialize_exception = serialize_funct(
|
|
135
|
+
fn=task_dict["fn"],
|
|
136
|
+
fn_args=task_args,
|
|
137
|
+
fn_kwargs=task_kwargs,
|
|
138
|
+
resource_dict=task_resource_dict,
|
|
139
|
+
cache_key=cache_key,
|
|
140
|
+
)
|
|
141
|
+
data_dict["error_log_file"] = error_log_file
|
|
142
|
+
if serialize_exception is not None:
|
|
143
|
+
task_dict["future"].set_exception(serialize_exception)
|
|
144
|
+
elif task_key not in memory_dict:
|
|
145
|
+
if os.path.join(
|
|
146
|
+
cache_directory, task_key + "_o.h5"
|
|
147
|
+
) not in get_cache_files(cache_directory=cache_directory):
|
|
148
|
+
file_name = os.path.join(cache_directory, task_key + "_i.h5")
|
|
149
|
+
if not disable_dependencies:
|
|
150
|
+
task_dependent_lst = [
|
|
151
|
+
process_dict[k]
|
|
152
|
+
for k in future_wait_key_lst
|
|
153
|
+
if k in process_dict
|
|
154
|
+
]
|
|
155
|
+
else:
|
|
156
|
+
if len(future_wait_key_lst) > 0:
|
|
157
|
+
task_dict["future"].set_exception(
|
|
158
|
+
ValueError(
|
|
159
|
+
"Future objects are not supported as input if disable_dependencies=True."
|
|
160
|
+
)
|
|
156
161
|
)
|
|
162
|
+
continue
|
|
163
|
+
task_dependent_lst = []
|
|
164
|
+
try:
|
|
165
|
+
process_dict[task_key] = execute_function(
|
|
166
|
+
command=get_cache_execute_command(
|
|
167
|
+
file_name=file_name,
|
|
168
|
+
cores=task_resource_dict["cores"],
|
|
169
|
+
backend=backend,
|
|
170
|
+
exclusive=task_resource_dict.get(
|
|
171
|
+
"exclusive", False
|
|
172
|
+
),
|
|
173
|
+
openmpi_oversubscribe=task_resource_dict.get(
|
|
174
|
+
"openmpi_oversubscribe", False
|
|
175
|
+
),
|
|
176
|
+
pmi_mode=pmi_mode,
|
|
177
|
+
),
|
|
178
|
+
file_name=file_name,
|
|
179
|
+
data_dict=data_dict,
|
|
180
|
+
task_dependent_lst=task_dependent_lst,
|
|
181
|
+
resource_dict=task_resource_dict,
|
|
182
|
+
config_directory=pysqa_config_directory,
|
|
183
|
+
backend=backend,
|
|
184
|
+
cache_directory=cache_directory,
|
|
157
185
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
queue_id = get_queue_id(file_name=file_name)
|
|
181
|
-
if queue_id is not None:
|
|
182
|
-
process_dict[task_key] = queue_id
|
|
183
|
-
memory_dict[task_key] = task_dict["future"]
|
|
184
|
-
cache_dir_dict[task_key] = cache_directory
|
|
185
|
-
elif memory_dict[task_key] != task_dict["future"]:
|
|
186
|
-
if task_key not in duplicate_dict:
|
|
187
|
-
duplicate_dict[task_key] = []
|
|
188
|
-
duplicate_dict[task_key].append(task_dict["future"])
|
|
189
|
-
future_queue.task_done()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
task_dict["future"].set_exception(e)
|
|
188
|
+
memory_dict[task_key] = task_dict["future"]
|
|
189
|
+
continue
|
|
190
|
+
file_name = os.path.join(cache_directory, task_key + "_o.h5")
|
|
191
|
+
file_name_dict[task_key] = file_name
|
|
192
|
+
queue_id = get_queue_id(file_name=file_name)
|
|
193
|
+
if queue_id is not None:
|
|
194
|
+
process_dict[task_key] = queue_id
|
|
195
|
+
memory_dict[task_key] = task_dict["future"]
|
|
196
|
+
cache_dir_dict[task_key] = cache_directory
|
|
197
|
+
elif memory_dict[task_key] != task_dict["future"]:
|
|
198
|
+
if task_key not in duplicate_dict:
|
|
199
|
+
duplicate_dict[task_key] = []
|
|
200
|
+
duplicate_dict[task_key].append(task_dict["future"])
|
|
201
|
+
except Exception as e:
|
|
202
|
+
# Fail only the current task, never the scheduler thread, e.g. when
|
|
203
|
+
# one of the input futures of this task failed before submission.
|
|
204
|
+
if not task_dict["future"].done():
|
|
205
|
+
task_dict["future"].set_exception(e)
|
|
206
|
+
finally:
|
|
207
|
+
future_queue.task_done()
|
|
190
208
|
else:
|
|
191
209
|
memory_dict = _refresh_memory_dict(
|
|
192
210
|
memory_dict=memory_dict,
|
|
@@ -300,7 +318,10 @@ def _update_future(
|
|
|
300
318
|
|
|
301
319
|
|
|
302
320
|
def _convert_args_and_kwargs(
|
|
303
|
-
task_dict: dict,
|
|
321
|
+
task_dict: dict,
|
|
322
|
+
memory_dict: dict,
|
|
323
|
+
file_name_dict: dict,
|
|
324
|
+
duplicate_dict: Optional[dict] = None,
|
|
304
325
|
) -> tuple[list, dict, list]:
|
|
305
326
|
"""
|
|
306
327
|
Convert the arguments and keyword arguments in a task dictionary to the appropriate types.
|
|
@@ -309,59 +330,84 @@ def _convert_args_and_kwargs(
|
|
|
309
330
|
task_dict (dict): The task dictionary containing the arguments and keyword arguments.
|
|
310
331
|
memory_dict (dict): The dictionary mapping future objects to their associated task keys.
|
|
311
332
|
file_name_dict (dict): The dictionary mapping task keys to their corresponding file names.
|
|
333
|
+
duplicate_dict (dict, optional): The dictionary mapping task keys to their associated duplicate future objects.
|
|
312
334
|
|
|
313
335
|
Returns:
|
|
314
336
|
Tuple[list, dict, list]: A tuple containing the converted arguments, converted keyword arguments, and a list of future wait keys.
|
|
315
337
|
|
|
338
|
+
Raises:
|
|
339
|
+
Exception: The exception of an input future which already failed, so the dependent task fails as well.
|
|
340
|
+
|
|
316
341
|
"""
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
future_wait_key_lst = []
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
task_args.append(arg)
|
|
341
|
-
for key, arg in task_dict["kwargs"].items():
|
|
342
|
-
selector = None
|
|
343
|
-
if isinstance(arg, Future):
|
|
344
|
-
if hasattr(arg, "_future") and hasattr(arg, "_selector"):
|
|
345
|
-
selector = arg._selector
|
|
346
|
-
future = arg._future
|
|
347
|
-
else:
|
|
348
|
-
future = arg
|
|
349
|
-
match_found = False
|
|
350
|
-
for k, v in memory_dict.items():
|
|
351
|
-
if future == v:
|
|
352
|
-
task_kwargs[key] = FutureItem(
|
|
353
|
-
file_name=file_name_dict[k], selector=selector
|
|
354
|
-
)
|
|
355
|
-
future_wait_key_lst.append(k)
|
|
356
|
-
match_found = True
|
|
357
|
-
break
|
|
358
|
-
if not match_found:
|
|
359
|
-
task_kwargs[key] = future.result()
|
|
360
|
-
else:
|
|
361
|
-
task_kwargs[key] = arg
|
|
342
|
+
if duplicate_dict is None:
|
|
343
|
+
duplicate_dict = {}
|
|
344
|
+
future_wait_key_lst: list = []
|
|
345
|
+
task_args = [
|
|
346
|
+
_convert_future(
|
|
347
|
+
arg=arg,
|
|
348
|
+
memory_dict=memory_dict,
|
|
349
|
+
file_name_dict=file_name_dict,
|
|
350
|
+
duplicate_dict=duplicate_dict,
|
|
351
|
+
future_wait_key_lst=future_wait_key_lst,
|
|
352
|
+
)
|
|
353
|
+
for arg in task_dict["args"]
|
|
354
|
+
]
|
|
355
|
+
task_kwargs = {
|
|
356
|
+
key: _convert_future(
|
|
357
|
+
arg=arg,
|
|
358
|
+
memory_dict=memory_dict,
|
|
359
|
+
file_name_dict=file_name_dict,
|
|
360
|
+
duplicate_dict=duplicate_dict,
|
|
361
|
+
future_wait_key_lst=future_wait_key_lst,
|
|
362
|
+
)
|
|
363
|
+
for key, arg in task_dict["kwargs"].items()
|
|
364
|
+
}
|
|
362
365
|
return task_args, task_kwargs, future_wait_key_lst
|
|
363
366
|
|
|
364
367
|
|
|
368
|
+
def _convert_future(
|
|
369
|
+
arg: Any,
|
|
370
|
+
memory_dict: dict,
|
|
371
|
+
file_name_dict: dict,
|
|
372
|
+
duplicate_dict: dict,
|
|
373
|
+
future_wait_key_lst: list,
|
|
374
|
+
) -> Any:
|
|
375
|
+
"""
|
|
376
|
+
Convert a single argument, replacing future objects by FutureItem objects or their results.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
arg (Any): The argument to convert.
|
|
380
|
+
memory_dict (dict): The dictionary mapping task keys to their associated future objects.
|
|
381
|
+
file_name_dict (dict): The dictionary mapping task keys to their corresponding file names.
|
|
382
|
+
duplicate_dict (dict): The dictionary mapping task keys to their associated duplicate future objects.
|
|
383
|
+
future_wait_key_lst (list): List of task keys the current task depends on, extended in place.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Any: The converted argument.
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
Exception: The exception of the input future, if it already failed.
|
|
390
|
+
|
|
391
|
+
"""
|
|
392
|
+
if not isinstance(arg, Future):
|
|
393
|
+
return arg
|
|
394
|
+
if hasattr(arg, "_future") and hasattr(arg, "_selector"):
|
|
395
|
+
selector = arg._selector
|
|
396
|
+
future = arg._future
|
|
397
|
+
else:
|
|
398
|
+
selector = None
|
|
399
|
+
future = arg
|
|
400
|
+
if future.done():
|
|
401
|
+
exception = CancelledError() if future.cancelled() else future.exception()
|
|
402
|
+
if exception is not None:
|
|
403
|
+
raise exception
|
|
404
|
+
for k, v in memory_dict.items():
|
|
405
|
+
if k in file_name_dict and (future == v or future in duplicate_dict.get(k, [])):
|
|
406
|
+
future_wait_key_lst.append(k)
|
|
407
|
+
return FutureItem(file_name=file_name_dict[k], selector=selector)
|
|
408
|
+
return arg.result()
|
|
409
|
+
|
|
410
|
+
|
|
365
411
|
def _refresh_memory_dict(
|
|
366
412
|
memory_dict: dict,
|
|
367
413
|
cache_dir_dict: dict,
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
from subprocess import CalledProcessError
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
from pandas import DataFrame
|
|
5
6
|
from pysqa import QueueAdapter
|
|
6
7
|
|
|
7
8
|
from executorlib.standalone.command_pysqa import pysqa_execute_command, pysqa_terminate
|
|
@@ -154,3 +155,58 @@ def terminate_task_in_cache(
|
|
|
154
155
|
backend=backend,
|
|
155
156
|
)
|
|
156
157
|
os.remove(file_name)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_queue_system_cache_data(
|
|
161
|
+
cache_dict: list[dict],
|
|
162
|
+
queue_type: Optional[str] = None,
|
|
163
|
+
config_directory: Optional[str] = None,
|
|
164
|
+
) -> list[dict]:
|
|
165
|
+
"""
|
|
166
|
+
Get the status of the queue system.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
cache_dict (list[dict]): List of dictionaries each representing on of the HDF5 files in the cache directory.
|
|
170
|
+
queue_type (str, optional): The type of the queue system ["slurm", "flux"].
|
|
171
|
+
config_directory (str, optional): The directory containing the configuration for the queue system.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
cache_dict (list[dict]): List of dictionaries each representing on of the HDF5 files in the cache directory.
|
|
175
|
+
"""
|
|
176
|
+
return _merge_cache_with_queue_status(
|
|
177
|
+
cache_dict=cache_dict,
|
|
178
|
+
dataframe=QueueAdapter(
|
|
179
|
+
directory=config_directory,
|
|
180
|
+
queue_type=queue_type,
|
|
181
|
+
).get_queue_status(),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _merge_cache_with_queue_status(
|
|
186
|
+
cache_dict: list[dict], dataframe: DataFrame
|
|
187
|
+
) -> list[dict]:
|
|
188
|
+
"""
|
|
189
|
+
Merge the cache data with the queue status.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
cache_dict (list[dict]): List of dictionaries each representing on of the HDF5 files in the cache directory.
|
|
193
|
+
dataframe (DataFrame): DataFrame containing the status of the queue system.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
cache_dict (list[dict]): List of dictionaries each representing on of the HDF5 files in the cache directory.
|
|
197
|
+
"""
|
|
198
|
+
cache_updated_dict = []
|
|
199
|
+
for row in cache_dict:
|
|
200
|
+
if "output" in row and row["output"]:
|
|
201
|
+
row["status"] = "finished"
|
|
202
|
+
elif "queue_id" in row and row["queue_id"] in dataframe["jobid"].values:
|
|
203
|
+
row["status"] = dataframe[dataframe["jobid"] == row["queue_id"]][
|
|
204
|
+
"status"
|
|
205
|
+
].values[-1]
|
|
206
|
+
elif "queue_id" not in row:
|
|
207
|
+
row["status"] = "running"
|
|
208
|
+
else:
|
|
209
|
+
row["status"] = "aborted"
|
|
210
|
+
cache_updated_dict.append(row)
|
|
211
|
+
|
|
212
|
+
return cache_updated_dict
|
|
@@ -15,7 +15,7 @@ from executorlib.standalone.interactive.communication import (
|
|
|
15
15
|
interface_bootup,
|
|
16
16
|
)
|
|
17
17
|
from executorlib.standalone.interactive.spawner import BaseSpawner, MpiExecSpawner
|
|
18
|
-
from executorlib.standalone.queue import cancel_items_in_queue
|
|
18
|
+
from executorlib.standalone.queue import cancel_items_in_queue, put_front
|
|
19
19
|
from executorlib.task_scheduler.base import TaskSchedulerBase, validate_resource_dict
|
|
20
20
|
from executorlib.task_scheduler.interactive.shared import (
|
|
21
21
|
execute_task_dict,
|
|
@@ -80,35 +80,37 @@ class BlockAllocationTaskScheduler(TaskSchedulerBase):
|
|
|
80
80
|
executor_kwargs["restart_limit"] = restart_limit
|
|
81
81
|
self._process_kwargs = executor_kwargs
|
|
82
82
|
self._max_workers = max_workers
|
|
83
|
-
|
|
84
|
-
self._self_id = self_id
|
|
83
|
+
self._self_id = random.getrandbits(128)
|
|
85
84
|
_interrupt_bootup_dict[self._self_id] = False
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
self._alive_workers = [max_workers]
|
|
86
|
+
self._alive_workers_lock = Lock()
|
|
87
|
+
self._bootup_events = [Event() for _ in range(self._max_workers)]
|
|
88
|
+
self._bootup_events[0].set()
|
|
90
89
|
self._set_process(
|
|
91
90
|
process=[
|
|
92
91
|
Thread(
|
|
93
92
|
target=_execute_multiple_tasks,
|
|
94
|
-
kwargs=
|
|
95
|
-
| {
|
|
96
|
-
"worker_id": worker_id,
|
|
97
|
-
"stop_function": lambda: _interrupt_bootup_dict[self_id],
|
|
98
|
-
"bootup_event": bootup_events[worker_id],
|
|
99
|
-
"next_bootup_event": (
|
|
100
|
-
bootup_events[worker_id + 1]
|
|
101
|
-
if worker_id + 1 < self._max_workers
|
|
102
|
-
else None
|
|
103
|
-
),
|
|
104
|
-
"alive_workers": alive_workers,
|
|
105
|
-
"alive_workers_lock": alive_workers_lock,
|
|
106
|
-
},
|
|
93
|
+
kwargs=self._worker_kwargs(worker_id),
|
|
107
94
|
)
|
|
108
95
|
for worker_id in range(self._max_workers)
|
|
109
96
|
],
|
|
110
97
|
)
|
|
111
98
|
|
|
99
|
+
def _worker_kwargs(self, worker_id: int) -> dict:
|
|
100
|
+
self_id = self._self_id
|
|
101
|
+
return self._process_kwargs | {
|
|
102
|
+
"worker_id": worker_id,
|
|
103
|
+
"stop_function": lambda: _interrupt_bootup_dict[self_id],
|
|
104
|
+
"bootup_event": self._bootup_events[worker_id],
|
|
105
|
+
"next_bootup_event": (
|
|
106
|
+
self._bootup_events[worker_id + 1]
|
|
107
|
+
if worker_id + 1 < len(self._bootup_events)
|
|
108
|
+
else None
|
|
109
|
+
),
|
|
110
|
+
"alive_workers": self._alive_workers,
|
|
111
|
+
"alive_workers_lock": self._alive_workers_lock,
|
|
112
|
+
}
|
|
113
|
+
|
|
112
114
|
@property
|
|
113
115
|
def max_workers(self) -> int:
|
|
114
116
|
return self._max_workers
|
|
@@ -120,18 +122,26 @@ class BlockAllocationTaskScheduler(TaskSchedulerBase):
|
|
|
120
122
|
):
|
|
121
123
|
if self._max_workers > max_workers:
|
|
122
124
|
for _ in range(self._max_workers - max_workers):
|
|
123
|
-
self._future_queue
|
|
125
|
+
put_front(self._future_queue, {"shutdown": True, "wait": True})
|
|
124
126
|
while len(self._process) > max_workers:
|
|
125
127
|
self._process = [
|
|
126
128
|
process for process in self._process if process.is_alive()
|
|
127
129
|
]
|
|
128
130
|
elif self._max_workers < max_workers:
|
|
131
|
+
old_max_workers = self._max_workers
|
|
132
|
+
self._bootup_events.extend(
|
|
133
|
+
Event() for _ in range(max_workers - old_max_workers)
|
|
134
|
+
)
|
|
135
|
+
for idx in range(old_max_workers, max_workers):
|
|
136
|
+
self._bootup_events[idx].set()
|
|
137
|
+
with self._alive_workers_lock:
|
|
138
|
+
self._alive_workers[0] += max_workers - old_max_workers
|
|
129
139
|
new_process_lst = [
|
|
130
140
|
Thread(
|
|
131
141
|
target=_execute_multiple_tasks,
|
|
132
|
-
kwargs=self.
|
|
142
|
+
kwargs=self._worker_kwargs(worker_id),
|
|
133
143
|
)
|
|
134
|
-
for
|
|
144
|
+
for worker_id in range(old_max_workers, max_workers)
|
|
135
145
|
]
|
|
136
146
|
for process_instance in new_process_lst:
|
|
137
147
|
process_instance.start()
|
|
@@ -101,13 +101,17 @@ def _execute_task_without_cache(
|
|
|
101
101
|
Returns:
|
|
102
102
|
bool: True if the task was submitted successfully, False otherwise.
|
|
103
103
|
"""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return False
|
|
104
|
+
try:
|
|
105
|
+
output = interface.send_and_receive_dict(input_dict=task_dict)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
future_obj.set_exception(exception=e)
|
|
109
108
|
else:
|
|
110
|
-
|
|
109
|
+
if "result" in output:
|
|
110
|
+
future_obj.set_result(output["result"])
|
|
111
|
+
elif isinstance(output["error"], ExecutorlibSocketError):
|
|
112
|
+
return False
|
|
113
|
+
else:
|
|
114
|
+
future_obj.set_exception(exception=output["error"])
|
|
111
115
|
return True
|
|
112
116
|
|
|
113
117
|
|
|
@@ -132,27 +136,34 @@ def _execute_task_with_cache(
|
|
|
132
136
|
"""
|
|
133
137
|
from executorlib.standalone.hdf import dump, get_cache_files, get_output
|
|
134
138
|
|
|
135
|
-
task_key, data_dict = serialize_funct(
|
|
139
|
+
task_key, data_dict, serialize_exception = serialize_funct(
|
|
136
140
|
fn=task_dict["fn"],
|
|
137
141
|
fn_args=task_dict["args"],
|
|
138
142
|
fn_kwargs=task_dict["kwargs"],
|
|
139
143
|
resource_dict=task_dict.get("resource_dict", {}),
|
|
140
144
|
cache_key=cache_key,
|
|
141
145
|
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
time_start = time.time()
|
|
145
|
-
output = interface.send_and_receive_dict(input_dict=task_dict)
|
|
146
|
-
if "result" in output:
|
|
147
|
-
data_dict["output"] = output["result"]
|
|
148
|
-
data_dict["runtime"] = time.time() - time_start
|
|
149
|
-
dump(file_name=file_name, data_dict=data_dict)
|
|
150
|
-
future_obj.set_result(output["result"])
|
|
151
|
-
elif isinstance(output["error"], ExecutorlibSocketError):
|
|
152
|
-
return False
|
|
153
|
-
else:
|
|
154
|
-
future_obj.set_exception(exception=output["error"])
|
|
146
|
+
if serialize_exception is not None:
|
|
147
|
+
future_obj.set_exception(exception=serialize_exception)
|
|
155
148
|
else:
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
file_name = os.path.abspath(os.path.join(cache_directory, task_key + "_o.h5"))
|
|
150
|
+
if file_name not in get_cache_files(cache_directory=cache_directory):
|
|
151
|
+
time_start = time.time()
|
|
152
|
+
try:
|
|
153
|
+
output = interface.send_and_receive_dict(input_dict=task_dict)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
future_obj.set_exception(exception=e)
|
|
156
|
+
return True
|
|
157
|
+
if "result" in output:
|
|
158
|
+
data_dict["output"] = output["result"]
|
|
159
|
+
data_dict["runtime"] = time.time() - time_start
|
|
160
|
+
dump(file_name=file_name, data_dict=data_dict)
|
|
161
|
+
future_obj.set_result(output["result"])
|
|
162
|
+
elif isinstance(output["error"], ExecutorlibSocketError):
|
|
163
|
+
return False
|
|
164
|
+
else:
|
|
165
|
+
future_obj.set_exception(exception=output["error"])
|
|
166
|
+
else:
|
|
167
|
+
_, _, result = get_output(file_name=file_name)
|
|
168
|
+
future_obj.set_result(result)
|
|
158
169
|
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: executorlib
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.4
|
|
4
4
|
Summary: Up-scale python functions for high performance computing (HPC) with executorlib.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pyiron/executorlib
|
|
6
6
|
Project-URL: Documentation, https://executorlib.readthedocs.io
|
|
@@ -49,31 +49,31 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
49
49
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
50
50
|
Requires-Python: <3.15,>3.9
|
|
51
51
|
Requires-Dist: cloudpickle<=3.1.2,>=2.0.0
|
|
52
|
-
Requires-Dist: pyzmq<=27.
|
|
52
|
+
Requires-Dist: pyzmq<=27.2.0,>=25.0.0
|
|
53
53
|
Provides-Extra: all
|
|
54
54
|
Requires-Dist: h5py<=3.16.0,>=3.6.0; extra == 'all'
|
|
55
55
|
Requires-Dist: ipython<=9.9.0,>=7.33.0; extra == 'all'
|
|
56
56
|
Requires-Dist: mpi4py<=4.1.2,>=3.1.4; extra == 'all'
|
|
57
57
|
Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'all'
|
|
58
|
-
Requires-Dist: pydantic<=2.
|
|
59
|
-
Requires-Dist: pygraphviz<=
|
|
60
|
-
Requires-Dist: pysqa==0.
|
|
58
|
+
Requires-Dist: pydantic<=2.14.1,>=2.5.3; extra == 'all'
|
|
59
|
+
Requires-Dist: pygraphviz<=2.0.3,>=1.10; extra == 'all'
|
|
60
|
+
Requires-Dist: pysqa==1.0.1; extra == 'all'
|
|
61
61
|
Provides-Extra: cache
|
|
62
62
|
Requires-Dist: h5py<=3.16.0,>=3.6.0; extra == 'cache'
|
|
63
63
|
Provides-Extra: cluster
|
|
64
64
|
Requires-Dist: h5py<=3.16.0,>=3.6.0; extra == 'cluster'
|
|
65
|
-
Requires-Dist: pysqa==0.
|
|
65
|
+
Requires-Dist: pysqa==1.0.1; extra == 'cluster'
|
|
66
66
|
Provides-Extra: graph
|
|
67
67
|
Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'graph'
|
|
68
|
-
Requires-Dist: pygraphviz<=
|
|
68
|
+
Requires-Dist: pygraphviz<=2.0.3,>=1.10; extra == 'graph'
|
|
69
69
|
Provides-Extra: graphnotebook
|
|
70
70
|
Requires-Dist: ipython<=9.9.0,>=7.33.0; extra == 'graphnotebook'
|
|
71
71
|
Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'graphnotebook'
|
|
72
|
-
Requires-Dist: pygraphviz<=
|
|
72
|
+
Requires-Dist: pygraphviz<=2.0.3,>=1.10; extra == 'graphnotebook'
|
|
73
73
|
Provides-Extra: mpi
|
|
74
74
|
Requires-Dist: mpi4py<=4.1.2,>=3.1.4; extra == 'mpi'
|
|
75
75
|
Provides-Extra: validation
|
|
76
|
-
Requires-Dist: pydantic<=2.
|
|
76
|
+
Requires-Dist: pydantic<=2.14.1,>=2.5.3; extra == 'validation'
|
|
77
77
|
Description-Content-Type: text/markdown
|
|
78
78
|
|
|
79
79
|
# executorlib
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
executorlib/__init__.py,sha256=
|
|
2
|
-
executorlib/_version.py,sha256=
|
|
1
|
+
executorlib/__init__.py,sha256=Xj1QeplikK6_6BFifidkFWPcTDzDM-XEV74RjXJU5KY,5841
|
|
2
|
+
executorlib/_version.py,sha256=usuurpwHDo5uv6MsesRr2lS6yJFV0JkHe1pMreJX8PQ,522
|
|
3
3
|
executorlib/api.py,sha256=F6ibDJnVcPGxxPe-UScFJHprWHiVsETGhgAvS1T-9ZI,1365
|
|
4
4
|
executorlib/backend/__init__.py,sha256=2b_iwds4VUrJe92nKtQXkoGxkJiXeEDSlbvCRN_-de8,456
|
|
5
5
|
executorlib/backend/cache_parallel.py,sha256=ldb2iYzrhcXJZ0LcH75FpTLO_6F51mleU9JGAilF00k,2020
|
|
@@ -16,35 +16,35 @@ executorlib/standalone/batched.py,sha256=o3TtsFJir8nP1O_NZFfW0Q78CoZAFgHMyroWlg5
|
|
|
16
16
|
executorlib/standalone/command.py,sha256=4Xndog84zJdku1ke1DwdUamkWvzRrMz-VDCi5cRXi0M,6548
|
|
17
17
|
executorlib/standalone/command_pysqa.py,sha256=eNTyHxtjUsn3950NOWHfNrFKgtxgD9GAVxCdN6Ewxcc,5948
|
|
18
18
|
executorlib/standalone/error.py,sha256=aaMQoRsoLzLCbYT-E4LZxGM9pm_AhjMesRjVt0d_LDk,714
|
|
19
|
-
executorlib/standalone/hdf.py,sha256=
|
|
19
|
+
executorlib/standalone/hdf.py,sha256=bEVlV-Y9vzYnd3NNcC0uOsdWJSzLN3c2HbzcWKo8328,7975
|
|
20
20
|
executorlib/standalone/inputcheck.py,sha256=kSWUzqdMOhILYXW4Isumn1EGH3SegxPUMWRkUWfEleU,10041
|
|
21
|
-
executorlib/standalone/queue.py,sha256
|
|
22
|
-
executorlib/standalone/select.py,sha256=
|
|
23
|
-
executorlib/standalone/serialize.py,sha256=
|
|
21
|
+
executorlib/standalone/queue.py,sha256=-q04hhz1UmZFhbJY95ctBg1xmxqjNxh0xCYhv3y9jK0,1281
|
|
22
|
+
executorlib/standalone/select.py,sha256=7zZOc9WLOMtKEVPybO_kGZjjLRnhGx4_tkqxZumWB_o,3215
|
|
23
|
+
executorlib/standalone/serialize.py,sha256=NdFdwF8xrIPPbYpBRQY3d5-n2yQSG6H41EDEr2Ojkes,4972
|
|
24
24
|
executorlib/standalone/validate.py,sha256=hHG32xomLhbqnqd6Vn4AO05bQ7B3WZ1rngTkMsODwwk,4430
|
|
25
25
|
executorlib/standalone/interactive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
executorlib/standalone/interactive/arguments.py,sha256=iaVXf7Wq9MUOu3WeYSi2t-f8Si_pyAPyykcnq8bf8cY,3239
|
|
27
27
|
executorlib/standalone/interactive/backend.py,sha256=gGgcvER8w75bM_p1uplSsr6yCtBBlOS_6wR1iQNsntc,3006
|
|
28
|
-
executorlib/standalone/interactive/communication.py,sha256=
|
|
28
|
+
executorlib/standalone/interactive/communication.py,sha256=hmv6APOu1RMScn5RIkoXH4-CZAI9QUkDwd-eq1NEabI,10467
|
|
29
29
|
executorlib/standalone/interactive/spawner.py,sha256=4TEPL4iI_rF25kOaW-eqY74jK93LFZ05cYOHUjIuo4M,6384
|
|
30
30
|
executorlib/task_scheduler/__init__.py,sha256=-sw60rfnEvwFiOAONFHpE3izPA9CPtp5cQ7vRetw2Mw,249
|
|
31
31
|
executorlib/task_scheduler/base.py,sha256=A9nQb8FYKbZGjQP1G5ccT75ODbEqqiM0OVybHAxjqzE,9718
|
|
32
32
|
executorlib/task_scheduler/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
executorlib/task_scheduler/file/backend.py,sha256=tojXcYytAsfABugsOMfkx9oAh01do_DcSneVldMfeK0,2876
|
|
34
|
-
executorlib/task_scheduler/file/shared.py,sha256=
|
|
35
|
-
executorlib/task_scheduler/file/spawner_pysqa.py,sha256=
|
|
34
|
+
executorlib/task_scheduler/file/shared.py,sha256=PpCHLapgVzjsEfQ-p1k5AGiAFHI7OcrvznkLZkJxu3A,26883
|
|
35
|
+
executorlib/task_scheduler/file/spawner_pysqa.py,sha256=jpE7eKGlE-OpzMM7ly7U--IQViMQ72vHnWve6fHXcwM,7991
|
|
36
36
|
executorlib/task_scheduler/file/spawner_subprocess.py,sha256=yMc4nHRUghgdR0ehihE2CYsaTn1RspRI3ddhIhkTgDw,3086
|
|
37
37
|
executorlib/task_scheduler/file/task_scheduler.py,sha256=gfEpNVJ9u87_yaDYZ86kHt_mAEXfOqBwvxX4IlpKt6o,6170
|
|
38
38
|
executorlib/task_scheduler/interactive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
executorlib/task_scheduler/interactive/blockallocation.py,sha256=
|
|
39
|
+
executorlib/task_scheduler/interactive/blockallocation.py,sha256=_hEkIA4s1LxYte3j7nykMEzNTuBj3ZYxNVq6CUPvqFg,18167
|
|
40
40
|
executorlib/task_scheduler/interactive/dependency.py,sha256=zQMfI6AjeUv3A1qyPzFah2BKYGWgGLRyEIdZ0XTjq1M,15222
|
|
41
41
|
executorlib/task_scheduler/interactive/dependency_plot.py,sha256=iZBZ9UMDeYOMFjD2-kYOxOdKeodj7TuyshI4HbH4XCU,11788
|
|
42
42
|
executorlib/task_scheduler/interactive/onetoone.py,sha256=cTXgTXR9kKzzlx6EfGumMsEm7DUa7BD9JSeBpdPiupI,13312
|
|
43
|
-
executorlib/task_scheduler/interactive/shared.py,sha256=
|
|
43
|
+
executorlib/task_scheduler/interactive/shared.py,sha256=Lr9R63f0kBl4g64E0vRl3xMvnYlNypoIx5ADYSQ2RdE,6720
|
|
44
44
|
executorlib/task_scheduler/interactive/spawner_flux.py,sha256=M7CPWvzY7MDkplJ4awymh7WbMYwKsDPTKxA2Op9wfNo,7075
|
|
45
45
|
executorlib/task_scheduler/interactive/spawner_pysqa.py,sha256=jQB8PQAH9cBo_ofpxHTzydg0ghwSOP51CtVL96YH_fc,10346
|
|
46
46
|
executorlib/task_scheduler/interactive/spawner_slurm.py,sha256=K5mj63PE8FYbkw9WWm4uEjoA5q0HM42z1d2wjAmO5YA,3713
|
|
47
|
-
executorlib-1.10.
|
|
48
|
-
executorlib-1.10.
|
|
49
|
-
executorlib-1.10.
|
|
50
|
-
executorlib-1.10.
|
|
47
|
+
executorlib-1.10.4.dist-info/METADATA,sha256=aCrXufqr_iKhoTTl2cN7Bwl27DyrvK8NNmdg6fjNFbY,20969
|
|
48
|
+
executorlib-1.10.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
49
|
+
executorlib-1.10.4.dist-info/licenses/LICENSE,sha256=NkrhccIxPXCbbvp_upyVojt7DNTwZIco3z9wsYpSLqU,1511
|
|
50
|
+
executorlib-1.10.4.dist-info/RECORD,,
|
|
File without changes
|