fractal-server 2.5.1__py3-none-any.whl → 2.5.2__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.
@@ -1 +1 @@
1
- __VERSION__ = "2.5.1"
1
+ __VERSION__ = "2.5.2"
@@ -238,6 +238,18 @@ async def apply_workflow(
238
238
  await db.merge(job)
239
239
  await db.commit()
240
240
 
241
+ # User appropriate FractalSSH object
242
+ if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
243
+ ssh_credentials = dict(
244
+ user=settings.FRACTAL_SLURM_SSH_USER,
245
+ host=settings.FRACTAL_SLURM_SSH_HOST,
246
+ key_path=settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH,
247
+ )
248
+ fractal_ssh_list = request.app.state.fractal_ssh_list
249
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
250
+ else:
251
+ fractal_ssh = None
252
+
241
253
  background_tasks.add_task(
242
254
  submit_workflow,
243
255
  workflow_id=workflow.id,
@@ -246,7 +258,7 @@ async def apply_workflow(
246
258
  worker_init=job.worker_init,
247
259
  slurm_user=user.slurm_user,
248
260
  user_cache_dir=user.cache_dir,
249
- fractal_ssh=request.app.state.fractal_ssh,
261
+ fractal_ssh=fractal_ssh,
250
262
  )
251
263
  request.app.state.jobsV2.append(job.id)
252
264
  logger.info(
@@ -124,11 +124,20 @@ async def collect_tasks_pip(
124
124
  db.add(state)
125
125
  await db.commit()
126
126
 
127
+ # User appropriate FractalSSH object
128
+ ssh_credentials = dict(
129
+ user=settings.FRACTAL_SLURM_SSH_USER,
130
+ host=settings.FRACTAL_SLURM_SSH_HOST,
131
+ key_path=settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH,
132
+ )
133
+ fractal_ssh_list = request.app.state.fractal_ssh_list
134
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
135
+
127
136
  background_tasks.add_task(
128
137
  background_collect_pip_ssh,
129
138
  state.id,
130
139
  task_pkg,
131
- request.app.state.fractal_ssh,
140
+ fractal_ssh,
132
141
  )
133
142
 
134
143
  response.status_code = status.HTTP_201_CREATED
@@ -163,17 +163,34 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
163
163
  settings = Inject(get_settings)
164
164
  self.python_remote = settings.FRACTAL_SLURM_WORKER_PYTHON
165
165
  if self.python_remote is None:
166
+ self._stop_and_join_wait_thread()
166
167
  raise ValueError("FRACTAL_SLURM_WORKER_PYTHON is not set. Exit.")
167
168
 
168
169
  # Initialize connection and perform handshake
169
170
  self.fractal_ssh = fractal_ssh
170
171
  logger.warning(self.fractal_ssh)
171
- self.handshake()
172
+ try:
173
+ self.handshake()
174
+ except Exception as e:
175
+ logger.warning(
176
+ "Stop/join waiting thread and then "
177
+ f"re-raise original error {str(e)}"
178
+ )
179
+ self._stop_and_join_wait_thread()
180
+ raise e
172
181
 
173
182
  # Set/validate parameters for SLURM submission scripts
174
183
  self.slurm_account = slurm_account
175
184
  self.common_script_lines = common_script_lines or []
176
- self._validate_common_script_lines()
185
+ try:
186
+ self._validate_common_script_lines()
187
+ except Exception as e:
188
+ logger.warning(
189
+ "Stop/join waiting thread and then "
190
+ f"re-raise original error {str(e)}"
191
+ )
192
+ self._stop_and_join_wait_thread()
193
+ raise e
177
194
 
178
195
  # Set/initialize some more options
179
196
  self.keep_pickle_files = keep_pickle_files
@@ -1385,6 +1402,10 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1385
1402
  self.fractal_ssh.run_command(cmd=scancel_command)
1386
1403
  logger.debug("Executor shutdown: end")
1387
1404
 
1405
+ def _stop_and_join_wait_thread(self):
1406
+ self.wait_thread.stop()
1407
+ self.wait_thread.join()
1408
+
1388
1409
  def __exit__(self, *args, **kwargs):
1389
1410
  """
1390
1411
  See
@@ -1393,8 +1414,7 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1393
1414
  logger.debug(
1394
1415
  "[FractalSlurmSSHExecutor.__exit__] Stop and join `wait_thread`"
1395
1416
  )
1396
- self.wait_thread.stop()
1397
- self.wait_thread.join()
1417
+ self._stop_and_join_wait_thread()
1398
1418
  logger.debug("[FractalSlurmSSHExecutor.__exit__] End")
1399
1419
 
1400
1420
  def run_squeue(self, job_ids):
@@ -259,6 +259,7 @@ class FractalSlurmExecutor(SlurmExecutor):
259
259
  for line in self.common_script_lines
260
260
  if line.startswith("#SBATCH --account=")
261
261
  )
262
+ self._stop_and_join_wait_thread()
262
263
  raise RuntimeError(
263
264
  "Invalid line in `FractalSlurmExecutor.common_script_lines`: "
264
265
  f"'{invalid_line}'.\n"
@@ -1287,6 +1288,10 @@ class FractalSlurmExecutor(SlurmExecutor):
1287
1288
 
1288
1289
  logger.debug("Executor shutdown: end")
1289
1290
 
1291
+ def _stop_and_join_wait_thread(self):
1292
+ self.wait_thread.stop()
1293
+ self.wait_thread.join()
1294
+
1290
1295
  def __exit__(self, *args, **kwargs):
1291
1296
  """
1292
1297
  See
@@ -1295,6 +1300,5 @@ class FractalSlurmExecutor(SlurmExecutor):
1295
1300
  logger.debug(
1296
1301
  "[FractalSlurmExecutor.__exit__] Stop and join `wait_thread`"
1297
1302
  )
1298
- self.wait_thread.stop()
1299
- self.wait_thread.join()
1303
+ self._stop_and_join_wait_thread()
1300
1304
  logger.debug("[FractalSlurmExecutor.__exit__] End")
fractal_server/main.py CHANGED
@@ -92,32 +92,34 @@ async def lifespan(app: FastAPI):
92
92
  settings = Inject(get_settings)
93
93
 
94
94
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
95
- from fractal_server.ssh._fabric import get_ssh_connection
96
- from fractal_server.ssh._fabric import FractalSSH
97
95
 
98
- connection = get_ssh_connection()
99
- app.state.fractal_ssh = FractalSSH(connection=connection)
96
+ from fractal_server.ssh._fabric import FractalSSHList
97
+
98
+ app.state.fractal_ssh_list = FractalSSHList()
99
+
100
100
  logger.info(
101
- f"Created SSH connection "
102
- f"({app.state.fractal_ssh.is_connected=})."
101
+ "Added empty FractalSSHList to app.state "
102
+ f"(id={id(app.state.fractal_ssh_list)})."
103
103
  )
104
104
  else:
105
- app.state.fractal_ssh = None
105
+ app.state.fractal_ssh_list = None
106
106
 
107
107
  config_uvicorn_loggers()
108
108
  logger.info("End application startup")
109
109
  reset_logger_handlers(logger)
110
+
110
111
  yield
112
+
111
113
  logger = get_logger("fractal_server.lifespan")
112
114
  logger.info("Start application shutdown")
113
115
 
114
116
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
115
117
  logger.info(
116
- f"Closing SSH connection "
117
- f"(current: {app.state.fractal_ssh.is_connected=})."
118
+ "Close FractalSSH connections "
119
+ f"(current size: {app.state.fractal_ssh_list.size})."
118
120
  )
119
121
 
120
- app.state.fractal_ssh.close()
122
+ app.state.fractal_ssh_list.close_all()
121
123
 
122
124
  logger.info(
123
125
  f"Current worker with pid {os.getpid()} is shutting down. "
@@ -16,20 +16,21 @@ from paramiko.ssh_exception import NoValidConnectionsError
16
16
 
17
17
  from ..logger import get_logger
18
18
  from ..logger import set_logger
19
- from fractal_server.config import get_settings
20
19
  from fractal_server.string_tools import validate_cmd
21
- from fractal_server.syringe import Inject
22
20
 
23
21
 
24
22
  class FractalSSHTimeoutError(RuntimeError):
25
23
  pass
26
24
 
27
25
 
26
+ class FractalSSHListTimeoutError(RuntimeError):
27
+ pass
28
+
29
+
28
30
  logger = set_logger(__name__)
29
31
 
30
32
 
31
33
  class FractalSSH(object):
32
-
33
34
  """
34
35
  FIXME SSH: Fix docstring
35
36
 
@@ -111,7 +112,6 @@ class FractalSSH(object):
111
112
  def run(
112
113
  self, *args, lock_timeout: Optional[float] = None, **kwargs
113
114
  ) -> Any:
114
-
115
115
  actual_lock_timeout = self.default_lock_timeout
116
116
  if lock_timeout is not None:
117
117
  actual_lock_timeout = lock_timeout
@@ -138,7 +138,19 @@ class FractalSSH(object):
138
138
  )
139
139
 
140
140
  def close(self) -> None:
141
- return self._connection.close()
141
+ """
142
+ Aggressively close `self._connection`.
143
+
144
+ When `Connection.is_connected` is `False`, `Connection.close()` does
145
+ not call `Connection.client.close()`. Thus we do this explicitly here,
146
+ because we observed cases where `is_connected=False` but the underlying
147
+ `Transport` object was not closed.
148
+ """
149
+
150
+ self._connection.close()
151
+
152
+ if self._connection.client is not None:
153
+ self._connection.client.close()
142
154
 
143
155
  def run_command(
144
156
  self,
@@ -335,36 +347,169 @@ class FractalSSH(object):
335
347
  f.write(content)
336
348
 
337
349
 
338
- def get_ssh_connection(
339
- *,
340
- host: Optional[str] = None,
341
- user: Optional[str] = None,
342
- key_filename: Optional[str] = None,
343
- ) -> Connection:
350
+ class FractalSSHList(object):
344
351
  """
345
- Create a `fabric.Connection` object based on fractal-server settings
346
- or explicit arguments.
352
+ Collection of `FractalSSH` objects
347
353
 
348
- Args:
349
- host:
350
- user:
351
- key_filename:
354
+ Attributes are all private, and access to this collection must be
355
+ through methods (mostly the `get` one).
352
356
 
353
- Returns:
354
- Fabric connection object
357
+ Attributes:
358
+ _data:
359
+ Mapping of unique keys (the SSH-credentials tuples) to
360
+ `FractalSSH` objects.
361
+ _lock:
362
+ A `threading.Lock object`, to be acquired when changing `_data`.
363
+ _timeout: Timeout for `_lock` acquisition.
364
+ _logger_name: Logger name.
355
365
  """
356
- settings = Inject(get_settings)
357
- if host is None:
358
- host = settings.FRACTAL_SLURM_SSH_HOST
359
- if user is None:
360
- user = settings.FRACTAL_SLURM_SSH_USER
361
- if key_filename is None:
362
- key_filename = settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH
363
-
364
- connection = Connection(
365
- host=host,
366
- user=user,
367
- forward_agent=False,
368
- connect_kwargs={"key_filename": key_filename},
369
- )
370
- return connection
366
+
367
+ _data: dict[tuple[str, str, str], FractalSSH]
368
+ _lock: Lock
369
+ _timeout: float
370
+ _logger_name: str
371
+
372
+ def __init__(
373
+ self,
374
+ *,
375
+ timeout: float = 5.0,
376
+ logger_name: str = "fractal_server.FractalSSHList",
377
+ ):
378
+ self._lock = Lock()
379
+ self._data = {}
380
+ self._timeout = timeout
381
+ self._logger_name = logger_name
382
+ set_logger(self._logger_name)
383
+
384
+ @property
385
+ def logger(self) -> logging.Logger:
386
+ """
387
+ This property exists so that we never have to propagate the
388
+ `Logger` object.
389
+ """
390
+ return get_logger(self._logger_name)
391
+
392
+ @property
393
+ def size(self) -> int:
394
+ """
395
+ Number of current key-value pairs in `self._data`.
396
+ """
397
+ return len(self._data.values())
398
+
399
+ def get(self, *, host: str, user: str, key_path: str) -> FractalSSH:
400
+ """
401
+ Get the `FractalSSH` for the current credentials, or create one.
402
+
403
+ Note: Changing `_data` requires acquiring `_lock`.
404
+
405
+ Arguments:
406
+ host:
407
+ user:
408
+ key_path:
409
+ """
410
+ key = (host, user, key_path)
411
+ fractal_ssh = self._data.get(key, None)
412
+ if fractal_ssh is not None:
413
+ self.logger.info(
414
+ f"Return existing FractalSSH object for {user}@{host}"
415
+ )
416
+ return fractal_ssh
417
+ else:
418
+ self.logger.info(f"Add new FractalSSH object for {user}@{host}")
419
+ connection = Connection(
420
+ host=host,
421
+ user=user,
422
+ forward_agent=False,
423
+ connect_kwargs={
424
+ "key_filename": key_path,
425
+ "look_for_keys": False,
426
+ },
427
+ )
428
+ with self.acquire_lock_with_timeout():
429
+ self._data[key] = FractalSSH(connection=connection)
430
+ return self._data[key]
431
+
432
+ def contains(
433
+ self,
434
+ *,
435
+ host: str,
436
+ user: str,
437
+ key_path: str,
438
+ ) -> bool:
439
+ """
440
+ Return whether a given key is present in the collection.
441
+
442
+ Arguments:
443
+ host:
444
+ user:
445
+ key_path:
446
+ """
447
+ key = (host, user, key_path)
448
+ return key in self._data.keys()
449
+
450
+ def remove(
451
+ self,
452
+ *,
453
+ host: str,
454
+ user: str,
455
+ key_path: str,
456
+ ) -> None:
457
+ """
458
+ Remove a key from `_data` and close the corresponding connection.
459
+
460
+ Note: Changing `_data` requires acquiring `_lock`.
461
+
462
+ Arguments:
463
+ host:
464
+ user:
465
+ key_path:
466
+ """
467
+ key = (host, user, key_path)
468
+ with self.acquire_lock_with_timeout():
469
+ self.logger.info(
470
+ f"Removing FractalSSH object for {user}@{host} "
471
+ "from collection."
472
+ )
473
+ fractal_ssh_obj = self._data.pop(key)
474
+ self.logger.info(
475
+ f"Closing FractalSSH object for {user}@{host} "
476
+ f"({fractal_ssh_obj.is_connected=})."
477
+ )
478
+ fractal_ssh_obj.close()
479
+
480
+ def close_all(self, *, timeout: float = 5.0):
481
+ """
482
+ Close all `FractalSSH` objects in the collection.
483
+
484
+ Arguments:
485
+ timeout:
486
+ Timeout for `FractalSSH._lock` acquisition, to be obtained
487
+ before closing.
488
+ """
489
+ for key, fractal_ssh_obj in self._data.items():
490
+ host, user, _ = key[:]
491
+ self.logger.info(
492
+ f"Closing FractalSSH object for {user}@{host} "
493
+ f"({fractal_ssh_obj.is_connected=})."
494
+ )
495
+ with fractal_ssh_obj.acquire_timeout(timeout=timeout):
496
+ fractal_ssh_obj.close()
497
+
498
+ @contextmanager
499
+ def acquire_lock_with_timeout(self) -> Generator[Literal[True], Any, None]:
500
+ self.logger.debug(
501
+ f"Trying to acquire lock, with timeout {self._timeout} s"
502
+ )
503
+ result = self._lock.acquire(timeout=self._timeout)
504
+ try:
505
+ if not result:
506
+ self.logger.error("Lock was *NOT* acquired.")
507
+ raise FractalSSHListTimeoutError(
508
+ f"Failed to acquire lock within {self._timeout} ss"
509
+ )
510
+ self.logger.debug("Lock was acquired.")
511
+ yield result
512
+ finally:
513
+ if result:
514
+ self._lock.release()
515
+ self.logger.debug("Lock was released")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.5.1
3
+ Version: 2.5.2
4
4
  Summary: Server component of the Fractal analytics platform
5
5
  Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
6
  License: BSD-3-Clause
@@ -1,4 +1,4 @@
1
- fractal_server/__init__.py,sha256=8LAfKcNqeSASwVPDsGBZzXPzRTZGKfeNUwe56FocjrA,22
1
+ fractal_server/__init__.py,sha256=zW_UoRr0gmuphO3yp_Nmzq1qV6ZNQIt_3zHJMXwFtIM,22
2
2
  fractal_server/__main__.py,sha256=upYBkGYrkBnkS1rp4D_nb_1LS37QT4j-wxGX1ZMvR4A,5704
3
3
  fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
4
4
  fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -43,9 +43,9 @@ fractal_server/app/routes/api/v2/images.py,sha256=JR1rR6qEs81nacjriOXAOBQjAbCXF4
43
43
  fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhKRjEH2_ayEE,5157
44
44
  fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhzYBsVGYFhHZZXAE,6653
45
45
  fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
46
- fractal_server/app/routes/api/v2/submit.py,sha256=iTGCYbxiZNszHQa8r3gmAR4QcF6QhVrb8ktzste2Ovc,8775
46
+ fractal_server/app/routes/api/v2/submit.py,sha256=tyaeEpGMEkazdmltlnJxJYfD9Y9_t9mP2MUmx3s1Ato,9223
47
47
  fractal_server/app/routes/api/v2/task.py,sha256=XgRnGBvSoI9VNJHtWZQ2Ide99f6elo7a2FN3GQkf0dU,8376
48
- fractal_server/app/routes/api/v2/task_collection.py,sha256=wEwP8VfsxhKPZ6K3v1Bnput_Zw0Cjhlaal0-e50feIQ,12337
48
+ fractal_server/app/routes/api/v2/task_collection.py,sha256=SirU4yiE4pGfW68cyopMLgHSevIzaepQXLZJeIdaoDE,12697
49
49
  fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=CbeC7xYYF8K9JVOOunL3Y_3wXBEGGGoiJcoPa2hEftI,6127
50
50
  fractal_server/app/routes/api/v2/workflow.py,sha256=rMCcclz9aJAMSVLncUdSDGrgkKbn4KOCZTqZtqs2HDY,10428
51
51
  fractal_server/app/routes/api/v2/workflowtask.py,sha256=-3-c8DDnxGjMwWbX_h5V5OLaC_iCLXYzwWKBUaL-5wE,7060
@@ -76,12 +76,12 @@ fractal_server/app/runner/executors/slurm/remote.py,sha256=wLziIsGdSMiO-jIXM8x77
76
76
  fractal_server/app/runner/executors/slurm/ssh/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
77
77
  fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py,sha256=bKo5Ja0IGxJWpPWyh9dN0AG-PwzTDZzD5LyaEHB3YU4,3742
78
78
  fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py,sha256=rwlqZzoGo4SAb4nSlFjsQJdaCgfM1J6YGcjb8yYxlqc,4506
79
- fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=oCc5cLZoNmJ3ENV0VaYRiIKayVClKDoEnjgZjHU864Y,57052
79
+ fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=K1lKsn40PqQb-GZKwJkzeJk2Q8OGqy6YR-IxcV0E0Pw,57705
80
80
  fractal_server/app/runner/executors/slurm/sudo/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
81
81
  fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py,sha256=wAgwpVcr6JIslKHOuS0FhRa_6T1KCManyRJqA-fifzw,1909
82
82
  fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py,sha256=z5LlhaiqAb8pHsF1WwdzXN39C5anQmwjo1rSQgtRAYE,4422
83
83
  fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py,sha256=g8wqUjSicN17UZVXlfaMomYZ-xOIbBu1oE7HdJTzfvw,5218
84
- fractal_server/app/runner/executors/slurm/sudo/executor.py,sha256=FXf9I-12C3bnP20OMTdxwnBVpniluEf4V6V2Y3QVStk,48594
84
+ fractal_server/app/runner/executors/slurm/sudo/executor.py,sha256=6OPe9t70gLyuC2JhWt2o1f0e7zhQPBtrbMHQkDd6RAQ,48725
85
85
  fractal_server/app/runner/extract_archive.py,sha256=tLpjDrX47OjTNhhoWvm6iNukg8KoieWyTb7ZfvE9eWU,2483
86
86
  fractal_server/app/runner/filenames.py,sha256=9lwu3yB4C67yiijYw8XIKaLFn3mJUt6_TCyVFM_aZUQ,206
87
87
  fractal_server/app/runner/run_subprocess.py,sha256=c3JbYXq3hX2aaflQU19qJ5Xs6J6oXGNvnTEoAfv2bxc,959
@@ -156,7 +156,7 @@ fractal_server/images/__init__.py,sha256=xO6jTLE4EZKO6cTDdJsBmK9cdeh9hFTaSbSuWgQ
156
156
  fractal_server/images/models.py,sha256=9ipU5h4N6ogBChoB-2vHoqtL0TXOHCv6kRR-fER3mkM,4167
157
157
  fractal_server/images/tools.py,sha256=gxeniYy4Z-cp_ToK2LHPJUTVVUUrdpogYdcBUvBuLiY,2209
158
158
  fractal_server/logger.py,sha256=56wfka6fHaa3Rx5qO009nEs_y8gx5wZ2NUNZZ1I-uvc,5130
159
- fractal_server/main.py,sha256=NUvMd8C8kosulAcQ8pCFLnOGdLw7j-6RzcHxoNvSB7k,5003
159
+ fractal_server/main.py,sha256=68rVybnviF28yFgRhfHZXwnf6LyzkcmeYYZZppboU4M,4925
160
160
  fractal_server/migrations/README,sha256=4rQvyDfqodGhpJw74VYijRmgFP49ji5chyEemWGHsuw,59
161
161
  fractal_server/migrations/env.py,sha256=mEiX0TRa_8KAYBrUGJTx1cFJ5YAq_oNHHsFCp1raegk,2543
162
162
  fractal_server/migrations/naming_convention.py,sha256=htbKrVdetx3pklowb_9Cdo5RqeF0fJ740DNecY5de_M,265
@@ -181,7 +181,7 @@ fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py
181
181
  fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py,sha256=9BwqUS9Gf7UW_KjrzHbtViC880qhD452KAytkHWWZyk,746
182
182
  fractal_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
183
183
  fractal_server/ssh/__init__.py,sha256=sVUmzxf7_DuXG1xoLQ1_00fo5NPhi2LJipSmU5EAkPs,124
184
- fractal_server/ssh/_fabric.py,sha256=wJFgdBZ_-Zs1FbHJ7sqqkx--1BVJhZIMEk-s_CKPY20,11591
184
+ fractal_server/ssh/_fabric.py,sha256=B9C7Cj9ibrT1_OGlu38Jz94elNofLvkP1pxf0Tv8Eic,16140
185
185
  fractal_server/string_tools.py,sha256=YyopB2ZZ8iL9JLDXUA8PI0hahivqLiohir2HsAlEzqE,2170
186
186
  fractal_server/syringe.py,sha256=3qSMW3YaMKKnLdgnooAINOPxnCOxP7y2jeAQYB21Gdo,2786
187
187
  fractal_server/tasks/__init__.py,sha256=kadmVUoIghl8s190_Tt-8f-WBqMi8u8oU4Pvw39NHE8,23
@@ -207,8 +207,8 @@ fractal_server/tasks/v2/utils.py,sha256=JOyCacb6MNvrwfLNTyLwcz8y79J29YuJeJ2MK5kq
207
207
  fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
208
208
  fractal_server/utils.py,sha256=b7WwFdcFZ8unyT65mloFToYuEDXpQoHRcmRNqrhd_dQ,2115
209
209
  fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
210
- fractal_server-2.5.1.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
211
- fractal_server-2.5.1.dist-info/METADATA,sha256=PERWmLEehrxcMRZM1CCRADi950e6Pa-ofw7tnwgyqXw,4628
212
- fractal_server-2.5.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
213
- fractal_server-2.5.1.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
214
- fractal_server-2.5.1.dist-info/RECORD,,
210
+ fractal_server-2.5.2.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
211
+ fractal_server-2.5.2.dist-info/METADATA,sha256=Q_lBgfC8qNcsp8ll6WrxkYOvt8P0YAVJCaWPuOIgFTY,4628
212
+ fractal_server-2.5.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
213
+ fractal_server-2.5.2.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
214
+ fractal_server-2.5.2.dist-info/RECORD,,