executorlib 1.7.2__tar.gz → 1.7.4__tar.gz

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.
Files changed (50) hide show
  1. {executorlib-1.7.2 → executorlib-1.7.4}/PKG-INFO +13 -12
  2. {executorlib-1.7.2 → executorlib-1.7.4}/README.md +1 -0
  3. {executorlib-1.7.2 → executorlib-1.7.4}/pyproject.toml +13 -13
  4. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/_version.py +2 -2
  5. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/executor/base.py +46 -3
  6. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/executor/flux.py +8 -0
  7. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/executor/single.py +8 -0
  8. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/executor/slurm.py +8 -0
  9. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/serialize.py +8 -1
  10. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/base.py +37 -0
  11. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/dependency.py +17 -6
  12. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/dependency_plot.py +134 -15
  13. {executorlib-1.7.2 → executorlib-1.7.4}/.gitignore +0 -0
  14. {executorlib-1.7.2 → executorlib-1.7.4}/LICENSE +0 -0
  15. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/__init__.py +0 -0
  16. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/api.py +0 -0
  17. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/backend/__init__.py +0 -0
  18. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/backend/cache_parallel.py +0 -0
  19. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/backend/cache_serial.py +0 -0
  20. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/backend/interactive_parallel.py +0 -0
  21. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/backend/interactive_serial.py +0 -0
  22. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/executor/__init__.py +0 -0
  23. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/__init__.py +0 -0
  24. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/batched.py +0 -0
  25. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/command.py +0 -0
  26. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/error.py +0 -0
  27. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/hdf.py +0 -0
  28. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/inputcheck.py +0 -0
  29. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/interactive/__init__.py +0 -0
  30. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/interactive/arguments.py +0 -0
  31. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/interactive/backend.py +0 -0
  32. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/interactive/communication.py +0 -0
  33. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/interactive/spawner.py +0 -0
  34. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/queue.py +0 -0
  35. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/scheduler.py +0 -0
  36. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/standalone/select.py +0 -0
  37. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/__init__.py +0 -0
  38. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/__init__.py +0 -0
  39. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/backend.py +0 -0
  40. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/shared.py +0 -0
  41. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/spawner_pysqa.py +0 -0
  42. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/spawner_subprocess.py +0 -0
  43. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/file/task_scheduler.py +0 -0
  44. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/__init__.py +0 -0
  45. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/blockallocation.py +0 -0
  46. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/onetoone.py +0 -0
  47. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/shared.py +0 -0
  48. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/spawner_flux.py +0 -0
  49. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/spawner_pysqa.py +0 -0
  50. {executorlib-1.7.2 → executorlib-1.7.4}/src/executorlib/task_scheduler/interactive/spawner_slurm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: executorlib
3
- Version: 1.7.2
3
+ Version: 1.7.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
@@ -47,26 +47,26 @@ Classifier: Programming Language :: Python :: 3.12
47
47
  Classifier: Programming Language :: Python :: 3.13
48
48
  Classifier: Topic :: Scientific/Engineering :: Physics
49
49
  Requires-Python: <3.14,>3.9
50
- Requires-Dist: cloudpickle<=3.1.1,>=2.0.0
50
+ Requires-Dist: cloudpickle<=3.1.2,>=2.0.0
51
51
  Requires-Dist: pyzmq<=27.1.0,>=25.0.0
52
52
  Provides-Extra: all
53
- Requires-Dist: h5py<=3.15.0,>=3.6.0; extra == 'all'
54
- Requires-Dist: ipython<=9.0.2,>=7.33.0; extra == 'all'
53
+ Requires-Dist: h5py<=3.15.1,>=3.6.0; extra == 'all'
54
+ Requires-Dist: ipython<=9.9.0,>=7.33.0; extra == 'all'
55
55
  Requires-Dist: mpi4py<=4.1.1,>=3.1.4; extra == 'all'
56
- Requires-Dist: networkx<=3.4.2,>=2.8.8; extra == 'all'
56
+ Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'all'
57
57
  Requires-Dist: pygraphviz<=1.14,>=1.10; extra == 'all'
58
- Requires-Dist: pysqa==0.3.2; extra == 'all'
58
+ Requires-Dist: pysqa==0.3.3; extra == 'all'
59
59
  Provides-Extra: cache
60
- Requires-Dist: h5py<=3.15.0,>=3.6.0; extra == 'cache'
60
+ Requires-Dist: h5py<=3.15.1,>=3.6.0; extra == 'cache'
61
61
  Provides-Extra: cluster
62
- Requires-Dist: h5py<=3.15.0,>=3.6.0; extra == 'cluster'
63
- Requires-Dist: pysqa==0.3.2; extra == 'cluster'
62
+ Requires-Dist: h5py<=3.15.1,>=3.6.0; extra == 'cluster'
63
+ Requires-Dist: pysqa==0.3.3; extra == 'cluster'
64
64
  Provides-Extra: graph
65
- Requires-Dist: networkx<=3.4.2,>=2.8.8; extra == 'graph'
65
+ Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'graph'
66
66
  Requires-Dist: pygraphviz<=1.14,>=1.10; extra == 'graph'
67
67
  Provides-Extra: graphnotebook
68
- Requires-Dist: ipython<=9.0.2,>=7.33.0; extra == 'graphnotebook'
69
- Requires-Dist: networkx<=3.4.2,>=2.8.8; extra == 'graphnotebook'
68
+ Requires-Dist: ipython<=9.9.0,>=7.33.0; extra == 'graphnotebook'
69
+ Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'graphnotebook'
70
70
  Requires-Dist: pygraphviz<=1.14,>=1.10; extra == 'graphnotebook'
71
71
  Provides-Extra: mpi
72
72
  Requires-Dist: mpi4py<=4.1.1,>=3.1.4; extra == 'mpi'
@@ -208,6 +208,7 @@ as hierarchical job scheduler within the allocations.
208
208
  * [Basic Functionality](https://executorlib.readthedocs.io/en/latest/1-single-node.html#basic-functionality)
209
209
  * [Parallel Functions](https://executorlib.readthedocs.io/en/latest/1-single-node.html#parallel-functions)
210
210
  * [Performance Optimization](https://executorlib.readthedocs.io/en/latest/1-single-node.html#performance-optimization)
211
+ * [Advanced Scheduling](https://executorlib.readthedocs.io/en/latest/1-single-node.html#advanced-scheduling)
211
212
  * [Testing and Debugging](https://executorlib.readthedocs.io/en/latest/1-single-node.html#testing-and-debugging)
212
213
  * [HPC Cluster Executor](https://executorlib.readthedocs.io/en/latest/2-hpc-cluster.html)
213
214
  * [SLURM](https://executorlib.readthedocs.io/en/latest/2-hpc-cluster.html#slurm)
@@ -134,6 +134,7 @@ as hierarchical job scheduler within the allocations.
134
134
  * [Basic Functionality](https://executorlib.readthedocs.io/en/latest/1-single-node.html#basic-functionality)
135
135
  * [Parallel Functions](https://executorlib.readthedocs.io/en/latest/1-single-node.html#parallel-functions)
136
136
  * [Performance Optimization](https://executorlib.readthedocs.io/en/latest/1-single-node.html#performance-optimization)
137
+ * [Advanced Scheduling](https://executorlib.readthedocs.io/en/latest/1-single-node.html#advanced-scheduling)
137
138
  * [Testing and Debugging](https://executorlib.readthedocs.io/en/latest/1-single-node.html#testing-and-debugging)
138
139
  * [HPC Cluster Executor](https://executorlib.readthedocs.io/en/latest/2-hpc-cluster.html)
139
140
  * [SLURM](https://executorlib.readthedocs.io/en/latest/2-hpc-cluster.html#slurm)
@@ -1,8 +1,8 @@
1
1
  [build-system]
2
2
  requires = [
3
- "hatchling==1.27.0",
3
+ "hatchling>=1.27.0,<=1.28.0",
4
4
  "hatch-vcs==0.5.0",
5
- "cloudpickle>=2.0.0,<=3.1.1",
5
+ "cloudpickle>=2.0.0,<=3.1.2",
6
6
  "pyzmq>=25.0.0,<=27.1.0",
7
7
  ]
8
8
  build-backend = "hatchling.build"
@@ -29,7 +29,7 @@ classifiers = [
29
29
  "Programming Language :: Python :: 3.13",
30
30
  ]
31
31
  dependencies = [
32
- "cloudpickle>=2.0.0,<=3.1.1",
32
+ "cloudpickle>=2.0.0,<=3.1.2",
33
33
  "pyzmq>=25.0.0,<=27.1.0",
34
34
  ]
35
35
  dynamic = ["version"]
@@ -40,28 +40,28 @@ Documentation = "https://executorlib.readthedocs.io"
40
40
  Repository = "https://github.com/pyiron/executorlib"
41
41
 
42
42
  [project.optional-dependencies]
43
- cache = ["h5py>=3.6.0,<=3.15.0"]
43
+ cache = ["h5py>=3.6.0,<=3.15.1"]
44
44
  graph = [
45
45
  "pygraphviz>=1.10,<=1.14",
46
- "networkx>=2.8.8,<=3.4.2",
46
+ "networkx>=2.8.8,<=3.6.1",
47
47
  ]
48
48
  graphnotebook = [
49
49
  "pygraphviz>=1.10,<=1.14",
50
- "networkx>=2.8.8,<=3.4.2",
51
- "ipython>=7.33.0,<=9.0.2",
50
+ "networkx>=2.8.8,<=3.6.1",
51
+ "ipython>=7.33.0,<=9.9.0",
52
52
  ]
53
53
  mpi = ["mpi4py>=3.1.4,<=4.1.1"]
54
54
  cluster = [
55
- "pysqa==0.3.2",
56
- "h5py>=3.6.0,<=3.15.0",
55
+ "pysqa==0.3.3",
56
+ "h5py>=3.6.0,<=3.15.1",
57
57
  ]
58
58
  all = [
59
59
  "mpi4py>=3.1.4,<=4.1.1",
60
- "pysqa==0.3.2",
61
- "h5py>=3.6.0,<=3.15.0",
60
+ "pysqa==0.3.3",
61
+ "h5py>=3.6.0,<=3.15.1",
62
62
  "pygraphviz>=1.10,<=1.14",
63
- "networkx>=2.8.8,<=3.4.2",
64
- "ipython>=7.33.0,<=9.0.2",
63
+ "networkx>=2.8.8,<=3.6.1",
64
+ "ipython>=7.33.0,<=9.9.0",
65
65
  ]
66
66
 
67
67
  [tool.ruff]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.7.2'
32
- __version_tuple__ = version_tuple = (1, 7, 2)
31
+ __version__ = version = '1.7.4'
32
+ __version_tuple__ = version_tuple = (1, 7, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -21,6 +21,7 @@ class BaseExecutor(FutureExecutor, ABC):
21
21
 
22
22
  def __init__(self, executor: TaskSchedulerBase):
23
23
  self._task_scheduler = executor
24
+ self._is_active = True
24
25
 
25
26
  @property
26
27
  def max_workers(self) -> Optional[int]:
@@ -99,9 +100,49 @@ class BaseExecutor(FutureExecutor, ABC):
99
100
  Returns:
100
101
  Future: A Future representing the given call.
101
102
  """
102
- return self._task_scheduler.submit(
103
- *([fn] + list(args)), resource_dict=resource_dict, **kwargs
104
- )
103
+ if self._is_active:
104
+ return self._task_scheduler.submit(
105
+ *([fn] + list(args)), resource_dict=resource_dict, **kwargs
106
+ )
107
+ else:
108
+ raise RuntimeError("cannot schedule new futures after shutdown")
109
+
110
+ def map(
111
+ self,
112
+ fn: Callable,
113
+ *iterables,
114
+ timeout: Optional[float] = None,
115
+ chunksize: int = 1,
116
+ ):
117
+ """Returns an iterator equivalent to map(fn, iter).
118
+
119
+ Args:
120
+ fn: A callable that will take as many arguments as there are
121
+ passed iterables.
122
+ timeout: The maximum number of seconds to wait. If None, then there
123
+ is no limit on the wait time.
124
+ chunksize: The size of the chunks the iterable will be broken into
125
+ before being passed to a child process. This argument is only
126
+ used by ProcessPoolExecutor; it is ignored by
127
+ ThreadPoolExecutor.
128
+
129
+ Returns:
130
+ An iterator equivalent to: map(func, *iterables) but the calls may
131
+ be evaluated out-of-order.
132
+
133
+ Raises:
134
+ TimeoutError: If the entire result iterator could not be generated
135
+ before the given timeout.
136
+ Exception: If fn(*args) raises for any values.
137
+ """
138
+ if self._is_active:
139
+ return self._task_scheduler.map(
140
+ *([fn] + list(iterables)),
141
+ timeout=timeout,
142
+ chunksize=chunksize,
143
+ )
144
+ else:
145
+ raise RuntimeError("cannot schedule new futures after shutdown")
105
146
 
106
147
  def shutdown(self, wait: bool = True, *, cancel_futures: bool = False):
107
148
  """
@@ -119,6 +160,7 @@ class BaseExecutor(FutureExecutor, ABC):
119
160
  cancelled.
120
161
  """
121
162
  self._task_scheduler.shutdown(wait=wait, cancel_futures=cancel_futures)
163
+ self._is_active = False
122
164
 
123
165
  def __len__(self) -> int:
124
166
  """
@@ -143,3 +185,4 @@ class BaseExecutor(FutureExecutor, ABC):
143
185
  Exit method called when exiting the context manager.
144
186
  """
145
187
  self._task_scheduler.__exit__(*args, **kwargs)
188
+ self._is_active = False
@@ -65,6 +65,7 @@ class FluxJobExecutor(BaseExecutor):
65
65
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
66
66
  debugging purposes and to get an overview of the specified dependencies.
67
67
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
68
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
68
69
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
69
70
 
70
71
  Examples:
@@ -105,6 +106,7 @@ class FluxJobExecutor(BaseExecutor):
105
106
  refresh_rate: float = 0.01,
106
107
  plot_dependency_graph: bool = False,
107
108
  plot_dependency_graph_filename: Optional[str] = None,
109
+ export_workflow_filename: Optional[str] = None,
108
110
  log_obj_size: bool = False,
109
111
  ):
110
112
  """
@@ -152,6 +154,7 @@ class FluxJobExecutor(BaseExecutor):
152
154
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
153
155
  debugging purposes and to get an overview of the specified dependencies.
154
156
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
157
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
155
158
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
156
159
 
157
160
  """
@@ -189,6 +192,7 @@ class FluxJobExecutor(BaseExecutor):
189
192
  refresh_rate=refresh_rate,
190
193
  plot_dependency_graph=plot_dependency_graph,
191
194
  plot_dependency_graph_filename=plot_dependency_graph_filename,
195
+ export_workflow_filename=export_workflow_filename,
192
196
  )
193
197
  )
194
198
  else:
@@ -255,6 +259,7 @@ class FluxClusterExecutor(BaseExecutor):
255
259
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
256
260
  debugging purposes and to get an overview of the specified dependencies.
257
261
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
262
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
258
263
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
259
264
 
260
265
  Examples:
@@ -293,6 +298,7 @@ class FluxClusterExecutor(BaseExecutor):
293
298
  refresh_rate: float = 0.01,
294
299
  plot_dependency_graph: bool = False,
295
300
  plot_dependency_graph_filename: Optional[str] = None,
301
+ export_workflow_filename: Optional[str] = None,
296
302
  log_obj_size: bool = False,
297
303
  ):
298
304
  """
@@ -338,6 +344,7 @@ class FluxClusterExecutor(BaseExecutor):
338
344
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
339
345
  debugging purposes and to get an overview of the specified dependencies.
340
346
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
347
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
341
348
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
342
349
 
343
350
  """
@@ -420,6 +427,7 @@ class FluxClusterExecutor(BaseExecutor):
420
427
  refresh_rate=refresh_rate,
421
428
  plot_dependency_graph=plot_dependency_graph,
422
429
  plot_dependency_graph_filename=plot_dependency_graph_filename,
430
+ export_workflow_filename=export_workflow_filename,
423
431
  )
424
432
  )
425
433
 
@@ -58,6 +58,7 @@ class SingleNodeExecutor(BaseExecutor):
58
58
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
59
59
  debugging purposes and to get an overview of the specified dependencies.
60
60
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
61
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
61
62
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
62
63
 
63
64
  Examples:
@@ -94,6 +95,7 @@ class SingleNodeExecutor(BaseExecutor):
94
95
  refresh_rate: float = 0.01,
95
96
  plot_dependency_graph: bool = False,
96
97
  plot_dependency_graph_filename: Optional[str] = None,
98
+ export_workflow_filename: Optional[str] = None,
97
99
  log_obj_size: bool = False,
98
100
  ):
99
101
  """
@@ -138,6 +140,7 @@ class SingleNodeExecutor(BaseExecutor):
138
140
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
139
141
  debugging purposes and to get an overview of the specified dependencies.
140
142
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
143
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
141
144
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
142
145
 
143
146
  """
@@ -171,6 +174,7 @@ class SingleNodeExecutor(BaseExecutor):
171
174
  refresh_rate=refresh_rate,
172
175
  plot_dependency_graph=plot_dependency_graph,
173
176
  plot_dependency_graph_filename=plot_dependency_graph_filename,
177
+ export_workflow_filename=export_workflow_filename,
174
178
  )
175
179
  )
176
180
  else:
@@ -226,6 +230,7 @@ class TestClusterExecutor(BaseExecutor):
226
230
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
227
231
  debugging purposes and to get an overview of the specified dependencies.
228
232
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
233
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
229
234
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
230
235
 
231
236
  Examples:
@@ -262,6 +267,7 @@ class TestClusterExecutor(BaseExecutor):
262
267
  refresh_rate: float = 0.01,
263
268
  plot_dependency_graph: bool = False,
264
269
  plot_dependency_graph_filename: Optional[str] = None,
270
+ export_workflow_filename: Optional[str] = None,
265
271
  log_obj_size: bool = False,
266
272
  ):
267
273
  """
@@ -299,6 +305,7 @@ class TestClusterExecutor(BaseExecutor):
299
305
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
300
306
  debugging purposes and to get an overview of the specified dependencies.
301
307
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
308
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
302
309
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
303
310
 
304
311
  """
@@ -358,6 +365,7 @@ class TestClusterExecutor(BaseExecutor):
358
365
  refresh_rate=refresh_rate,
359
366
  plot_dependency_graph=plot_dependency_graph,
360
367
  plot_dependency_graph_filename=plot_dependency_graph_filename,
368
+ export_workflow_filename=export_workflow_filename,
361
369
  )
362
370
  )
363
371
 
@@ -63,6 +63,7 @@ class SlurmClusterExecutor(BaseExecutor):
63
63
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
64
64
  debugging purposes and to get an overview of the specified dependencies.
65
65
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
66
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
66
67
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
67
68
 
68
69
  Examples:
@@ -101,6 +102,7 @@ class SlurmClusterExecutor(BaseExecutor):
101
102
  refresh_rate: float = 0.01,
102
103
  plot_dependency_graph: bool = False,
103
104
  plot_dependency_graph_filename: Optional[str] = None,
105
+ export_workflow_filename: Optional[str] = None,
104
106
  log_obj_size: bool = False,
105
107
  ):
106
108
  """
@@ -146,6 +148,7 @@ class SlurmClusterExecutor(BaseExecutor):
146
148
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
147
149
  debugging purposes and to get an overview of the specified dependencies.
148
150
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
151
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
149
152
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
150
153
 
151
154
  """
@@ -225,6 +228,7 @@ class SlurmClusterExecutor(BaseExecutor):
225
228
  refresh_rate=refresh_rate,
226
229
  plot_dependency_graph=plot_dependency_graph,
227
230
  plot_dependency_graph_filename=plot_dependency_graph_filename,
231
+ export_workflow_filename=export_workflow_filename,
228
232
  )
229
233
  )
230
234
 
@@ -275,6 +279,7 @@ class SlurmJobExecutor(BaseExecutor):
275
279
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
276
280
  debugging purposes and to get an overview of the specified dependencies.
277
281
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
282
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
278
283
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
279
284
 
280
285
  Examples:
@@ -312,6 +317,7 @@ class SlurmJobExecutor(BaseExecutor):
312
317
  refresh_rate: float = 0.01,
313
318
  plot_dependency_graph: bool = False,
314
319
  plot_dependency_graph_filename: Optional[str] = None,
320
+ export_workflow_filename: Optional[str] = None,
315
321
  log_obj_size: bool = False,
316
322
  ):
317
323
  """
@@ -360,6 +366,7 @@ class SlurmJobExecutor(BaseExecutor):
360
366
  plot_dependency_graph (bool): Plot the dependencies of multiple future objects without executing them. For
361
367
  debugging purposes and to get an overview of the specified dependencies.
362
368
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
369
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
363
370
  log_obj_size (bool): Enable debug mode which reports the size of the communicated objects.
364
371
 
365
372
  """
@@ -394,6 +401,7 @@ class SlurmJobExecutor(BaseExecutor):
394
401
  refresh_rate=refresh_rate,
395
402
  plot_dependency_graph=plot_dependency_graph,
396
403
  plot_dependency_graph_filename=plot_dependency_graph_filename,
404
+ export_workflow_filename=export_workflow_filename,
397
405
  )
398
406
  )
399
407
  else:
@@ -75,7 +75,7 @@ def serialize_funct(
75
75
  "kwargs": fn_kwargs,
76
76
  }
77
77
  )
78
- task_key = fn.__name__ + _get_hash(binary=binary_all)
78
+ task_key = _get_function_name(fn=fn) + _get_hash(binary=binary_all)
79
79
  data = {
80
80
  "fn": fn,
81
81
  "args": fn_args,
@@ -99,3 +99,10 @@ def _get_hash(binary: bytes) -> str:
99
99
  # Remove specification of jupyter kernel from hash to be deterministic
100
100
  binary_no_ipykernel = re.sub(b"(?<=/ipykernel_)(.*)(?=/)", b"", binary)
101
101
  return str(hashlib.md5(binary_no_ipykernel).hexdigest())
102
+
103
+
104
+ def _get_function_name(fn: Callable) -> str:
105
+ if hasattr(fn, "__name__"):
106
+ return fn.__name__
107
+ else:
108
+ return str(fn.__class__).split("'")[-2].split(".")[-1]
@@ -143,6 +143,43 @@ class TaskSchedulerBase(FutureExecutor):
143
143
  )
144
144
  return f
145
145
 
146
+ def map(
147
+ self,
148
+ fn: Callable,
149
+ *iterables,
150
+ timeout: Optional[float] = None,
151
+ chunksize: int = 1,
152
+ ):
153
+ """Returns an iterator equivalent to map(fn, iter).
154
+
155
+ Args:
156
+ fn: A callable that will take as many arguments as there are
157
+ passed iterables.
158
+ timeout: The maximum number of seconds to wait. If None, then there
159
+ is no limit on the wait time.
160
+ chunksize: The size of the chunks the iterable will be broken into
161
+ before being passed to a child process. This argument is only
162
+ used by ProcessPoolExecutor; it is ignored by
163
+ ThreadPoolExecutor.
164
+
165
+ Returns:
166
+ An iterator equivalent to: map(func, *iterables) but the calls may
167
+ be evaluated out-of-order.
168
+
169
+ Raises:
170
+ TimeoutError: If the entire result iterator could not be generated
171
+ before the given timeout.
172
+ Exception: If fn(*args) raises for any values.
173
+ """
174
+ if isinstance(iterables, (list, tuple)) and any(
175
+ isinstance(i, Future) for i in iterables
176
+ ):
177
+ iterables = tuple(
178
+ i.result() if isinstance(i, Future) else i for i in iterables
179
+ )
180
+
181
+ return super().map(fn, *iterables, timeout=timeout, chunksize=chunksize)
182
+
146
183
  def shutdown(self, wait: bool = True, *, cancel_futures: bool = False):
147
184
  """
148
185
  Clean-up the resources associated with the Executor.
@@ -13,6 +13,7 @@ from executorlib.standalone.interactive.arguments import (
13
13
  )
14
14
  from executorlib.task_scheduler.base import TaskSchedulerBase
15
15
  from executorlib.task_scheduler.interactive.dependency_plot import (
16
+ export_dependency_graph_function,
16
17
  generate_nodes_and_edges_for_plotting,
17
18
  generate_task_hash_for_plotting,
18
19
  plot_dependency_graph_function,
@@ -28,6 +29,7 @@ class DependencyTaskScheduler(TaskSchedulerBase):
28
29
  refresh_rate (float, optional): The refresh rate for updating the executor queue. Defaults to 0.01.
29
30
  plot_dependency_graph (bool, optional): Whether to generate and plot the dependency graph. Defaults to False.
30
31
  plot_dependency_graph_filename (str): Name of the file to store the plotted graph in.
32
+ export_workflow_filename (str): Name of the file to store the exported workflow graph in.
31
33
 
32
34
  Attributes:
33
35
  _future_hash_dict (Dict[str, Future]): A dictionary mapping task hash to future object.
@@ -44,6 +46,7 @@ class DependencyTaskScheduler(TaskSchedulerBase):
44
46
  refresh_rate: float = 0.01,
45
47
  plot_dependency_graph: bool = False,
46
48
  plot_dependency_graph_filename: Optional[str] = None,
49
+ export_workflow_filename: Optional[str] = None,
47
50
  ) -> None:
48
51
  super().__init__(max_cores=max_cores)
49
52
  self._process_kwargs = {
@@ -61,7 +64,8 @@ class DependencyTaskScheduler(TaskSchedulerBase):
61
64
  self._future_hash_dict: dict = {}
62
65
  self._task_hash_dict: dict = {}
63
66
  self._plot_dependency_graph_filename = plot_dependency_graph_filename
64
- if plot_dependency_graph_filename is None:
67
+ self._export_workflow_filename = export_workflow_filename
68
+ if plot_dependency_graph_filename is None and export_workflow_filename is None:
65
69
  self._generate_dependency_graph = plot_dependency_graph
66
70
  else:
67
71
  self._generate_dependency_graph = True
@@ -209,11 +213,18 @@ class DependencyTaskScheduler(TaskSchedulerBase):
209
213
  v: k for k, v in self._future_hash_dict.items()
210
214
  },
211
215
  )
212
- return plot_dependency_graph_function(
213
- node_lst=node_lst,
214
- edge_lst=edge_lst,
215
- filename=self._plot_dependency_graph_filename,
216
- )
216
+ if self._export_workflow_filename is not None:
217
+ return export_dependency_graph_function(
218
+ node_lst=node_lst,
219
+ edge_lst=edge_lst,
220
+ file_name=self._export_workflow_filename,
221
+ )
222
+ else:
223
+ return plot_dependency_graph_function(
224
+ node_lst=node_lst,
225
+ edge_lst=edge_lst,
226
+ filename=self._plot_dependency_graph_filename,
227
+ )
217
228
  else:
218
229
  return None
219
230
 
@@ -1,8 +1,11 @@
1
+ import inspect
2
+ import json
1
3
  import os.path
2
4
  from concurrent.futures import Future
3
5
  from typing import Optional
4
6
 
5
7
  import cloudpickle
8
+ import numpy as np
6
9
 
7
10
  from executorlib.standalone.select import FutureSelector
8
11
 
@@ -24,6 +27,12 @@ def generate_nodes_and_edges_for_plotting(
24
27
  edge_lst: list = []
25
28
  hash_id_dict: dict = {}
26
29
 
30
+ def extend_args(funct_dict):
31
+ sig = inspect.signature(funct_dict["fn"])
32
+ args = sig.bind(*funct_dict["args"], **funct_dict["kwargs"])
33
+ funct_dict["signature"] = args.arguments
34
+ return funct_dict
35
+
27
36
  def add_element(arg, link_to, label=""):
28
37
  """
29
38
  Add element to the node and edge lists.
@@ -39,6 +48,8 @@ def generate_nodes_and_edges_for_plotting(
39
48
  "start": hash_id_dict[future_hash_inverse_dict[arg._future]],
40
49
  "end": link_to,
41
50
  "label": label + str(arg._selector),
51
+ "end_label": label,
52
+ "start_label": str(arg._selector),
42
53
  }
43
54
  )
44
55
  elif isinstance(arg, Future):
@@ -53,39 +64,71 @@ def generate_nodes_and_edges_for_plotting(
53
64
  lst_no_future = [a if not isinstance(a, Future) else "$" for a in arg]
54
65
  node_id = len(node_lst)
55
66
  node_lst.append(
56
- {"name": str(lst_no_future), "id": node_id, "shape": "circle"}
67
+ {
68
+ "name": str(lst_no_future),
69
+ "value": "python_workflow_definition.shared.get_list",
70
+ "id": node_id,
71
+ "type": "function",
72
+ "shape": "box",
73
+ }
57
74
  )
58
75
  edge_lst.append({"start": node_id, "end": link_to, "label": label})
59
76
  for i, a in enumerate(arg):
60
77
  if isinstance(a, Future):
61
- add_element(arg=a, link_to=node_id, label="ind: " + str(i))
78
+ add_element(arg=a, link_to=node_id, label=str(i))
62
79
  elif isinstance(arg, dict) and any(isinstance(a, Future) for a in arg.values()):
63
80
  dict_no_future = {
64
81
  kt: vt if not isinstance(vt, Future) else "$" for kt, vt in arg.items()
65
82
  }
66
83
  node_id = len(node_lst)
67
84
  node_lst.append(
68
- {"name": str(dict_no_future), "id": node_id, "shape": "circle"}
85
+ {
86
+ "name": str(dict_no_future),
87
+ "value": "python_workflow_definition.shared.get_dict",
88
+ "id": node_id,
89
+ "type": "function",
90
+ "shape": "box",
91
+ }
69
92
  )
70
93
  edge_lst.append({"start": node_id, "end": link_to, "label": label})
71
94
  for kt, vt in arg.items():
72
- if isinstance(vt, Future):
73
- add_element(arg=vt, link_to=node_id, label="key: " + kt)
95
+ add_element(arg=vt, link_to=node_id, label=kt)
74
96
  else:
75
- node_id = len(node_lst)
76
- node_lst.append({"name": str(arg), "id": node_id, "shape": "circle"})
97
+ value_dict = {
98
+ str(n["value"]): n["id"] for n in node_lst if n["type"] == "input"
99
+ }
100
+ if str(arg) not in value_dict:
101
+ node_id = len(node_lst)
102
+ node_lst.append(
103
+ {
104
+ "name": label,
105
+ "value": arg,
106
+ "id": node_id,
107
+ "type": "input",
108
+ "shape": "circle",
109
+ }
110
+ )
111
+ else:
112
+ node_id = value_dict[str(arg)]
77
113
  edge_lst.append({"start": node_id, "end": link_to, "label": label})
78
114
 
79
- for k, v in task_hash_dict.items():
115
+ task_hash_modified_dict = {
116
+ k: extend_args(funct_dict=v) for k, v in task_hash_dict.items()
117
+ }
118
+
119
+ for k, v in task_hash_modified_dict.items():
80
120
  hash_id_dict[k] = len(node_lst)
81
121
  node_lst.append(
82
- {"name": v["fn"].__name__, "id": hash_id_dict[k], "shape": "box"}
122
+ {
123
+ "name": v["fn"].__name__,
124
+ "type": "function",
125
+ "value": v["fn"].__module__ + "." + v["fn"].__name__,
126
+ "id": hash_id_dict[k],
127
+ "shape": "box",
128
+ }
83
129
  )
84
- for k, task_dict in task_hash_dict.items():
85
- for arg in task_dict["args"]:
86
- add_element(arg=arg, link_to=hash_id_dict[k], label="")
87
-
88
- for kw, v in task_dict["kwargs"].items():
130
+ for k, task_dict in task_hash_modified_dict.items():
131
+ for kw, v in task_dict["signature"].items():
89
132
  add_element(arg=v, link_to=hash_id_dict[k], label=str(kw))
90
133
 
91
134
  return node_lst, edge_lst
@@ -175,7 +218,10 @@ def plot_dependency_graph_function(
175
218
 
176
219
  graph = nx.DiGraph()
177
220
  for node in node_lst:
178
- graph.add_node(node["id"], label=node["name"], shape=node["shape"])
221
+ if node["type"] == "input":
222
+ graph.add_node(node["id"], label=str(node["value"]), shape=node["shape"])
223
+ else:
224
+ graph.add_node(node["id"], label=str(node["name"]), shape=node["shape"])
179
225
  for edge in edge_lst:
180
226
  graph.add_edge(edge["start"], edge["end"], label=edge["label"])
181
227
  if filename is not None:
@@ -186,3 +232,76 @@ def plot_dependency_graph_function(
186
232
  from IPython.display import SVG, display # noqa
187
233
 
188
234
  display(SVG(nx.nx_agraph.to_agraph(graph).draw(prog="dot", format="svg")))
235
+
236
+
237
+ def export_dependency_graph_function(
238
+ node_lst: list, edge_lst: list, file_name: str = "workflow.json"
239
+ ):
240
+ """
241
+ Export the graph visualization of nodes and edges as a JSON dictionary.
242
+
243
+ Args:
244
+ node_lst (list): List of nodes.
245
+ edge_lst (list): List of edges.
246
+ file_name (str): Name of the file to store the exported graph in.
247
+ """
248
+ pwd_nodes_lst = []
249
+ for n in node_lst:
250
+ if n["type"] == "function":
251
+ pwd_nodes_lst.append(
252
+ {"id": n["id"], "type": n["type"], "value": n["value"]}
253
+ )
254
+ elif n["type"] == "input" and isinstance(n["value"], np.ndarray):
255
+ pwd_nodes_lst.append(
256
+ {
257
+ "id": n["id"],
258
+ "type": n["type"],
259
+ "value": n["value"].tolist(),
260
+ "name": n["name"],
261
+ }
262
+ )
263
+ else:
264
+ pwd_nodes_lst.append(
265
+ {
266
+ "id": n["id"],
267
+ "type": n["type"],
268
+ "value": n["value"],
269
+ "name": n["name"],
270
+ }
271
+ )
272
+
273
+ final_node = {"id": len(pwd_nodes_lst), "type": "output", "name": "result"}
274
+ pwd_nodes_lst.append(final_node)
275
+ pwd_edges_lst = [
276
+ (
277
+ {
278
+ "target": e["end"],
279
+ "targetPort": e["label"],
280
+ "source": e["start"],
281
+ "sourcePort": None,
282
+ }
283
+ if "start_label" not in e
284
+ else {
285
+ "target": e["end"],
286
+ "targetPort": e["end_label"],
287
+ "source": e["start"],
288
+ "sourcePort": e["start_label"],
289
+ }
290
+ )
291
+ for e in edge_lst
292
+ ]
293
+ pwd_edges_lst.append(
294
+ {
295
+ "target": final_node["id"],
296
+ "targetPort": None,
297
+ "source": max([e["target"] for e in pwd_edges_lst]),
298
+ "sourcePort": None,
299
+ }
300
+ )
301
+ pwd_dict = {
302
+ "version": "0.1.0",
303
+ "nodes": pwd_nodes_lst,
304
+ "edges": pwd_edges_lst,
305
+ }
306
+ with open(file_name, "w") as f:
307
+ json.dump(pwd_dict, f, indent=4)
File without changes
File without changes