parsl 2024.5.20__py3-none-any.whl → 2024.5.27__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 (55) hide show
  1. parsl/config.py +7 -1
  2. parsl/dataflow/dependency_resolvers.py +115 -0
  3. parsl/dataflow/dflow.py +44 -38
  4. parsl/executors/high_throughput/errors.py +10 -0
  5. parsl/executors/high_throughput/executor.py +2 -1
  6. parsl/executors/high_throughput/mpi_executor.py +1 -1
  7. parsl/executors/high_throughput/mpi_prefix_composer.py +18 -2
  8. parsl/executors/high_throughput/zmq_pipes.py +36 -2
  9. parsl/executors/radical/rpex_resources.py +3 -7
  10. parsl/tests/conftest.py +2 -2
  11. parsl/tests/sites/test_dynamic_executor.py +0 -1
  12. parsl/tests/test_bash_apps/test_std_uri.py +0 -6
  13. parsl/tests/test_checkpointing/test_periodic.py +2 -7
  14. parsl/tests/test_checkpointing/test_python_checkpoint_2.py +0 -1
  15. parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -1
  16. parsl/tests/test_checkpointing/test_task_exit.py +0 -1
  17. parsl/tests/test_htex/test_basic.py +0 -1
  18. parsl/tests/test_htex/test_command_client_timeout.py +69 -0
  19. parsl/tests/test_htex/test_cpu_affinity_explicit.py +1 -8
  20. parsl/tests/test_htex/test_manager_failure.py +0 -1
  21. parsl/tests/test_htex/test_managers_command.py +2 -7
  22. parsl/tests/test_htex/test_missing_worker.py +2 -8
  23. parsl/tests/test_monitoring/test_app_names.py +0 -1
  24. parsl/tests/test_monitoring/test_basic.py +0 -2
  25. parsl/tests/test_monitoring/test_db_locks.py +0 -1
  26. parsl/tests/test_monitoring/test_fuzz_zmq.py +0 -1
  27. parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +0 -2
  28. parsl/tests/test_monitoring/test_incomplete_futures.py +0 -1
  29. parsl/tests/test_monitoring/test_memoization_representation.py +0 -1
  30. parsl/tests/test_monitoring/test_stdouterr.py +0 -2
  31. parsl/tests/test_mpi_apps/test_mpi_mode_disabled.py +2 -7
  32. parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py +10 -1
  33. parsl/tests/test_mpi_apps/test_resource_spec.py +14 -9
  34. parsl/tests/test_python_apps/test_context_manager.py +1 -9
  35. parsl/tests/test_python_apps/test_lifted.py +10 -6
  36. parsl/tests/test_python_apps/test_pluggable_future_resolution.py +161 -0
  37. parsl/tests/test_scaling/test_regression_1621.py +0 -2
  38. parsl/tests/test_scaling/test_shutdown_scalein.py +0 -2
  39. parsl/tests/test_serialization/test_proxystore_configured.py +0 -1
  40. parsl/tests/test_shutdown/test_kill_monitoring.py +0 -2
  41. parsl/tests/test_staging/test_1316.py +0 -2
  42. parsl/tests/test_staging/test_elaborate_noop_file.py +0 -1
  43. parsl/tests/test_summary.py +0 -1
  44. parsl/tests/test_threads/test_configs.py +0 -1
  45. parsl/tests/test_threads/test_lazy_errors.py +0 -1
  46. parsl/version.py +1 -1
  47. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/METADATA +6 -6
  48. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/RECORD +55 -52
  49. {parsl-2024.5.20.data → parsl-2024.5.27.data}/scripts/exec_parsl_function.py +0 -0
  50. {parsl-2024.5.20.data → parsl-2024.5.27.data}/scripts/parsl_coprocess.py +0 -0
  51. {parsl-2024.5.20.data → parsl-2024.5.27.data}/scripts/process_worker_pool.py +0 -0
  52. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/LICENSE +0 -0
  53. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/WHEEL +0 -0
  54. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/entry_points.txt +0 -0
  55. {parsl-2024.5.20.dist-info → parsl-2024.5.27.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,6 @@ def load_config():
20
20
  yield
21
21
 
22
22
  parsl.dfk().cleanup()
23
- parsl.clear()
24
23
 
25
24
 
26
25
  @python_app
@@ -7,16 +7,11 @@ from parsl.app.app import python_app
7
7
  from parsl.tests.configs.htex_local import fresh_config
8
8
 
9
9
 
10
- def local_setup():
10
+ def local_config():
11
11
  config = fresh_config()
12
12
  config.executors[0].poll_period = 1
13
13
  config.executors[0].max_workers_per_node = 1
14
- parsl.load(config)
15
-
16
-
17
- def local_teardown():
18
- parsl.dfk().cleanup()
19
- parsl.clear()
14
+ return config
20
15
 
21
16
 
22
17
  @python_app
@@ -5,18 +5,12 @@ from parsl.app.app import python_app
5
5
  from parsl.tests.configs.htex_local import fresh_config
6
6
 
7
7
 
8
- def local_setup():
8
+ def local_config():
9
9
  config = fresh_config()
10
10
  config.executors[0].poll_period = 1
11
11
  config.executors[0].max_workers_per_node = 1
12
12
  config.executors[0].launch_cmd = "executable_that_hopefully_does_not_exist_1030509.py"
13
- parsl.load(config)
14
-
15
-
16
- def local_teardown():
17
-
18
- parsl.dfk().cleanup()
19
- parsl.clear()
13
+ return config
20
14
 
21
15
 
22
16
  @python_app
@@ -61,7 +61,6 @@ def test_app_name(get_app, expected_name, expected_result, tmpd_cwd):
61
61
  assert app().result() == expected_result
62
62
 
63
63
  parsl.dfk().cleanup()
64
- parsl.clear()
65
64
 
66
65
  engine = sqlalchemy.create_engine(c.monitoring.logging_endpoint)
67
66
  with engine.begin() as connection:
@@ -66,8 +66,6 @@ def test_row_counts(tmpd_cwd, fresh_config):
66
66
  with parsl.load(config):
67
67
  assert this_app().result() == 5
68
68
 
69
- parsl.clear()
70
-
71
69
  # at this point, we should find one row in the monitoring database.
72
70
 
73
71
  engine = sqlalchemy.create_engine(db_url)
@@ -63,7 +63,6 @@ def test_row_counts():
63
63
 
64
64
  logger.info("cleaning up parsl")
65
65
  parsl.dfk().cleanup()
66
- parsl.clear()
67
66
 
68
67
  # at this point, we should find data consistent with executing one
69
68
  # task in the database.
@@ -83,7 +83,6 @@ def test_row_counts():
83
83
 
84
84
  logger.info("cleaning up parsl")
85
85
  parsl.dfk().cleanup()
86
- parsl.clear()
87
86
 
88
87
  # at this point, we should find one row in the monitoring database.
89
88
 
@@ -65,8 +65,6 @@ def test_row_counts(tmpd_cwd, strategy):
65
65
 
66
66
  this_app().result()
67
67
 
68
- parsl.clear()
69
-
70
68
  engine = sqlalchemy.create_engine(db_url)
71
69
  with engine.begin() as connection:
72
70
 
@@ -52,7 +52,6 @@ def test_future_representation(tmpd_cwd):
52
52
  # seconds, with the assumption "data will arrive in the DB within
53
53
  # 30 seconds, but probably much sooner".
54
54
  parsl.dfk().cleanup()
55
- parsl.clear()
56
55
 
57
56
  engine = sqlalchemy.create_engine(monitoring_url)
58
57
  with engine.begin() as connection:
@@ -47,7 +47,6 @@ def test_hashsum():
47
47
 
48
48
  logger.info("cleaning up parsl")
49
49
  parsl.dfk().cleanup()
50
- parsl.clear()
51
50
 
52
51
  # at this point, we should find one row in the monitoring database.
53
52
 
@@ -103,8 +103,6 @@ def test_stdstream_to_monitoring(stdx, expected_stdx, stream, tmpd_cwd, caplog):
103
103
  kwargs = {stream: stdx}
104
104
  stdapp(**kwargs).result()
105
105
 
106
- parsl.clear()
107
-
108
106
  engine = sqlalchemy.create_engine(c.monitoring.logging_endpoint)
109
107
  with engine.begin() as connection:
110
108
 
@@ -7,17 +7,12 @@ from parsl.tests.configs.htex_local import fresh_config
7
7
  EXECUTOR_LABEL = "MPI_TEST"
8
8
 
9
9
 
10
- def local_setup():
10
+ def local_config():
11
11
  config = fresh_config()
12
12
  config.executors[0].label = EXECUTOR_LABEL
13
13
  config.executors[0].max_workers_per_node = 1
14
14
  config.executors[0].enable_mpi_mode = False
15
- parsl.load(config)
16
-
17
-
18
- def local_teardown():
19
- parsl.dfk().cleanup()
20
- parsl.clear()
15
+ return config
21
16
 
22
17
 
23
18
  @python_app
@@ -6,6 +6,8 @@ import parsl
6
6
  from parsl import python_app, bash_app
7
7
  from parsl.tests.configs.htex_local import fresh_config
8
8
 
9
+ from parsl.executors.high_throughput.mpi_prefix_composer import MissingResourceSpecification
10
+
9
11
  import os
10
12
 
11
13
  EXECUTOR_LABEL = "MPI_TEST"
@@ -28,7 +30,6 @@ def local_setup():
28
30
 
29
31
  def local_teardown():
30
32
  parsl.dfk().cleanup()
31
- parsl.clear()
32
33
 
33
34
 
34
35
  @python_app
@@ -169,3 +170,11 @@ def test_simulated_load(rounds: int = 100):
169
170
  total_ranks, nodes = future.result(timeout=10)
170
171
  assert len(nodes) == futures[future]["num_nodes"]
171
172
  assert total_ranks == futures[future]["num_nodes"] * futures[future]["ranks_per_node"]
173
+
174
+
175
+ @pytest.mark.local
176
+ def test_missing_resource_spec():
177
+
178
+ with pytest.raises(MissingResourceSpecification):
179
+ future = mock_app(sleep_dur=0.4)
180
+ future.result(timeout=10)
@@ -19,7 +19,8 @@ from parsl.executors.high_throughput.mpi_resource_management import (
19
19
  )
20
20
  from parsl.executors.high_throughput.mpi_prefix_composer import (
21
21
  validate_resource_spec,
22
- InvalidResourceSpecification
22
+ InvalidResourceSpecification,
23
+ MissingResourceSpecification
23
24
  )
24
25
 
25
26
  EXECUTOR_LABEL = "MPI_TEST"
@@ -122,18 +123,22 @@ def test_top_level():
122
123
 
123
124
  @pytest.mark.local
124
125
  @pytest.mark.parametrize(
125
- "resource_spec, exception",
126
+ "resource_spec, is_mpi_enabled, exception",
126
127
  (
127
- ({"num_nodes": 2, "ranks_per_node": 1}, None),
128
- ({"launcher_options": "--debug_foo"}, None),
129
- ({"num_nodes": 2, "BAD_OPT": 1}, InvalidResourceSpecification),
130
- ({}, None),
128
+ ({"num_nodes": 2, "ranks_per_node": 1}, False, None),
129
+ ({"launcher_options": "--debug_foo"}, False, None),
130
+ ({"num_nodes": 2, "BAD_OPT": 1}, False, InvalidResourceSpecification),
131
+ ({}, False, None),
132
+ ({"num_nodes": 2, "ranks_per_node": 1}, True, None),
133
+ ({"launcher_options": "--debug_foo"}, True, None),
134
+ ({"num_nodes": 2, "BAD_OPT": 1}, True, InvalidResourceSpecification),
135
+ ({}, True, MissingResourceSpecification),
131
136
  )
132
137
  )
133
- def test_resource_spec(resource_spec: Dict, exception):
138
+ def test_resource_spec(resource_spec: Dict, is_mpi_enabled: bool, exception):
134
139
  if exception:
135
140
  with pytest.raises(exception):
136
- validate_resource_spec(resource_spec)
141
+ validate_resource_spec(resource_spec, is_mpi_enabled)
137
142
  else:
138
- result = validate_resource_spec(resource_spec)
143
+ result = validate_resource_spec(resource_spec, is_mpi_enabled)
139
144
  assert result is None
@@ -15,14 +15,6 @@ def foo(x, stdout='foo.stdout'):
15
15
  return f"echo {x + 1}"
16
16
 
17
17
 
18
- def local_setup():
19
- pass
20
-
21
-
22
- def local_teardown():
23
- parsl.clear()
24
-
25
-
26
18
  @pytest.mark.local
27
19
  def test_within_context_manger(tmpd_cwd):
28
20
  config = fresh_config()
@@ -37,4 +29,4 @@ def test_within_context_manger(tmpd_cwd):
37
29
 
38
30
  with pytest.raises(NoDataFlowKernelError) as excinfo:
39
31
  square(2).result()
40
- assert str(excinfo.value) == "Cannot submit to a DFK that has been cleaned up"
32
+ assert str(excinfo.value) == "Must first load config"
@@ -3,24 +3,28 @@ import pytest
3
3
  from concurrent.futures import Future
4
4
  from parsl import python_app
5
5
 
6
+ from typing import TypeVar
7
+
8
+ T = TypeVar('T')
9
+
6
10
 
7
11
  @python_app
8
- def returns_a_dict():
12
+ def returns_a_dict() -> dict:
9
13
  return {"a": "X", "b": "Y"}
10
14
 
11
15
 
12
16
  @python_app
13
- def returns_a_list():
17
+ def returns_a_list() -> list:
14
18
  return ["X", "Y"]
15
19
 
16
20
 
17
21
  @python_app
18
- def returns_a_tuple():
22
+ def returns_a_tuple() -> tuple:
19
23
  return ("X", "Y")
20
24
 
21
25
 
22
26
  @python_app
23
- def returns_a_class():
27
+ def returns_a_class() -> type:
24
28
  from dataclasses import dataclass
25
29
 
26
30
  @dataclass
@@ -38,7 +42,7 @@ class MyOuterClass():
38
42
 
39
43
 
40
44
  @python_app
41
- def returns_a_class_instance():
45
+ def returns_a_class_instance() -> object:
42
46
  return MyOuterClass()
43
47
 
44
48
 
@@ -110,7 +114,7 @@ def test_returns_a_class():
110
114
 
111
115
 
112
116
  @python_app
113
- def passthrough(v):
117
+ def passthrough(v: T) -> T:
114
118
  return v
115
119
 
116
120
 
@@ -0,0 +1,161 @@
1
+ from concurrent.futures import Future
2
+ from pathlib import Path
3
+ from threading import Event
4
+ from typing import Sequence
5
+
6
+ import pytest
7
+
8
+ import parsl
9
+ from parsl.config import Config
10
+ from parsl.dataflow.errors import DependencyError
11
+ from parsl.dataflow.dependency_resolvers import DEEP_DEPENDENCY_RESOLVER
12
+
13
+
14
+ def local_config():
15
+ return Config(dependency_resolver=DEEP_DEPENDENCY_RESOLVER)
16
+
17
+
18
+ @parsl.python_app
19
+ def a(event):
20
+ event.wait()
21
+ return 7
22
+
23
+
24
+ @parsl.python_app
25
+ def b(x: int):
26
+ return x + 1
27
+
28
+
29
+ @pytest.mark.local
30
+ def test_simple_pos_arg():
31
+ e = Event()
32
+ s = a(e)
33
+ f_b = b(s)
34
+ e.set()
35
+
36
+ assert f_b.result() == 8
37
+
38
+
39
+ @parsl.python_app
40
+ def b_first(x: Sequence[int]):
41
+ return x[0] + 1
42
+
43
+
44
+ @pytest.mark.local
45
+ def test_tuple_pos_arg():
46
+ e = Event()
47
+ s = (a(e),)
48
+ f_b = b_first(s)
49
+ e.set()
50
+ assert f_b.result() == 8
51
+
52
+
53
+ @pytest.mark.local
54
+ def test_list_exception():
55
+ a = Future()
56
+ a.set_exception(RuntimeError("artificial error"))
57
+ f_b = b([a])
58
+ assert isinstance(f_b.exception(), DependencyError)
59
+
60
+
61
+ @parsl.python_app
62
+ def make_path(s: str):
63
+ return Path(s)
64
+
65
+
66
+ @parsl.python_app
67
+ def append_paths(iterable, end_str: str = "end"):
68
+ type_ = type(iterable)
69
+ return type_([Path(s, end_str) for s in iterable])
70
+
71
+
72
+ @pytest.mark.local
73
+ @pytest.mark.parametrize(
74
+ "type_",
75
+ [
76
+ tuple,
77
+ list,
78
+ set,
79
+ ],
80
+ )
81
+ def test_resolving_iterables(type_):
82
+ output1 = make_path("test1")
83
+ output2 = make_path("test2")
84
+ output3 = append_paths(type_([output1, output2]), end_str="end")
85
+ assert output3.result() == type_([Path("test1", "end"), Path("test2", "end")])
86
+
87
+
88
+ @parsl.python_app
89
+ def append_paths_dict(iterable: dict, end_str: str = "end"):
90
+ return {Path(k, end_str): Path(v, end_str) for k, v in iterable.items()}
91
+
92
+
93
+ @pytest.mark.local
94
+ def test_resolving_dict():
95
+ output1 = make_path("test1")
96
+ output2 = make_path("test2")
97
+ output3 = append_paths_dict({output1: output2}, end_str="end")
98
+ assert output3.result() == {Path("test1", "end"): Path("test2", "end")}
99
+
100
+
101
+ @parsl.python_app
102
+ def extract_deep(struct: list):
103
+ return struct[0][0][0][0][0]
104
+
105
+
106
+ @pytest.mark.local
107
+ def test_deeper_list():
108
+ f = Future()
109
+ f.set_result(7)
110
+ f_b = extract_deep([[[[[f]]]]])
111
+
112
+ assert f_b.result() == 7
113
+
114
+
115
+ @pytest.mark.local
116
+ def test_deeper_list_and_tuple():
117
+ f = Future()
118
+ f.set_result(7)
119
+ f_b = extract_deep([([([f],)],)])
120
+
121
+ assert f_b.result() == 7
122
+
123
+
124
+ @parsl.python_app
125
+ def dictionary_checker(d):
126
+ assert d["a"] == [30, 10]
127
+ assert d["b"] == 20
128
+
129
+
130
+ @pytest.mark.local
131
+ def test_dictionary():
132
+ k1 = Future()
133
+ k1.set_result("a")
134
+ k2 = Future()
135
+ k2.set_result("b")
136
+ v1 = Future()
137
+ v1.set_result(10)
138
+
139
+ # this .result() will fail if the asserts fail
140
+ dictionary_checker({k1: [30, v1], k2: 20}).result()
141
+
142
+
143
+ @pytest.mark.local
144
+ def test_dictionary_later():
145
+ k1 = Future()
146
+ k2 = Future()
147
+ v1 = Future()
148
+
149
+ f1 = dictionary_checker({k1: [30, v1], k2: 20})
150
+
151
+ assert not f1.done()
152
+ k1.set_result("a")
153
+ k2.set_result("b")
154
+ v1.set_result(10)
155
+
156
+ # having set the results, f1 should fairly rapidly complete (but not
157
+ # instantly) so we can't assert on done() here... but we can
158
+ # check that f1 does "eventually" complete... meaning that
159
+ # none of the assertions inside test_dictionary have failed
160
+
161
+ assert f1.result() is None
@@ -69,6 +69,4 @@ def test_one_block(tmpd_cwd):
69
69
  with parsl.load(config):
70
70
  app().result()
71
71
 
72
- parsl.clear()
73
-
74
72
  assert oneshot_provider.recorded_submits == 1
@@ -72,7 +72,5 @@ def test_shutdown_scalein_blocks(tmpd_cwd, try_assert):
72
72
  # this will wait for everything to be scaled out fully
73
73
  try_assert(lambda: len(htex.connected_managers()) == BLOCK_COUNT)
74
74
 
75
- parsl.clear()
76
-
77
75
  assert len(accumulating_provider.submit_job_ids) == BLOCK_COUNT, f"Exactly {BLOCK_COUNT} blocks should have been launched"
78
76
  assert len(accumulating_provider.cancel_job_ids) == BLOCK_COUNT, f"Exactly {BLOCK_COUNT} blocks should have been scaled in"
@@ -38,7 +38,6 @@ def local_setup():
38
38
 
39
39
  def local_teardown():
40
40
  parsl.dfk().cleanup()
41
- parsl.clear()
42
41
 
43
42
  methods_for_data.clear()
44
43
  methods_for_data.update(previous_methods)
@@ -25,7 +25,6 @@ def test_no_kills():
25
25
  assert parsl.dfk().monitoring is not None, "This test requires monitoring"
26
26
 
27
27
  parsl.dfk().cleanup()
28
- parsl.clear()
29
28
 
30
29
 
31
30
  @pytest.mark.local
@@ -62,4 +61,3 @@ def test_kill_monitoring_helper_process(sig, process_attr, try_assert):
62
61
  simple_app().result()
63
62
 
64
63
  parsl.dfk().cleanup()
65
- parsl.clear()
@@ -60,7 +60,6 @@ def test_1316_local_path_on_execution_side_sp2():
60
60
  assert not file.local_path, "The local_path on the submit side should not be set"
61
61
 
62
62
  parsl.dfk().cleanup()
63
- parsl.clear()
64
63
 
65
64
 
66
65
  @pytest.mark.local
@@ -83,4 +82,3 @@ def test_1316_local_path_setting_preserves_dependency_sp2():
83
82
  assert not file.local_path, "The local_path on the submit side should not be set"
84
83
 
85
84
  parsl.dfk().cleanup()
86
- parsl.clear()
@@ -44,7 +44,6 @@ def storage_access_parsl():
44
44
  yield _setup_config
45
45
 
46
46
  parsl.dfk().cleanup()
47
- parsl.clear()
48
47
 
49
48
 
50
49
  @pytest.mark.local
@@ -22,7 +22,6 @@ def test_summary(caplog):
22
22
  fail().exception()
23
23
 
24
24
  parsl.dfk().cleanup()
25
- parsl.clear()
26
25
 
27
26
  assert "Summary of tasks in DFK:" in caplog.text
28
27
  assert "Tasks in state States.exec_done: 1" in caplog.text
@@ -30,5 +30,4 @@ def test_parallel_for():
30
30
  assert thread_count <= config.executors[0].max_threads, "More threads than allowed"
31
31
  assert process_count == 1, "More processes than allowed"
32
32
  dfk.cleanup()
33
- parsl.clear()
34
33
  return d
@@ -24,5 +24,4 @@ def test_lazy_behavior():
24
24
  assert f.done()
25
25
 
26
26
  parsl.dfk().cleanup()
27
- parsl.clear()
28
27
  return
parsl/version.py CHANGED
@@ -3,4 +3,4 @@
3
3
  Year.Month.Day[alpha/beta/..]
4
4
  Alphas will be numbered like this -> 2024.12.10a0
5
5
  """
6
- VERSION = '2024.05.20'
6
+ VERSION = '2024.05.27'
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parsl
3
- Version: 2024.5.20
3
+ Version: 2024.5.27
4
4
  Summary: Simple data dependent workflows in Python
5
5
  Home-page: https://github.com/Parsl/parsl
6
- Download-URL: https://github.com/Parsl/parsl/archive/2024.05.20.tar.gz
6
+ Download-URL: https://github.com/Parsl/parsl/archive/2024.05.27.tar.gz
7
7
  Author: The Parsl Team
8
8
  Author-email: parsl@googlegroups.com
9
9
  License: Apache 2.0
@@ -55,8 +55,8 @@ Requires-Dist: pyyaml ; extra == 'all'
55
55
  Requires-Dist: cffi ; extra == 'all'
56
56
  Requires-Dist: jsonschema ; extra == 'all'
57
57
  Requires-Dist: proxystore ; extra == 'all'
58
- Requires-Dist: radical.pilot ==1.52.1 ; extra == 'all'
59
- Requires-Dist: radical.utils ==1.52 ; extra == 'all'
58
+ Requires-Dist: radical.pilot ==1.60 ; extra == 'all'
59
+ Requires-Dist: radical.utils ==1.60 ; extra == 'all'
60
60
  Provides-Extra: aws
61
61
  Requires-Dist: boto3 ; extra == 'aws'
62
62
  Provides-Extra: azure
@@ -85,8 +85,8 @@ Requires-Dist: oauth-ssh >=0.9 ; extra == 'oauth_ssh'
85
85
  Provides-Extra: proxystore
86
86
  Requires-Dist: proxystore ; extra == 'proxystore'
87
87
  Provides-Extra: radical-pilot
88
- Requires-Dist: radical.pilot ==1.52.1 ; extra == 'radical-pilot'
89
- Requires-Dist: radical.utils ==1.52 ; extra == 'radical-pilot'
88
+ Requires-Dist: radical.pilot ==1.60 ; extra == 'radical-pilot'
89
+ Requires-Dist: radical.utils ==1.60 ; extra == 'radical-pilot'
90
90
  Provides-Extra: visualization
91
91
  Requires-Dist: pydot ; extra == 'visualization'
92
92
  Requires-Dist: networkx <2.6,>=2.5 ; extra == 'visualization'