parsl 2023.10.23__py3-none-any.whl → 2023.11.20__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.
Files changed (58) hide show
  1. parsl/__init__.py +1 -0
  2. parsl/app/app.py +29 -21
  3. parsl/channels/base.py +12 -24
  4. parsl/config.py +19 -12
  5. parsl/configs/ad_hoc.py +2 -2
  6. parsl/dataflow/dflow.py +10 -4
  7. parsl/executors/base.py +1 -3
  8. parsl/executors/high_throughput/executor.py +3 -3
  9. parsl/executors/high_throughput/interchange.py +59 -53
  10. parsl/executors/high_throughput/process_worker_pool.py +2 -2
  11. parsl/executors/high_throughput/zmq_pipes.py +1 -1
  12. parsl/executors/radical/__init__.py +4 -0
  13. parsl/executors/radical/executor.py +550 -0
  14. parsl/executors/radical/rpex_master.py +42 -0
  15. parsl/executors/radical/rpex_resources.py +165 -0
  16. parsl/executors/radical/rpex_worker.py +61 -0
  17. parsl/executors/status_handling.py +1 -2
  18. parsl/executors/taskvine/exec_parsl_function.py +3 -4
  19. parsl/executors/taskvine/executor.py +18 -4
  20. parsl/executors/taskvine/factory.py +1 -1
  21. parsl/executors/taskvine/manager.py +12 -16
  22. parsl/executors/taskvine/utils.py +5 -5
  23. parsl/executors/threads.py +1 -2
  24. parsl/executors/workqueue/exec_parsl_function.py +2 -1
  25. parsl/executors/workqueue/executor.py +34 -24
  26. parsl/jobs/job_status_poller.py +2 -3
  27. parsl/monitoring/monitoring.py +6 -6
  28. parsl/monitoring/remote.py +1 -1
  29. parsl/monitoring/visualization/plots/default/workflow_plots.py +4 -4
  30. parsl/monitoring/visualization/plots/default/workflow_resource_plots.py +2 -2
  31. parsl/providers/slurm/slurm.py +1 -1
  32. parsl/tests/configs/ad_hoc_cluster_htex.py +3 -3
  33. parsl/tests/configs/htex_ad_hoc_cluster.py +1 -1
  34. parsl/tests/configs/local_radical.py +20 -0
  35. parsl/tests/configs/local_radical_mpi.py +20 -0
  36. parsl/tests/configs/local_threads_monitoring.py +1 -1
  37. parsl/tests/conftest.py +6 -2
  38. parsl/tests/scaling_tests/vineex_condor.py +1 -1
  39. parsl/tests/scaling_tests/vineex_local.py +1 -1
  40. parsl/tests/scaling_tests/wqex_condor.py +1 -1
  41. parsl/tests/scaling_tests/wqex_local.py +1 -1
  42. parsl/tests/test_docs/test_kwargs.py +37 -0
  43. parsl/tests/test_python_apps/test_garbage_collect.py +1 -1
  44. parsl/tests/test_python_apps/test_lifted.py +3 -2
  45. parsl/tests/test_radical/__init__.py +0 -0
  46. parsl/tests/test_radical/test_mpi_funcs.py +27 -0
  47. parsl/tests/test_regression/test_1606_wait_for_current_tasks.py +1 -1
  48. parsl/utils.py +4 -4
  49. parsl/version.py +1 -1
  50. {parsl-2023.10.23.data → parsl-2023.11.20.data}/scripts/exec_parsl_function.py +2 -1
  51. {parsl-2023.10.23.data → parsl-2023.11.20.data}/scripts/process_worker_pool.py +2 -2
  52. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/METADATA +5 -2
  53. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/RECORD +58 -48
  54. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/WHEEL +1 -1
  55. {parsl-2023.10.23.data → parsl-2023.11.20.data}/scripts/parsl_coprocess.py +0 -0
  56. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/LICENSE +0 -0
  57. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/entry_points.txt +0 -0
  58. {parsl-2023.10.23.dist-info → parsl-2023.11.20.dist-info}/top_level.txt +0 -0
parsl/__init__.py CHANGED
@@ -62,6 +62,7 @@ __all__ = [
62
62
  'ThreadPoolExecutor',
63
63
  'HighThroughputExecutor',
64
64
  'WorkQueueExecutor',
65
+ 'RadicalPilotExecutor',
65
66
 
66
67
  # monitoring
67
68
  'MonitoringHub',
parsl/app/app.py CHANGED
@@ -6,11 +6,16 @@ import logging
6
6
  import typeguard
7
7
  from abc import ABCMeta, abstractmethod
8
8
  from inspect import signature
9
- from typing import List, Optional, Union
9
+ from typing import List, Optional, Sequence, Union
10
10
  from typing_extensions import Literal
11
11
 
12
12
  from parsl.dataflow.dflow import DataFlowKernel
13
13
 
14
+ from typing import Any, Callable, Dict
15
+
16
+ from parsl.dataflow.futures import AppFuture
17
+
18
+
14
19
  logger = logging.getLogger(__name__)
15
20
 
16
21
 
@@ -22,7 +27,12 @@ class AppBase(metaclass=ABCMeta):
22
27
 
23
28
  """
24
29
 
25
- def __init__(self, func, data_flow_kernel=None, executors='all', cache=False, ignore_for_cache=None):
30
+ @typeguard.typechecked
31
+ def __init__(self, func: Callable,
32
+ data_flow_kernel: Optional[DataFlowKernel] = None,
33
+ executors: Union[List[str], Literal['all']] = 'all',
34
+ cache: bool = False,
35
+ ignore_for_cache: Optional[Sequence[str]] = None) -> None:
26
36
  """Construct the App object.
27
37
 
28
38
  Args:
@@ -34,7 +44,7 @@ class AppBase(metaclass=ABCMeta):
34
44
  after calling :meth:`parsl.dataflow.dflow.DataFlowKernelLoader.load`.
35
45
  - executors (str|list) : Labels of the executors that this app can execute over. Default is 'all'.
36
46
  - cache (Bool) : Enable caching of this app ?
37
- - ignore_for_cache (list|None): Names of arguments which will be ignored by the caching mechanism.
47
+ - ignore_for_cache (sequence|None): Names of arguments which will be ignored by the caching mechanism.
38
48
 
39
49
  Returns:
40
50
  - App object.
@@ -46,12 +56,10 @@ class AppBase(metaclass=ABCMeta):
46
56
  self.executors = executors
47
57
  self.cache = cache
48
58
  self.ignore_for_cache = ignore_for_cache
49
- if not (isinstance(executors, list) or isinstance(executors, str)):
50
- logger.error("App {} specifies invalid executor option, expects string or list".format(
51
- func.__name__))
52
59
 
53
60
  params = signature(func).parameters
54
61
 
62
+ self.kwargs: Dict[str, Any]
55
63
  self.kwargs = {}
56
64
  if 'stdout' in params:
57
65
  self.kwargs['stdout'] = params['stdout'].default
@@ -65,16 +73,16 @@ class AppBase(metaclass=ABCMeta):
65
73
  self.inputs = params['inputs'].default if 'inputs' in params else []
66
74
 
67
75
  @abstractmethod
68
- def __call__(self, *args, **kwargs):
76
+ def __call__(self, *args: Any, **kwargs: Any) -> AppFuture:
69
77
  pass
70
78
 
71
79
 
72
80
  @typeguard.typechecked
73
- def python_app(function=None,
81
+ def python_app(function: Optional[Callable] = None,
74
82
  data_flow_kernel: Optional[DataFlowKernel] = None,
75
83
  cache: bool = False,
76
84
  executors: Union[List[str], Literal['all']] = 'all',
77
- ignore_for_cache: Optional[List[str]] = None):
85
+ ignore_for_cache: Optional[Sequence[str]] = None) -> Callable:
78
86
  """Decorator function for making python apps.
79
87
 
80
88
  Parameters
@@ -91,13 +99,13 @@ def python_app(function=None,
91
99
  Labels of the executors that this app can execute over. Default is 'all'.
92
100
  cache : bool
93
101
  Enable caching of the app call. Default is False.
94
- ignore_for_cache : (list|None)
102
+ ignore_for_cache : (sequence|None)
95
103
  Names of arguments which will be ignored by the caching mechanism.
96
104
  """
97
105
  from parsl.app.python import PythonApp
98
106
 
99
- def decorator(func):
100
- def wrapper(f):
107
+ def decorator(func: Callable) -> Callable:
108
+ def wrapper(f: Callable) -> PythonApp:
101
109
  return PythonApp(f,
102
110
  data_flow_kernel=data_flow_kernel,
103
111
  cache=cache,
@@ -111,10 +119,10 @@ def python_app(function=None,
111
119
 
112
120
 
113
121
  @typeguard.typechecked
114
- def join_app(function=None,
122
+ def join_app(function: Optional[Callable] = None,
115
123
  data_flow_kernel: Optional[DataFlowKernel] = None,
116
124
  cache: bool = False,
117
- ignore_for_cache: Optional[List[str]] = None):
125
+ ignore_for_cache: Optional[Sequence[str]] = None) -> Callable:
118
126
  """Decorator function for making join apps
119
127
 
120
128
  Parameters
@@ -129,13 +137,13 @@ def join_app(function=None,
129
137
  be omitted only after calling :meth:`parsl.dataflow.dflow.DataFlowKernelLoader.load`. Default is None.
130
138
  cache : bool
131
139
  Enable caching of the app call. Default is False.
132
- ignore_for_cache : (list|None)
140
+ ignore_for_cache : (sequence|None)
133
141
  Names of arguments which will be ignored by the caching mechanism.
134
142
  """
135
143
  from parsl.app.python import PythonApp
136
144
 
137
- def decorator(func):
138
- def wrapper(f):
145
+ def decorator(func: Callable) -> Callable:
146
+ def wrapper(f: Callable) -> PythonApp:
139
147
  return PythonApp(f,
140
148
  data_flow_kernel=data_flow_kernel,
141
149
  cache=cache,
@@ -149,11 +157,11 @@ def join_app(function=None,
149
157
 
150
158
 
151
159
  @typeguard.typechecked
152
- def bash_app(function=None,
160
+ def bash_app(function: Optional[Callable] = None,
153
161
  data_flow_kernel: Optional[DataFlowKernel] = None,
154
162
  cache: bool = False,
155
163
  executors: Union[List[str], Literal['all']] = 'all',
156
- ignore_for_cache: Optional[List[str]] = None):
164
+ ignore_for_cache: Optional[Sequence[str]] = None) -> Callable:
157
165
  """Decorator function for making bash apps.
158
166
 
159
167
  Parameters
@@ -177,8 +185,8 @@ def bash_app(function=None,
177
185
  """
178
186
  from parsl.app.bash import BashApp
179
187
 
180
- def decorator(func):
181
- def wrapper(f):
188
+ def decorator(func: Callable) -> Callable:
189
+ def wrapper(f: Callable) -> BashApp:
182
190
  return BashApp(f,
183
191
  data_flow_kernel=data_flow_kernel,
184
192
  cache=cache,
parsl/channels/base.py CHANGED
@@ -4,33 +4,21 @@ from typing import Dict, Tuple
4
4
 
5
5
 
6
6
  class Channel(metaclass=ABCMeta):
7
- """For certain resources such as campus clusters or supercomputers at
7
+ """Channels are abstractions that enable ExecutionProviders to talk to
8
+ resource managers of remote compute facilities.
9
+
10
+ For certain resources such as campus clusters or supercomputers at
8
11
  research laboratories, resource requirements may require authentication.
9
12
  For instance some resources may allow access to their job schedulers from
10
- only their login-nodes which require you to authenticate on through SSH,
11
- GSI-SSH and sometimes even require two factor authentication. Channels are
12
- simple abstractions that enable the ExecutionProvider component to talk to
13
- the resource managers of compute facilities. The simplest Channel,
14
- *LocalChannel*, simply executes commands locally on a shell, while the
15
- *SshChannel* authenticates you to remote systems.
16
-
17
- Channels are usually called via the execute_wait function.
18
- For channels that execute remotely, a push_file function allows you to copy over files.
19
-
20
- .. code:: python
21
-
22
- +------------------
23
- |
24
- cmd, wtime ------->| execute_wait
25
- (ec, stdout, stderr)<-|---+
26
- |
27
- src, dst_dir ------->| push_file
28
- dst_path <--------|----+
29
- |
30
- dst_script_dir <------| script_dir
31
- |
32
- +-------------------
13
+ only their login-nodes which require you to authenticate through SSH, or
14
+ require two factor authentication.
15
+
16
+ The simplest Channel, *LocalChannel*, executes commands locally in a
17
+ shell, while the *SSHChannel* authenticates you to remote systems.
33
18
 
19
+ Channels provide the ability to execute commands remotely, using the
20
+ execute_wait method, and manipulate the remote file system using methods
21
+ such as push_file, pull_file and makedirs.
34
22
 
35
23
  Channels should ensure that each launched command runs in a new process
36
24
  group, so that providers (such as AdHocProvider and LocalProvider) which
parsl/config.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  import typeguard
3
3
 
4
- from typing import Callable, List, Optional, Sequence, Union
4
+ from typing import Callable, Iterable, Optional, Sequence, Union
5
5
  from typing_extensions import Literal
6
6
 
7
7
  from parsl.utils import RepresentationMixin
@@ -20,9 +20,9 @@ class Config(RepresentationMixin):
20
20
 
21
21
  Parameters
22
22
  ----------
23
- executors : list of ParslExecutor, optional
24
- List of `ParslExecutor` instances to use for executing tasks.
25
- Default is [:class:`~parsl.executors.threads.ThreadPoolExecutor()`].
23
+ executors : sequence of ParslExecutor, optional
24
+ List (or other iterable) of `ParslExecutor` instances to use for executing tasks.
25
+ Default is (:class:`~parsl.executors.threads.ThreadPoolExecutor()`,).
26
26
  app_cache : bool, optional
27
27
  Enable app caching. Default is True.
28
28
  checkpoint_files : sequence of str, optional
@@ -73,7 +73,7 @@ class Config(RepresentationMixin):
73
73
 
74
74
  @typeguard.typechecked
75
75
  def __init__(self,
76
- executors: Optional[List[ParslExecutor]] = None,
76
+ executors: Optional[Iterable[ParslExecutor]] = None,
77
77
  app_cache: bool = True,
78
78
  checkpoint_files: Optional[Sequence[str]] = None,
79
79
  checkpoint_mode: Union[None,
@@ -92,9 +92,14 @@ class Config(RepresentationMixin):
92
92
  monitoring: Optional[MonitoringHub] = None,
93
93
  usage_tracking: bool = False,
94
94
  initialize_logging: bool = True) -> None:
95
- if executors is None:
96
- executors = [ThreadPoolExecutor()]
97
- self.executors = executors
95
+
96
+ executors = tuple(executors or [])
97
+ if not executors:
98
+ executors = (ThreadPoolExecutor(),)
99
+
100
+ self._executors: Sequence[ParslExecutor] = executors
101
+ self._validate_executors()
102
+
98
103
  self.app_cache = app_cache
99
104
  self.checkpoint_files = checkpoint_files
100
105
  self.checkpoint_mode = checkpoint_mode
@@ -125,11 +130,13 @@ class Config(RepresentationMixin):
125
130
  def executors(self) -> Sequence[ParslExecutor]:
126
131
  return self._executors
127
132
 
128
- @executors.setter
129
- def executors(self, executors: Sequence[ParslExecutor]):
130
- labels = [e.label for e in executors]
133
+ def _validate_executors(self) -> None:
134
+
135
+ if len(self.executors) == 0:
136
+ raise ConfigurationError('At least one executor must be specified')
137
+
138
+ labels = [e.label for e in self.executors]
131
139
  duplicates = [e for n, e in enumerate(labels) if e in labels[:n]]
132
140
  if len(duplicates) > 0:
133
141
  raise ConfigurationError('Executors must have unique labels ({})'.format(
134
142
  ', '.join(['label={}'.format(repr(d)) for d in duplicates])))
135
- self._executors = executors
parsl/configs/ad_hoc.py CHANGED
@@ -9,8 +9,8 @@ user_opts = {'adhoc':
9
9
  {'username': 'YOUR_USERNAME',
10
10
  'script_dir': 'YOUR_SCRIPT_DIR',
11
11
  'remote_hostnames': ['REMOTE_HOST_URL_1', 'REMOTE_HOST_URL_2']
12
+ }
12
13
  }
13
- }
14
14
 
15
15
 
16
16
  config = Config(
@@ -26,7 +26,7 @@ config = Config(
26
26
  channels=[SSHChannel(hostname=m,
27
27
  username=user_opts['adhoc']['username'],
28
28
  script_dir=user_opts['adhoc']['script_dir'],
29
- ) for m in user_opts['adhoc']['remote_hostnames']]
29
+ ) for m in user_opts['adhoc']['remote_hostnames']]
30
30
  )
31
31
  )
32
32
  ],
parsl/dataflow/dflow.py CHANGED
@@ -918,7 +918,7 @@ class DataFlowKernel:
918
918
  - executors (list or string) : List of executors this call could go to.
919
919
  Default='all'
920
920
  - cache (Bool) : To enable memoization or not
921
- - ignore_for_cache (list) : List of kwargs to be ignored for memoization/checkpointing
921
+ - ignore_for_cache (sequence) : List of kwargs to be ignored for memoization/checkpointing
922
922
  - app_kwargs (dict) : Rest of the kwargs to the fn passed as dict.
923
923
 
924
924
  Returns:
@@ -984,6 +984,7 @@ class DataFlowKernel:
984
984
  'joins': None,
985
985
  'try_id': 0,
986
986
  'id': task_id,
987
+ 'task_launch_lock': threading.Lock(),
987
988
  'time_invoked': datetime.datetime.now(),
988
989
  'time_returned': None,
989
990
  'try_time_launched': None,
@@ -1029,8 +1030,6 @@ class DataFlowKernel:
1029
1030
  task_record['func_name'],
1030
1031
  waiting_message))
1031
1032
 
1032
- task_record['task_launch_lock'] = threading.Lock()
1033
-
1034
1033
  app_fu.add_done_callback(partial(self.handle_app_update, task_record))
1035
1034
  self.update_task_state(task_record, States.pending)
1036
1035
  logger.debug("Task {} set to pending state with AppFuture: {}".format(task_id, task_record['app_fu']))
@@ -1091,7 +1090,14 @@ class DataFlowKernel:
1091
1090
  """
1092
1091
  run_dir = self.run_dir
1093
1092
  if channel.script_dir is None:
1094
- channel.script_dir = os.path.join(run_dir, 'submit_scripts')
1093
+
1094
+ # This case will be detected as unreachable by mypy, because of
1095
+ # the type of script_dir, which is str, not Optional[str].
1096
+ # The type system doesn't represent the initialized/uninitialized
1097
+ # state of a channel so cannot represent that a channel needs
1098
+ # its script directory set or not.
1099
+
1100
+ channel.script_dir = os.path.join(run_dir, 'submit_scripts') # type: ignore[unreachable]
1095
1101
 
1096
1102
  # Only create dirs if we aren't on a shared-fs
1097
1103
  if not channel.isdir(run_dir):
parsl/executors/base.py CHANGED
@@ -5,8 +5,6 @@ from typing_extensions import Literal, Self
5
5
 
6
6
  from parsl.jobs.states import JobStatus
7
7
 
8
- import parsl # noqa F401
9
-
10
8
 
11
9
  class ParslExecutor(metaclass=ABCMeta):
12
10
  """Executors are abstractions that represent available compute resources
@@ -74,7 +72,7 @@ class ParslExecutor(metaclass=ABCMeta):
74
72
  pass
75
73
 
76
74
  @abstractmethod
77
- def shutdown(self) -> bool:
75
+ def shutdown(self) -> None:
78
76
  """Shutdown the executor.
79
77
 
80
78
  This includes all attached resources such as workers and controllers.
@@ -8,7 +8,7 @@ import datetime
8
8
  import pickle
9
9
  import warnings
10
10
  from multiprocessing import Queue
11
- from typing import Dict, Sequence # noqa F401 (used in type annotation)
11
+ from typing import Dict, Sequence
12
12
  from typing import List, Optional, Tuple, Union, Callable
13
13
  import math
14
14
 
@@ -479,10 +479,10 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin):
479
479
  "heartbeat_threshold": self.heartbeat_threshold,
480
480
  "poll_period": self.poll_period,
481
481
  "logging_level": logging.DEBUG if self.worker_debug else logging.INFO
482
- },
482
+ },
483
483
  daemon=True,
484
484
  name="HTEX-Interchange"
485
- )
485
+ )
486
486
  self.interchange_proc.start()
487
487
  try:
488
488
  (self.worker_task_port, self.worker_result_port) = comm_q.get(block=True, timeout=120)