fractal-server 2.14.4a0__py3-none-any.whl → 2.14.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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/db/__init__.py +2 -2
- fractal_server/app/models/security.py +8 -8
- fractal_server/app/models/user_settings.py +8 -10
- fractal_server/app/models/v2/accounting.py +2 -3
- fractal_server/app/models/v2/dataset.py +1 -2
- fractal_server/app/models/v2/history.py +3 -4
- fractal_server/app/models/v2/job.py +10 -11
- fractal_server/app/models/v2/project.py +1 -2
- fractal_server/app/models/v2/task.py +13 -14
- fractal_server/app/models/v2/task_group.py +15 -16
- fractal_server/app/models/v2/workflow.py +1 -2
- fractal_server/app/models/v2/workflowtask.py +6 -7
- fractal_server/app/routes/admin/v2/accounting.py +3 -4
- fractal_server/app/routes/admin/v2/job.py +13 -14
- fractal_server/app/routes/admin/v2/project.py +2 -4
- fractal_server/app/routes/admin/v2/task.py +11 -13
- fractal_server/app/routes/admin/v2/task_group.py +15 -17
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +5 -8
- fractal_server/app/routes/api/v2/__init__.py +2 -0
- fractal_server/app/routes/api/v2/_aux_functions.py +7 -9
- fractal_server/app/routes/api/v2/_aux_functions_history.py +1 -1
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +1 -3
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +5 -6
- fractal_server/app/routes/api/v2/dataset.py +6 -8
- fractal_server/app/routes/api/v2/history.py +5 -8
- fractal_server/app/routes/api/v2/images.py +2 -3
- fractal_server/app/routes/api/v2/job.py +5 -6
- fractal_server/app/routes/api/v2/pre_submission_checks.py +1 -3
- fractal_server/app/routes/api/v2/project.py +2 -4
- fractal_server/app/routes/api/v2/status_legacy.py +2 -4
- fractal_server/app/routes/api/v2/submit.py +3 -4
- fractal_server/app/routes/api/v2/task.py +6 -7
- fractal_server/app/routes/api/v2/task_collection.py +11 -13
- fractal_server/app/routes/api/v2/task_collection_custom.py +4 -4
- fractal_server/app/routes/api/v2/task_group.py +6 -8
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +6 -9
- fractal_server/app/routes/api/v2/task_version_update.py +270 -0
- fractal_server/app/routes/api/v2/workflow.py +5 -6
- fractal_server/app/routes/api/v2/workflow_import.py +3 -5
- fractal_server/app/routes/api/v2/workflowtask.py +2 -114
- fractal_server/app/routes/auth/current_user.py +2 -2
- fractal_server/app/routes/pagination.py +2 -3
- fractal_server/app/runner/exceptions.py +16 -22
- fractal_server/app/runner/executors/base_runner.py +19 -7
- fractal_server/app/runner/executors/call_command_wrapper.py +52 -0
- fractal_server/app/runner/executors/local/get_local_config.py +2 -3
- fractal_server/app/runner/executors/local/runner.py +52 -13
- fractal_server/app/runner/executors/slurm_common/_batching.py +2 -3
- fractal_server/app/runner/executors/slurm_common/_slurm_config.py +27 -29
- fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +95 -63
- fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +2 -3
- fractal_server/app/runner/executors/slurm_common/remote.py +47 -92
- fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +22 -22
- fractal_server/app/runner/executors/slurm_ssh/run_subprocess.py +2 -3
- fractal_server/app/runner/executors/slurm_ssh/runner.py +4 -6
- fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -6
- fractal_server/app/runner/executors/slurm_sudo/runner.py +9 -18
- fractal_server/app/runner/set_start_and_last_task_index.py +2 -5
- fractal_server/app/runner/shutdown.py +5 -11
- fractal_server/app/runner/task_files.py +3 -13
- fractal_server/app/runner/v2/_local.py +3 -4
- fractal_server/app/runner/v2/_slurm_ssh.py +5 -7
- fractal_server/app/runner/v2/_slurm_sudo.py +8 -10
- fractal_server/app/runner/v2/runner.py +4 -5
- fractal_server/app/runner/v2/runner_functions.py +20 -35
- fractal_server/app/runner/v2/submit_workflow.py +7 -10
- fractal_server/app/runner/v2/task_interface.py +2 -3
- fractal_server/app/runner/versions.py +3 -13
- fractal_server/app/schemas/user.py +2 -4
- fractal_server/app/schemas/user_group.py +1 -2
- fractal_server/app/schemas/user_settings.py +19 -21
- fractal_server/app/schemas/v2/dataset.py +2 -3
- fractal_server/app/schemas/v2/dumps.py +13 -15
- fractal_server/app/schemas/v2/history.py +6 -7
- fractal_server/app/schemas/v2/job.py +17 -18
- fractal_server/app/schemas/v2/manifest.py +12 -13
- fractal_server/app/schemas/v2/status_legacy.py +2 -2
- fractal_server/app/schemas/v2/task.py +29 -30
- fractal_server/app/schemas/v2/task_collection.py +8 -9
- fractal_server/app/schemas/v2/task_group.py +22 -23
- fractal_server/app/schemas/v2/workflow.py +1 -2
- fractal_server/app/schemas/v2/workflowtask.py +27 -29
- fractal_server/app/security/__init__.py +10 -12
- fractal_server/config.py +32 -42
- fractal_server/images/models.py +2 -4
- fractal_server/images/tools.py +4 -7
- fractal_server/logger.py +3 -5
- fractal_server/ssh/_fabric.py +41 -13
- fractal_server/string_tools.py +2 -2
- fractal_server/syringe.py +1 -1
- fractal_server/tasks/v2/local/collect.py +2 -3
- fractal_server/tasks/v2/local/deactivate.py +1 -1
- fractal_server/tasks/v2/local/reactivate.py +1 -1
- fractal_server/tasks/v2/ssh/collect.py +256 -245
- fractal_server/tasks/v2/ssh/deactivate.py +210 -187
- fractal_server/tasks/v2/ssh/reactivate.py +154 -146
- fractal_server/tasks/v2/utils_background.py +2 -3
- fractal_server/types/__init__.py +1 -2
- fractal_server/types/validators/_filter_validators.py +1 -2
- fractal_server/utils.py +4 -5
- fractal_server/zip_tools.py +1 -1
- {fractal_server-2.14.4a0.dist-info → fractal_server-2.14.6.dist-info}/METADATA +2 -9
- {fractal_server-2.14.4a0.dist-info → fractal_server-2.14.6.dist-info}/RECORD +107 -108
- fractal_server/app/history/__init__.py +0 -0
- fractal_server/app/runner/executors/slurm_common/utils_executors.py +0 -58
- fractal_server/app/runner/v2/runner_functions_low_level.py +0 -122
- {fractal_server-2.14.4a0.dist-info → fractal_server-2.14.6.dist-info}/LICENSE +0 -0
- {fractal_server-2.14.4a0.dist-info → fractal_server-2.14.6.dist-info}/WHEEL +0 -0
- {fractal_server-2.14.4a0.dist-info → fractal_server-2.14.6.dist-info}/entry_points.txt +0 -0
@@ -27,11 +27,9 @@ registers the client and the relative routes.
|
|
27
27
|
All routes are registered under the `auth/` prefix.
|
28
28
|
"""
|
29
29
|
import contextlib
|
30
|
+
from collections.abc import AsyncGenerator
|
30
31
|
from typing import Any
|
31
|
-
from typing import AsyncGenerator
|
32
32
|
from typing import Generic
|
33
|
-
from typing import Optional
|
34
|
-
from typing import Type
|
35
33
|
|
36
34
|
from fastapi import Depends
|
37
35
|
from fastapi import Request
|
@@ -82,24 +80,24 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
|
|
82
80
|
"""
|
83
81
|
|
84
82
|
session: AsyncSession
|
85
|
-
user_model:
|
86
|
-
oauth_account_model:
|
83
|
+
user_model: type[UP]
|
84
|
+
oauth_account_model: type[OAuthAccount] | None = None
|
87
85
|
|
88
86
|
def __init__(
|
89
87
|
self,
|
90
88
|
session: AsyncSession,
|
91
|
-
user_model:
|
92
|
-
oauth_account_model:
|
89
|
+
user_model: type[UP],
|
90
|
+
oauth_account_model: type[OAuthAccount] | None = None,
|
93
91
|
):
|
94
92
|
self.session = session
|
95
93
|
self.user_model = user_model
|
96
94
|
self.oauth_account_model = oauth_account_model
|
97
95
|
|
98
|
-
async def get(self, id: ID) ->
|
96
|
+
async def get(self, id: ID) -> UP | None:
|
99
97
|
"""Get a single user by id."""
|
100
98
|
return await self.session.get(self.user_model, id)
|
101
99
|
|
102
|
-
async def get_by_email(self, email: str) ->
|
100
|
+
async def get_by_email(self, email: str) -> UP | None:
|
103
101
|
"""Get a single user by email."""
|
104
102
|
statement = select(self.user_model).where(
|
105
103
|
func.lower(self.user_model.email) == func.lower(email)
|
@@ -112,7 +110,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
|
|
112
110
|
|
113
111
|
async def get_by_oauth_account(
|
114
112
|
self, oauth: str, account_id: str
|
115
|
-
) ->
|
113
|
+
) -> UP | None: # noqa
|
116
114
|
"""Get a single user by OAuth account id."""
|
117
115
|
if self.oauth_account_model is None:
|
118
116
|
raise NotImplementedError()
|
@@ -212,7 +210,7 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
|
212
210
|
)
|
213
211
|
|
214
212
|
async def on_after_register(
|
215
|
-
self, user: UserOAuth, request:
|
213
|
+
self, user: UserOAuth, request: Request | None = None
|
216
214
|
):
|
217
215
|
logger.info(
|
218
216
|
f"New-user registration completed ({user.id=}, {user.email=})."
|
@@ -290,7 +288,7 @@ async def _create_first_user(
|
|
290
288
|
password: str,
|
291
289
|
is_superuser: bool = False,
|
292
290
|
is_verified: bool = False,
|
293
|
-
username:
|
291
|
+
username: str | None = None,
|
294
292
|
) -> None:
|
295
293
|
"""
|
296
294
|
Private method to create the first fractal-server user
|
fractal_server/config.py
CHANGED
@@ -18,7 +18,6 @@ from os import environ
|
|
18
18
|
from os import getenv
|
19
19
|
from pathlib import Path
|
20
20
|
from typing import Literal
|
21
|
-
from typing import Optional
|
22
21
|
from typing import TypeVar
|
23
22
|
|
24
23
|
from cryptography.fernet import Fernet
|
@@ -56,8 +55,8 @@ class MailSettings(BaseModel):
|
|
56
55
|
recipients: list[EmailStr] = Field(min_length=1)
|
57
56
|
smtp_server: str
|
58
57
|
port: int
|
59
|
-
encrypted_password:
|
60
|
-
encryption_key:
|
58
|
+
encrypted_password: SecretStr | None = None
|
59
|
+
encryption_key: SecretStr | None = None
|
61
60
|
instance_name: str
|
62
61
|
use_starttls: bool
|
63
62
|
use_login: bool
|
@@ -100,8 +99,8 @@ class OAuthClientConfig(BaseModel):
|
|
100
99
|
CLIENT_NAME: str
|
101
100
|
CLIENT_ID: str
|
102
101
|
CLIENT_SECRET: SecretStr
|
103
|
-
OIDC_CONFIGURATION_ENDPOINT:
|
104
|
-
REDIRECT_URL:
|
102
|
+
OIDC_CONFIGURATION_ENDPOINT: str | None = None
|
103
|
+
REDIRECT_URL: str | None = None
|
105
104
|
|
106
105
|
@model_validator(mode="before")
|
107
106
|
@classmethod
|
@@ -139,7 +138,7 @@ class Settings(BaseSettings):
|
|
139
138
|
JWT token lifetime, in seconds.
|
140
139
|
"""
|
141
140
|
|
142
|
-
JWT_SECRET_KEY:
|
141
|
+
JWT_SECRET_KEY: SecretStr | None = None
|
143
142
|
"""
|
144
143
|
JWT secret
|
145
144
|
|
@@ -202,23 +201,23 @@ class Settings(BaseSettings):
|
|
202
201
|
"""
|
203
202
|
If `True`, make database operations verbose.
|
204
203
|
"""
|
205
|
-
POSTGRES_USER:
|
204
|
+
POSTGRES_USER: str | None = None
|
206
205
|
"""
|
207
206
|
User to use when connecting to the PostgreSQL database.
|
208
207
|
"""
|
209
|
-
POSTGRES_PASSWORD:
|
208
|
+
POSTGRES_PASSWORD: SecretStr | None = None
|
210
209
|
"""
|
211
210
|
Password to use when connecting to the PostgreSQL database.
|
212
211
|
"""
|
213
|
-
POSTGRES_HOST:
|
212
|
+
POSTGRES_HOST: str | None = "localhost"
|
214
213
|
"""
|
215
214
|
URL to the PostgreSQL server or path to a UNIX domain socket.
|
216
215
|
"""
|
217
|
-
POSTGRES_PORT:
|
216
|
+
POSTGRES_PORT: str | None = "5432"
|
218
217
|
"""
|
219
218
|
Port number to use when connecting to the PostgreSQL server.
|
220
219
|
"""
|
221
|
-
POSTGRES_DB:
|
220
|
+
POSTGRES_DB: str | None = None
|
222
221
|
"""
|
223
222
|
Name of the PostgreSQL database to connect to.
|
224
223
|
"""
|
@@ -275,13 +274,13 @@ class Settings(BaseSettings):
|
|
275
274
|
default admin credentials.
|
276
275
|
"""
|
277
276
|
|
278
|
-
FRACTAL_TASKS_DIR:
|
277
|
+
FRACTAL_TASKS_DIR: Path | None = None
|
279
278
|
"""
|
280
279
|
Directory under which all the tasks will be saved (either an absolute path
|
281
280
|
or a path relative to current working directory).
|
282
281
|
"""
|
283
282
|
|
284
|
-
FRACTAL_RUNNER_WORKING_BASE_DIR:
|
283
|
+
FRACTAL_RUNNER_WORKING_BASE_DIR: Path | None = None
|
285
284
|
"""
|
286
285
|
Base directory for job files (either an absolute path or a path relative to
|
287
286
|
current working directory).
|
@@ -293,7 +292,7 @@ class Settings(BaseSettings):
|
|
293
292
|
mode="after",
|
294
293
|
)
|
295
294
|
@classmethod
|
296
|
-
def make_paths_absolute(cls, path:
|
295
|
+
def make_paths_absolute(cls, path: Path | None) -> Path | None:
|
297
296
|
if path is None or path.is_absolute():
|
298
297
|
return path
|
299
298
|
else:
|
@@ -319,7 +318,7 @@ class Settings(BaseSettings):
|
|
319
318
|
Only logs of with this level (or higher) will appear in the console logs.
|
320
319
|
"""
|
321
320
|
|
322
|
-
FRACTAL_LOCAL_CONFIG_FILE:
|
321
|
+
FRACTAL_LOCAL_CONFIG_FILE: Path | None = None
|
323
322
|
"""
|
324
323
|
Path of JSON file with configuration for the local backend.
|
325
324
|
"""
|
@@ -335,27 +334,27 @@ class Settings(BaseSettings):
|
|
335
334
|
Waiting time for the shutdown phase of executors
|
336
335
|
"""
|
337
336
|
|
338
|
-
FRACTAL_SLURM_CONFIG_FILE:
|
337
|
+
FRACTAL_SLURM_CONFIG_FILE: Path | None = None
|
339
338
|
"""
|
340
339
|
Path of JSON file with configuration for the SLURM backend.
|
341
340
|
"""
|
342
341
|
|
343
|
-
FRACTAL_SLURM_WORKER_PYTHON:
|
342
|
+
FRACTAL_SLURM_WORKER_PYTHON: AbsolutePathStr | None = None
|
344
343
|
"""
|
345
344
|
Absolute path to Python interpreter that will run the jobs on the SLURM
|
346
345
|
nodes. If not specified, the same interpreter that runs the server is used.
|
347
346
|
"""
|
348
347
|
|
349
|
-
FRACTAL_TASKS_PYTHON_DEFAULT_VERSION:
|
348
|
+
FRACTAL_TASKS_PYTHON_DEFAULT_VERSION: None | (
|
350
349
|
Literal["3.9", "3.10", "3.11", "3.12"]
|
351
|
-
|
350
|
+
) = None
|
352
351
|
"""
|
353
352
|
Default Python version to be used for task collection. Defaults to the
|
354
353
|
current version. Requires the corresponding variable (e.g
|
355
354
|
`FRACTAL_TASKS_PYTHON_3_10`) to be set.
|
356
355
|
"""
|
357
356
|
|
358
|
-
FRACTAL_TASKS_PYTHON_3_9:
|
357
|
+
FRACTAL_TASKS_PYTHON_3_9: str | None = None
|
359
358
|
"""
|
360
359
|
Absolute path to the Python 3.9 interpreter that serves as base for virtual
|
361
360
|
environments tasks. Note that this interpreter must have the `venv` module
|
@@ -364,17 +363,17 @@ class Settings(BaseSettings):
|
|
364
363
|
unset, `sys.executable` is used as a default.
|
365
364
|
"""
|
366
365
|
|
367
|
-
FRACTAL_TASKS_PYTHON_3_10:
|
366
|
+
FRACTAL_TASKS_PYTHON_3_10: str | None = None
|
368
367
|
"""
|
369
368
|
Same as `FRACTAL_TASKS_PYTHON_3_9`, for Python 3.10.
|
370
369
|
"""
|
371
370
|
|
372
|
-
FRACTAL_TASKS_PYTHON_3_11:
|
371
|
+
FRACTAL_TASKS_PYTHON_3_11: str | None = None
|
373
372
|
"""
|
374
373
|
Same as `FRACTAL_TASKS_PYTHON_3_9`, for Python 3.11.
|
375
374
|
"""
|
376
375
|
|
377
|
-
FRACTAL_TASKS_PYTHON_3_12:
|
376
|
+
FRACTAL_TASKS_PYTHON_3_12: str | None = None
|
378
377
|
"""
|
379
378
|
Same as `FRACTAL_TASKS_PYTHON_3_9`, for Python 3.12.
|
380
379
|
"""
|
@@ -460,16 +459,7 @@ class Settings(BaseSettings):
|
|
460
459
|
running a task that produces multiple SLURM jobs.
|
461
460
|
"""
|
462
461
|
|
463
|
-
|
464
|
-
"""
|
465
|
-
Interval to wait (in seconds) when the SLURM backend does not find an
|
466
|
-
output pickle file - which could be due to several reasons (e.g. the SLURM
|
467
|
-
job was cancelled or failed, or writing the file is taking long). If the
|
468
|
-
file is still missing after this time interval, this leads to a
|
469
|
-
`JobExecutionError`.
|
470
|
-
"""
|
471
|
-
|
472
|
-
FRACTAL_PIP_CACHE_DIR: Optional[AbsolutePathStr] = None
|
462
|
+
FRACTAL_PIP_CACHE_DIR: AbsolutePathStr | None = None
|
473
463
|
"""
|
474
464
|
Absolute path to the cache directory for `pip`; if unset,
|
475
465
|
`--no-cache-dir` is used.
|
@@ -516,7 +506,7 @@ class Settings(BaseSettings):
|
|
516
506
|
viewer paths. Useful when vizarr viewer is not used.
|
517
507
|
"""
|
518
508
|
|
519
|
-
FRACTAL_VIEWER_BASE_FOLDER:
|
509
|
+
FRACTAL_VIEWER_BASE_FOLDER: str | None = None
|
520
510
|
"""
|
521
511
|
Base path to Zarr files that will be served by fractal-vizarr-viewer;
|
522
512
|
This variable is required and used only when
|
@@ -527,31 +517,31 @@ class Settings(BaseSettings):
|
|
527
517
|
# SMTP SERVICE
|
528
518
|
###########################################################################
|
529
519
|
|
530
|
-
FRACTAL_EMAIL_SENDER:
|
520
|
+
FRACTAL_EMAIL_SENDER: EmailStr | None = None
|
531
521
|
"""
|
532
522
|
Address of the OAuth-signup email sender.
|
533
523
|
"""
|
534
|
-
FRACTAL_EMAIL_PASSWORD:
|
524
|
+
FRACTAL_EMAIL_PASSWORD: SecretStr | None = None
|
535
525
|
"""
|
536
526
|
Password for the OAuth-signup email sender.
|
537
527
|
"""
|
538
|
-
FRACTAL_EMAIL_PASSWORD_KEY:
|
528
|
+
FRACTAL_EMAIL_PASSWORD_KEY: SecretStr | None = None
|
539
529
|
"""
|
540
530
|
Key value for `cryptography.fernet` decrypt
|
541
531
|
"""
|
542
|
-
FRACTAL_EMAIL_SMTP_SERVER:
|
532
|
+
FRACTAL_EMAIL_SMTP_SERVER: str | None = None
|
543
533
|
"""
|
544
534
|
SMTP server for the OAuth-signup emails.
|
545
535
|
"""
|
546
|
-
FRACTAL_EMAIL_SMTP_PORT:
|
536
|
+
FRACTAL_EMAIL_SMTP_PORT: int | None = None
|
547
537
|
"""
|
548
538
|
SMTP server port for the OAuth-signup emails.
|
549
539
|
"""
|
550
|
-
FRACTAL_EMAIL_INSTANCE_NAME:
|
540
|
+
FRACTAL_EMAIL_INSTANCE_NAME: str | None = None
|
551
541
|
"""
|
552
542
|
Fractal instance name, to be included in the OAuth-signup emails.
|
553
543
|
"""
|
554
|
-
FRACTAL_EMAIL_RECIPIENTS:
|
544
|
+
FRACTAL_EMAIL_RECIPIENTS: str | None = None
|
555
545
|
"""
|
556
546
|
Comma-separated list of recipients of the OAuth-signup emails.
|
557
547
|
"""
|
@@ -567,7 +557,7 @@ class Settings(BaseSettings):
|
|
567
557
|
provided.
|
568
558
|
Accepted values: 'true', 'false'.
|
569
559
|
"""
|
570
|
-
email_settings:
|
560
|
+
email_settings: MailSettings | None = None
|
571
561
|
|
572
562
|
@model_validator(mode="after")
|
573
563
|
def validate_email_settings(self):
|
fractal_server/images/models.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
1
|
from pydantic import BaseModel
|
4
2
|
from pydantic import Field
|
5
3
|
|
@@ -23,7 +21,7 @@ class _SingleImageBase(BaseModel):
|
|
23
21
|
"""
|
24
22
|
|
25
23
|
zarr_url: ZarrUrlStr
|
26
|
-
origin:
|
24
|
+
origin: ZarrDirStr | None = None
|
27
25
|
|
28
26
|
attributes: DictStrAny = Field(default_factory=dict)
|
29
27
|
types: ImageTypes = Field(default_factory=dict)
|
@@ -48,4 +46,4 @@ class SingleImage(_SingleImageBase):
|
|
48
46
|
class SingleImageUpdate(BaseModel):
|
49
47
|
zarr_url: ZarrUrlStr
|
50
48
|
attributes: ImageAttributes = None
|
51
|
-
types:
|
49
|
+
types: ImageTypes | None = None
|
fractal_server/images/tools.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from copy import copy
|
2
2
|
from typing import Any
|
3
3
|
from typing import Literal
|
4
|
-
from typing import Optional
|
5
4
|
from typing import Union
|
6
5
|
|
7
6
|
from fractal_server.types import AttributeFilters
|
@@ -13,7 +12,7 @@ def find_image_by_zarr_url(
|
|
13
12
|
*,
|
14
13
|
images: list[dict[str, Any]],
|
15
14
|
zarr_url: str,
|
16
|
-
) ->
|
15
|
+
) -> ImageSearch | None:
|
17
16
|
"""
|
18
17
|
Return a copy of the image with a given zarr_url, and its positional index.
|
19
18
|
|
@@ -65,8 +64,8 @@ def match_filter(
|
|
65
64
|
|
66
65
|
def filter_image_list(
|
67
66
|
images: list[dict[str, Any]],
|
68
|
-
type_filters:
|
69
|
-
attribute_filters:
|
67
|
+
type_filters: dict[str, bool] | None = None,
|
68
|
+
attribute_filters: AttributeFilters | None = None,
|
70
69
|
) -> list[dict[str, Any]]:
|
71
70
|
"""
|
72
71
|
Compute a sublist with images that match a filter set.
|
@@ -141,6 +140,4 @@ def aggregate_types(images: list[dict[str, Any]]) -> list[str]:
|
|
141
140
|
"""
|
142
141
|
Given a list of images, this function returns a list of all image types.
|
143
142
|
"""
|
144
|
-
return list(
|
145
|
-
set(type for image in images for type in image["types"].keys())
|
146
|
-
)
|
143
|
+
return list({type for image in images for type in image["types"].keys()})
|
fractal_server/logger.py
CHANGED
@@ -14,8 +14,6 @@ This module provides logging utilities
|
|
14
14
|
"""
|
15
15
|
import logging
|
16
16
|
from pathlib import Path
|
17
|
-
from typing import Optional
|
18
|
-
from typing import Union
|
19
17
|
|
20
18
|
from .config import get_settings
|
21
19
|
from .syringe import Inject
|
@@ -25,7 +23,7 @@ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
25
23
|
LOG_FORMATTER = logging.Formatter(LOG_FORMAT)
|
26
24
|
|
27
25
|
|
28
|
-
def get_logger(logger_name:
|
26
|
+
def get_logger(logger_name: str | None = None) -> logging.Logger:
|
29
27
|
"""
|
30
28
|
Wrap the
|
31
29
|
[`logging.getLogger`](https://docs.python.org/3/library/logging.html#logging.getLogger)
|
@@ -57,8 +55,8 @@ def get_logger(logger_name: Optional[str] = None) -> logging.Logger:
|
|
57
55
|
def set_logger(
|
58
56
|
logger_name: str,
|
59
57
|
*,
|
60
|
-
log_file_path:
|
61
|
-
default_logging_level:
|
58
|
+
log_file_path: str | Path | None = None,
|
59
|
+
default_logging_level: int | None = None,
|
62
60
|
) -> logging.Logger:
|
63
61
|
"""
|
64
62
|
Set up a `fractal-server` logger
|
fractal_server/ssh/_fabric.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
3
|
import time
|
4
|
+
from collections.abc import Generator
|
4
5
|
from contextlib import contextmanager
|
5
6
|
from pathlib import Path
|
6
7
|
from threading import Lock
|
7
8
|
from typing import Any
|
8
|
-
from typing import Generator
|
9
9
|
from typing import Literal
|
10
|
-
from typing import Optional
|
11
10
|
|
12
11
|
import paramiko.sftp_client
|
13
12
|
from fabric import Connection
|
14
13
|
from invoke import UnexpectedExit
|
15
14
|
from paramiko.ssh_exception import NoValidConnectionsError
|
15
|
+
from pydantic import BaseModel
|
16
16
|
|
17
17
|
from ..logger import get_logger
|
18
18
|
from ..logger import set_logger
|
@@ -35,6 +35,12 @@ class FractalSSHUnknownError(RuntimeError):
|
|
35
35
|
pass
|
36
36
|
|
37
37
|
|
38
|
+
class SSHConfig(BaseModel):
|
39
|
+
host: str
|
40
|
+
user: str
|
41
|
+
key_path: str
|
42
|
+
|
43
|
+
|
38
44
|
logger = set_logger(__name__)
|
39
45
|
|
40
46
|
|
@@ -56,6 +62,7 @@ def _acquire_lock_with_timeout(
|
|
56
62
|
"""
|
57
63
|
logger = get_logger(logger_name)
|
58
64
|
logger.info(f"Trying to acquire lock for '{label}', with {timeout=}")
|
65
|
+
t_start_lock_acquire = time.perf_counter()
|
59
66
|
result = lock.acquire(timeout=timeout)
|
60
67
|
try:
|
61
68
|
if not result:
|
@@ -64,7 +71,9 @@ def _acquire_lock_with_timeout(
|
|
64
71
|
f"Failed to acquire lock for '{label}' within "
|
65
72
|
f"{timeout} seconds"
|
66
73
|
)
|
67
|
-
|
74
|
+
t_end_lock_acquire = time.perf_counter()
|
75
|
+
elapsed = t_end_lock_acquire - t_start_lock_acquire
|
76
|
+
logger.info(f"Lock for '{label}' was acquired - {elapsed=:.4f} s")
|
68
77
|
yield result
|
69
78
|
finally:
|
70
79
|
if result:
|
@@ -72,7 +81,7 @@ def _acquire_lock_with_timeout(
|
|
72
81
|
logger.info(f"Lock for '{label}' was released.")
|
73
82
|
|
74
83
|
|
75
|
-
class FractalSSH
|
84
|
+
class FractalSSH:
|
76
85
|
"""
|
77
86
|
Wrapper of `fabric.Connection` object, enriched with locks.
|
78
87
|
|
@@ -156,7 +165,7 @@ class FractalSSH(object):
|
|
156
165
|
raise e
|
157
166
|
|
158
167
|
def _run(
|
159
|
-
self, *args, label: str, lock_timeout:
|
168
|
+
self, *args, label: str, lock_timeout: float | None = None, **kwargs
|
160
169
|
) -> Any:
|
161
170
|
actual_lock_timeout = self.default_lock_timeout
|
162
171
|
if lock_timeout is not None:
|
@@ -272,10 +281,10 @@ class FractalSSH(object):
|
|
272
281
|
self,
|
273
282
|
*,
|
274
283
|
cmd: str,
|
275
|
-
allow_char:
|
276
|
-
max_attempts:
|
277
|
-
base_interval:
|
278
|
-
lock_timeout:
|
284
|
+
allow_char: str | None = None,
|
285
|
+
max_attempts: int | None = None,
|
286
|
+
base_interval: float | None = None,
|
287
|
+
lock_timeout: int | None = None,
|
279
288
|
) -> str:
|
280
289
|
"""
|
281
290
|
Run a command within an open SSH connection.
|
@@ -372,7 +381,7 @@ class FractalSSH(object):
|
|
372
381
|
*,
|
373
382
|
local: str,
|
374
383
|
remote: str,
|
375
|
-
lock_timeout:
|
384
|
+
lock_timeout: float | None = None,
|
376
385
|
) -> None:
|
377
386
|
"""
|
378
387
|
Transfer a file via SSH
|
@@ -412,7 +421,7 @@ class FractalSSH(object):
|
|
412
421
|
*,
|
413
422
|
local: str,
|
414
423
|
remote: str,
|
415
|
-
lock_timeout:
|
424
|
+
lock_timeout: float | None = None,
|
416
425
|
) -> None:
|
417
426
|
"""
|
418
427
|
Transfer a file via SSH
|
@@ -503,7 +512,7 @@ class FractalSSH(object):
|
|
503
512
|
*,
|
504
513
|
path: str,
|
505
514
|
content: str,
|
506
|
-
lock_timeout:
|
515
|
+
lock_timeout: float | None = None,
|
507
516
|
) -> None:
|
508
517
|
"""
|
509
518
|
Open a remote file via SFTP and write it.
|
@@ -557,7 +566,7 @@ class FractalSSH(object):
|
|
557
566
|
)
|
558
567
|
|
559
568
|
|
560
|
-
class FractalSSHList
|
569
|
+
class FractalSSHList:
|
561
570
|
"""
|
562
571
|
Collection of `FractalSSH` objects
|
563
572
|
|
@@ -711,3 +720,22 @@ class FractalSSHList(object):
|
|
711
720
|
f"({fractal_ssh_obj.is_connected=})."
|
712
721
|
)
|
713
722
|
fractal_ssh_obj.close()
|
723
|
+
|
724
|
+
|
725
|
+
@contextmanager
|
726
|
+
def SingleUseFractalSSH(
|
727
|
+
*,
|
728
|
+
ssh_config: SSHConfig,
|
729
|
+
logger_name: str,
|
730
|
+
) -> Generator[FractalSSH, Any, None]:
|
731
|
+
"""
|
732
|
+
Get a new FractalSSH object (with a fresh connection).
|
733
|
+
|
734
|
+
Args:
|
735
|
+
ssh_config:
|
736
|
+
logger_name:
|
737
|
+
"""
|
738
|
+
_fractal_ssh_list = FractalSSHList(logger_name=logger_name)
|
739
|
+
_fractal_ssh = _fractal_ssh_list.get(**ssh_config.model_dump())
|
740
|
+
yield _fractal_ssh
|
741
|
+
_fractal_ssh.close()
|
fractal_server/string_tools.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import string
|
2
|
-
|
2
|
+
|
3
3
|
|
4
4
|
__SPECIAL_CHARACTERS__ = f"{string.punctuation}{string.whitespace}"
|
5
5
|
|
@@ -36,7 +36,7 @@ def sanitize_string(value: str) -> str:
|
|
36
36
|
def validate_cmd(
|
37
37
|
command: str,
|
38
38
|
*,
|
39
|
-
allow_char:
|
39
|
+
allow_char: str | None = None,
|
40
40
|
attribute_name: str = "Command",
|
41
41
|
):
|
42
42
|
"""
|
fractal_server/syringe.py
CHANGED
@@ -4,7 +4,6 @@ import shutil
|
|
4
4
|
import time
|
5
5
|
from pathlib import Path
|
6
6
|
from tempfile import TemporaryDirectory
|
7
|
-
from typing import Optional
|
8
7
|
|
9
8
|
from ..utils_database import create_db_tasks_and_update_task_group_sync
|
10
9
|
from ._utils import _customize_and_run_template
|
@@ -39,7 +38,7 @@ def collect_local(
|
|
39
38
|
*,
|
40
39
|
task_group_activity_id: int,
|
41
40
|
task_group_id: int,
|
42
|
-
wheel_file:
|
41
|
+
wheel_file: WheelFile | None = None,
|
43
42
|
) -> None:
|
44
43
|
"""
|
45
44
|
Collect a task package.
|
@@ -132,7 +131,7 @@ def collect_local(
|
|
132
131
|
).as_posix(),
|
133
132
|
prefix=(
|
134
133
|
f"{int(time.time())}_"
|
135
|
-
f"{TaskGroupActivityActionV2.COLLECT
|
134
|
+
f"{TaskGroupActivityActionV2.COLLECT}_"
|
136
135
|
),
|
137
136
|
logger_name=LOGGER_NAME,
|
138
137
|
)
|