parsl 2024.12.23__py3-none-any.whl → 2025.1.6__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.
- parsl/jobs/strategy.py +2 -2
- parsl/monitoring/db_manager.py +1 -1
- parsl/tests/test_monitoring/test_app_names.py +2 -2
- parsl/tests/test_monitoring/test_stdouterr.py +2 -2
- parsl/tests/test_scaling/test_regression_3696_oscillation.py +103 -0
- parsl/version.py +1 -1
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/METADATA +8 -8
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/RECORD +16 -15
- {parsl-2024.12.23.data → parsl-2025.1.6.data}/scripts/exec_parsl_function.py +0 -0
- {parsl-2024.12.23.data → parsl-2025.1.6.data}/scripts/interchange.py +0 -0
- {parsl-2024.12.23.data → parsl-2025.1.6.data}/scripts/parsl_coprocess.py +0 -0
- {parsl-2024.12.23.data → parsl-2025.1.6.data}/scripts/process_worker_pool.py +0 -0
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/LICENSE +0 -0
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/WHEEL +0 -0
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/entry_points.txt +0 -0
- {parsl-2024.12.23.dist-info → parsl-2025.1.6.dist-info}/top_level.txt +0 -0
parsl/jobs/strategy.py
CHANGED
@@ -298,8 +298,8 @@ class Strategy:
|
|
298
298
|
# Scale in for htex
|
299
299
|
if isinstance(executor, HighThroughputExecutor):
|
300
300
|
if active_blocks > min_blocks:
|
301
|
-
excess_slots = math.
|
302
|
-
excess_blocks = math.
|
301
|
+
excess_slots = math.floor(active_slots - (active_tasks * parallelism))
|
302
|
+
excess_blocks = math.floor(float(excess_slots) / (tasks_per_node * nodes_per_block))
|
303
303
|
excess_blocks = min(excess_blocks, active_blocks - min_blocks)
|
304
304
|
logger.debug(f"Requesting scaling in by {excess_blocks} blocks with idle time {self.max_idletime}s")
|
305
305
|
executor.scale_in_facade(excess_blocks, max_idletime=self.max_idletime)
|
parsl/monitoring/db_manager.py
CHANGED
@@ -78,7 +78,7 @@ class Database:
|
|
78
78
|
|
79
79
|
def _get_mapper(self, table_obj: Table) -> Mapper:
|
80
80
|
all_mappers: Set[Mapper] = set()
|
81
|
-
for mapper_registry in mapperlib._all_registries():
|
81
|
+
for mapper_registry in mapperlib._all_registries():
|
82
82
|
all_mappers.update(mapper_registry.mappers)
|
83
83
|
mapper_gen = (
|
84
84
|
mapper for mapper in all_mappers
|
@@ -67,7 +67,7 @@ def test_app_name(get_app, expected_name, expected_result, tmpd_cwd):
|
|
67
67
|
with engine.begin() as connection:
|
68
68
|
|
69
69
|
def count_rows(table: str):
|
70
|
-
result = connection.execute(f"SELECT COUNT(*) FROM {table}")
|
70
|
+
result = connection.execute(sqlalchemy.text(f"SELECT COUNT(*) FROM {table}"))
|
71
71
|
(c, ) = result.first()
|
72
72
|
return c
|
73
73
|
|
@@ -81,6 +81,6 @@ def test_app_name(get_app, expected_name, expected_result, tmpd_cwd):
|
|
81
81
|
assert count_rows("try") == 1
|
82
82
|
|
83
83
|
# ... and has the expected name.
|
84
|
-
result = connection.execute("SELECT task_func_name FROM task")
|
84
|
+
result = connection.execute(sqlalchemy.text("SELECT task_func_name FROM task"))
|
85
85
|
(c, ) = result.first()
|
86
86
|
assert c == expected_name
|
@@ -106,7 +106,7 @@ def test_stdstream_to_monitoring(stdx, expected_stdx, stream, tmpd_cwd, caplog):
|
|
106
106
|
with engine.begin() as connection:
|
107
107
|
|
108
108
|
def count_rows(table: str):
|
109
|
-
result = connection.execute(f"SELECT COUNT(*) FROM {table}")
|
109
|
+
result = connection.execute(sqlalchemy.text(f"SELECT COUNT(*) FROM {table}"))
|
110
110
|
(c, ) = result.first()
|
111
111
|
return c
|
112
112
|
|
@@ -120,7 +120,7 @@ def test_stdstream_to_monitoring(stdx, expected_stdx, stream, tmpd_cwd, caplog):
|
|
120
120
|
assert count_rows("try") == 1
|
121
121
|
|
122
122
|
# ... and has the expected name.
|
123
|
-
result = connection.execute(f"SELECT task_{stream} FROM task")
|
123
|
+
result = connection.execute(sqlalchemy.text(f"SELECT task_{stream} FROM task"))
|
124
124
|
(c, ) = result.first()
|
125
125
|
|
126
126
|
if isinstance(expected_stdx, str):
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import math
|
2
|
+
from unittest.mock import MagicMock
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from parsl.executors.high_throughput.executor import HighThroughputExecutor
|
7
|
+
from parsl.jobs.states import JobState, JobStatus
|
8
|
+
from parsl.jobs.strategy import Strategy
|
9
|
+
|
10
|
+
|
11
|
+
# the parameterize tuple consists of:
|
12
|
+
# Input:
|
13
|
+
# * number of tasks to mock the load as
|
14
|
+
# * number of workers per node
|
15
|
+
# Expected output:
|
16
|
+
# * the number of blocks we should expect to be launched
|
17
|
+
# in this situation
|
18
|
+
#
|
19
|
+
# This test will configure an executor, then run strategize
|
20
|
+
# a few times, asserting that it converges to the correct
|
21
|
+
# number of blocks without oscillating.
|
22
|
+
@pytest.mark.local
|
23
|
+
@pytest.mark.parametrize("ns", [(14, 48, 1), # values from issue #3696
|
24
|
+
|
25
|
+
(1, 1, 1), # one task needs one block
|
26
|
+
|
27
|
+
(100, 1, 20), # many one-task blocks, hitting hard-coded max blocks
|
28
|
+
|
29
|
+
(47, 48, 1), # some edge cases around #3696 values
|
30
|
+
(48, 48, 1), # "
|
31
|
+
(49, 48, 2), # "
|
32
|
+
(149, 50, 3)]) # "
|
33
|
+
def test_htex_strategy_does_not_oscillate(ns):
|
34
|
+
"""Check for oscillations in htex scaling.
|
35
|
+
In issue 3696, with a large number of workers per block
|
36
|
+
and a smaller number of active tasks, the htex scaling
|
37
|
+
strategy oscillates between 0 and 1 active block, rather
|
38
|
+
than converging to 1 active block.
|
39
|
+
"""
|
40
|
+
|
41
|
+
n_tasks, n_workers, n_blocks = ns
|
42
|
+
|
43
|
+
s = Strategy(strategy='htex_auto_scale', max_idletime=0)
|
44
|
+
|
45
|
+
provider = MagicMock()
|
46
|
+
executor = MagicMock(spec=HighThroughputExecutor)
|
47
|
+
|
48
|
+
statuses = {}
|
49
|
+
|
50
|
+
executor.provider = provider
|
51
|
+
executor.outstanding = n_tasks
|
52
|
+
executor.status_facade = statuses
|
53
|
+
executor.workers_per_node = n_workers
|
54
|
+
|
55
|
+
provider.parallelism = 1
|
56
|
+
provider.init_blocks = 0
|
57
|
+
provider.min_blocks = 0
|
58
|
+
provider.max_blocks = 20
|
59
|
+
provider.nodes_per_block = 1
|
60
|
+
|
61
|
+
def scale_out(n):
|
62
|
+
for _ in range(n):
|
63
|
+
statuses[len(statuses)] = JobStatus(state=JobState.PENDING)
|
64
|
+
|
65
|
+
executor.scale_out_facade.side_effect = scale_out
|
66
|
+
|
67
|
+
def scale_in(n, max_idletime=None):
|
68
|
+
# find n PENDING jobs and set them to CANCELLED
|
69
|
+
for k in statuses:
|
70
|
+
if n == 0:
|
71
|
+
return
|
72
|
+
if statuses[k].state == JobState.PENDING:
|
73
|
+
statuses[k].state = JobState.CANCELLED
|
74
|
+
n -= 1
|
75
|
+
|
76
|
+
executor.scale_in_facade.side_effect = scale_in
|
77
|
+
|
78
|
+
s.add_executors([executor])
|
79
|
+
|
80
|
+
# In issue #3696, this first strategise does initial and load based
|
81
|
+
# scale outs, because n_tasks > n_workers*0
|
82
|
+
s.strategize([executor])
|
83
|
+
|
84
|
+
executor.scale_out_facade.assert_called()
|
85
|
+
assert len(statuses) == n_blocks, "Should have launched n_blocks"
|
86
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == n_blocks
|
87
|
+
# there might be several calls to scale_out_facade inside strategy,
|
88
|
+
# but the end effect should be that exactly one block is scaled out.
|
89
|
+
|
90
|
+
executor.scale_in_facade.assert_not_called()
|
91
|
+
|
92
|
+
# In issue #3696, this second strategize does a scale in, because n_tasks < n_workers*1
|
93
|
+
s.strategize([executor])
|
94
|
+
|
95
|
+
# assert that there should still be n_blocks pending blocks
|
96
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == n_blocks
|
97
|
+
# this assert fails due to issue #3696
|
98
|
+
|
99
|
+
# Now check scale in happens with 0 load
|
100
|
+
executor.outstanding = 0
|
101
|
+
s.strategize([executor])
|
102
|
+
executor.scale_in_facade.assert_called()
|
103
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == 0
|
parsl/version.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: parsl
|
3
|
-
Version:
|
3
|
+
Version: 2025.1.6
|
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/
|
6
|
+
Download-URL: https://github.com/Parsl/parsl/archive/2025.01.06.tar.gz
|
7
7
|
Author: The Parsl Team
|
8
8
|
Author-email: parsl@googlegroups.com
|
9
9
|
License: Apache 2.0
|
@@ -28,9 +28,9 @@ Requires-Dist: psutil>=5.5.1
|
|
28
28
|
Requires-Dist: setproctitle
|
29
29
|
Requires-Dist: filelock<4,>=3.13
|
30
30
|
Provides-Extra: all
|
31
|
-
Requires-Dist: sqlalchemy<2,>=
|
32
|
-
Requires-Dist: pydot; extra == "all"
|
33
|
-
Requires-Dist: networkx<
|
31
|
+
Requires-Dist: sqlalchemy<2.1,>=2; extra == "all"
|
32
|
+
Requires-Dist: pydot>=1.4.2; extra == "all"
|
33
|
+
Requires-Dist: networkx<3.3,>=3.2; extra == "all"
|
34
34
|
Requires-Dist: Flask>=1.0.2; extra == "all"
|
35
35
|
Requires-Dist: flask-sqlalchemy; extra == "all"
|
36
36
|
Requires-Dist: pandas<2.2; extra == "all"
|
@@ -76,15 +76,15 @@ Requires-Dist: python-gssapi; extra == "gssapi"
|
|
76
76
|
Provides-Extra: kubernetes
|
77
77
|
Requires-Dist: kubernetes; extra == "kubernetes"
|
78
78
|
Provides-Extra: monitoring
|
79
|
-
Requires-Dist: sqlalchemy<2,>=
|
79
|
+
Requires-Dist: sqlalchemy<2.1,>=2; extra == "monitoring"
|
80
80
|
Provides-Extra: proxystore
|
81
81
|
Requires-Dist: proxystore; extra == "proxystore"
|
82
82
|
Provides-Extra: radical-pilot
|
83
83
|
Requires-Dist: radical.pilot==1.90; extra == "radical-pilot"
|
84
84
|
Requires-Dist: radical.utils==1.90; extra == "radical-pilot"
|
85
85
|
Provides-Extra: visualization
|
86
|
-
Requires-Dist: pydot; extra == "visualization"
|
87
|
-
Requires-Dist: networkx<
|
86
|
+
Requires-Dist: pydot>=1.4.2; extra == "visualization"
|
87
|
+
Requires-Dist: networkx<3.3,>=3.2; extra == "visualization"
|
88
88
|
Requires-Dist: Flask>=1.0.2; extra == "visualization"
|
89
89
|
Requires-Dist: flask-sqlalchemy; extra == "visualization"
|
90
90
|
Requires-Dist: pandas<2.2; extra == "visualization"
|
@@ -8,7 +8,7 @@ parsl/multiprocessing.py,sha256=MyaEcEq-Qf860u7V98u-PZrPNdtzOZL_NW6EhIJnmfQ,1937
|
|
8
8
|
parsl/process_loggers.py,sha256=uQ7Gd0W72Jz7rrcYlOMfLsAEhkRltxXJL2MgdduJjEw,1136
|
9
9
|
parsl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
parsl/utils.py,sha256=5FvHIMao3Ik0Rm2p2ieL1KQcQcYXc5K83Jrx5csi-B4,14301
|
11
|
-
parsl/version.py,sha256=
|
11
|
+
parsl/version.py,sha256=lIhZrp6NnBCcbBMgntHRdPaNhpDBWe88rZRBa8C0pSw,131
|
12
12
|
parsl/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
parsl/app/app.py,sha256=0gbM4AH2OtFOLsv07I5nglpElcwMSOi-FzdZZfrk7So,8532
|
14
14
|
parsl/app/bash.py,sha256=jm2AvePlCT9DZR7H_4ANDWxatp5dN_22FUlT_gWhZ-g,5528
|
@@ -105,13 +105,13 @@ parsl/jobs/error_handlers.py,sha256=BBXwUAMJpBm0HxV1P-I6jv7ZF9wcrhnCfzSTlsd2g4w,
|
|
105
105
|
parsl/jobs/errors.py,sha256=cpSQXCrlKtuHsQf7usjF-lX8XsDkFnE5kWpmFjiN6OU,178
|
106
106
|
parsl/jobs/job_status_poller.py,sha256=b37JOqDpSesqeSreEh1HzfVTFnD5Aoy6k8JDXkkPDmk,2192
|
107
107
|
parsl/jobs/states.py,sha256=dUM8gC4YVpUjLMARJJ_tDERs6oHsoNheAtG6JWPIJt4,5058
|
108
|
-
parsl/jobs/strategy.py,sha256=
|
108
|
+
parsl/jobs/strategy.py,sha256=wQk5q-K0adh1oZFI9Mei2iASLRQ8khup1ebiZD-4xfQ,13801
|
109
109
|
parsl/launchers/__init__.py,sha256=jJeDOWGKJjvpmWTLsj1zSqce_UAhWRc_IO-TzaOAlII,579
|
110
110
|
parsl/launchers/base.py,sha256=CblcvPTJiu-MNLWaRtFe29SZQ0BpTOlaY8CGcHdlHIE,538
|
111
111
|
parsl/launchers/errors.py,sha256=8YMV_CHpBNVa4eXkGE4x5DaFQlZkDCRCHmBktYcY6TA,467
|
112
112
|
parsl/launchers/launchers.py,sha256=cQsNsHuCOL_nQTjPXf0--YsgsDoMoJ77bO1Wt4ncLjs,15134
|
113
113
|
parsl/monitoring/__init__.py,sha256=0ywNz6i0lM1xo_7_BIxhETDGeVd2C_0wwD7qgeaMR4c,83
|
114
|
-
parsl/monitoring/db_manager.py,sha256=
|
114
|
+
parsl/monitoring/db_manager.py,sha256=ra5PqmbUstfDx0o_bkBYI8GIUi461-GV3b4A-Q6DVVE,33300
|
115
115
|
parsl/monitoring/errors.py,sha256=D6jpYzEzp0d6FmVKGqhvjAxr4ztZfJX2s-aXemH9bBU,148
|
116
116
|
parsl/monitoring/message_type.py,sha256=Khn88afNxcOIciKiCK4GLnn90I5BlRTiOL3zK-P07yQ,401
|
117
117
|
parsl/monitoring/monitoring.py,sha256=fkBZU4fWp7qBQUcKYtWjd4d-SsFlJUZNMZacFOh0IoA,12687
|
@@ -330,14 +330,14 @@ parsl/tests/test_htex/test_resource_spec_validation.py,sha256=VzOk4rjMNiDcEVLb-3
|
|
330
330
|
parsl/tests/test_htex/test_worker_failure.py,sha256=Uz-RHI-LK78FMjXUvrUFmo4iYfmpDVBUcBxxRb3UG9M,603
|
331
331
|
parsl/tests/test_htex/test_zmq_binding.py,sha256=WNFsCKKfid2uEfem0WLgl1wnBncIabpAv6kmg3imBxk,4001
|
332
332
|
parsl/tests/test_monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
333
|
-
parsl/tests/test_monitoring/test_app_names.py,sha256=
|
333
|
+
parsl/tests/test_monitoring/test_app_names.py,sha256=A-mOMCVhZDnUyJp32fsTUkHdcyval8o7WPEWacDkbD4,2208
|
334
334
|
parsl/tests/test_monitoring/test_basic.py,sha256=VdF6JHfqsEOIMg-ysIAREgygZIjHWNDVLNVQ7jhWxmQ,4592
|
335
335
|
parsl/tests/test_monitoring/test_db_locks.py,sha256=3s3c1xhKo230ZZIJ3f1Ca4U7LcEdXnanOGVXQyNlk2U,2895
|
336
336
|
parsl/tests/test_monitoring/test_fuzz_zmq.py,sha256=--3-pQUvXXbkr8v_BEJoPvVvNly1oXvrD2nJh6yl_0M,3436
|
337
337
|
parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py,sha256=_QV8zjBKVF_qBbBnhT0C3X9AmfS7IKLcOnEw_cU6HeM,2622
|
338
338
|
parsl/tests/test_monitoring/test_incomplete_futures.py,sha256=ZnO1sFSwlWUBHX64C_zwfTVRVC_UFNlU4h0POgx6NEo,2005
|
339
339
|
parsl/tests/test_monitoring/test_memoization_representation.py,sha256=dknv2nO7pNZ1jGxWGsC_AW3rs90gjMIeC5d7pIJ75Xc,2645
|
340
|
-
parsl/tests/test_monitoring/test_stdouterr.py,sha256=
|
340
|
+
parsl/tests/test_monitoring/test_stdouterr.py,sha256=AjzD4oumRIe9XMDPU4Cn_9-fwx34Vg8eAq58VfCO_zc,4492
|
341
341
|
parsl/tests/test_monitoring/test_viz_colouring.py,sha256=83Qdmn3gM0j7IL6kPDcuIsp_nl4zj-liPijyIN632SY,592
|
342
342
|
parsl/tests/test_mpi_apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
343
343
|
parsl/tests/test_mpi_apps/test_bad_mpi_config.py,sha256=QKvEUSrHIBrvqu2fRj1MAqxsYxDfcrdQ7dzWdOZejuU,1320
|
@@ -401,6 +401,7 @@ parsl/tests/test_scaling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
401
401
|
parsl/tests/test_scaling/test_block_error_handler.py,sha256=OS1IyiK8gjRFI1VzpmOvEnKsPev2vKmC6Z2Hp5LaHpA,6068
|
402
402
|
parsl/tests/test_scaling/test_regression_1621.py,sha256=e3-bkHR3d8LxA-uY0BugyWgYzksh00I_UbaA-jHOzKY,1872
|
403
403
|
parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py,sha256=bjE_NIBoWK6heEz5LN0tzE1977vUA9kVemAYCqcIbzY,2942
|
404
|
+
parsl/tests/test_scaling/test_regression_3696_oscillation.py,sha256=7Xc3vgocXXUbUegh9t5OyXlV91lRXDVMUlrOwErYOXA,3621
|
404
405
|
parsl/tests/test_scaling/test_scale_down.py,sha256=vHJOMRUriW6xPtaY8GTKYXd5P0WJkQV6Q1IPui05aLU,2736
|
405
406
|
parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py,sha256=EnVNllKO2AGKkGa6927cLrzvvG6mpNQeFDzVktv6x08,4521
|
406
407
|
parsl/tests/test_scaling/test_scale_down_htex_unregistered.py,sha256=OrdnYmd58n7UfkANPJ7mzha4WSCPdbgJRX1O1Zdu0tI,1954
|
@@ -449,13 +450,13 @@ parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
449
450
|
parsl/usage_tracking/api.py,sha256=iaCY58Dc5J4UM7_dJzEEs871P1p1HdxBMtNGyVdzc9g,1821
|
450
451
|
parsl/usage_tracking/levels.py,sha256=xbfzYEsd55KiZJ-mzNgPebvOH4rRHum04hROzEf41tU,291
|
451
452
|
parsl/usage_tracking/usage.py,sha256=tcoZ2OUjsQVakG8Uu9_HFuEdzpSHyt4JarSRcLGnSMw,8918
|
452
|
-
parsl-
|
453
|
-
parsl-
|
454
|
-
parsl-
|
455
|
-
parsl-
|
456
|
-
parsl-
|
457
|
-
parsl-
|
458
|
-
parsl-
|
459
|
-
parsl-
|
460
|
-
parsl-
|
461
|
-
parsl-
|
453
|
+
parsl-2025.1.6.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
|
454
|
+
parsl-2025.1.6.data/scripts/interchange.py,sha256=rUhF_Bwk5NOqLhh-HgP-ei_gclKnPIJJ7uS32p0j-XI,30129
|
455
|
+
parsl-2025.1.6.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
|
456
|
+
parsl-2025.1.6.data/scripts/process_worker_pool.py,sha256=82FoJTye2SysJzPg-N8BpenuHGU7hOI8-Bedq8HV9C0,41851
|
457
|
+
parsl-2025.1.6.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
458
|
+
parsl-2025.1.6.dist-info/METADATA,sha256=vd794sFRn6CYChMUdYFIu7M5BZTvn18yg0Ymji3XGjI,3860
|
459
|
+
parsl-2025.1.6.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
460
|
+
parsl-2025.1.6.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
|
461
|
+
parsl-2025.1.6.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
|
462
|
+
parsl-2025.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|