fractal-server 2.10.1__py3-none-any.whl → 2.10.3__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/__main__.py +71 -0
- fractal_server/app/routes/api/v2/task_collection.py +10 -0
- fractal_server/app/routes/api/v2/workflowtask.py +7 -11
- fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +5 -0
- fractal_server/app/runner/executors/slurm/sudo/executor.py +24 -0
- fractal_server/app/security/__init__.py +19 -2
- fractal_server/app/security/signup_email.py +39 -0
- fractal_server/config.py +121 -3
- fractal_server/tasks/utils.py +1 -0
- fractal_server/tasks/v2/local/deactivate.py +11 -0
- fractal_server/tasks/v2/local/reactivate.py +0 -1
- fractal_server/tasks/v2/ssh/deactivate.py +11 -0
- fractal_server/tasks/v2/ssh/reactivate.py +0 -1
- fractal_server/tasks/v2/templates/2_pip_install.sh +5 -4
- fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +4 -3
- fractal_server/tasks/v2/utils_templates.py +1 -0
- fractal_server/urls.py +2 -1
- {fractal_server-2.10.1.dist-info → fractal_server-2.10.3.dist-info}/METADATA +2 -1
- {fractal_server-2.10.1.dist-info → fractal_server-2.10.3.dist-info}/RECORD +23 -22
- {fractal_server-2.10.1.dist-info → fractal_server-2.10.3.dist-info}/LICENSE +0 -0
- {fractal_server-2.10.1.dist-info → fractal_server-2.10.3.dist-info}/WHEEL +0 -0
- {fractal_server-2.10.1.dist-info → fractal_server-2.10.3.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.10.
|
1
|
+
__VERSION__ = "2.10.3"
|
fractal_server/__main__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import argparse as ap
|
2
2
|
import asyncio
|
3
|
+
import json
|
3
4
|
import sys
|
4
5
|
|
5
6
|
import uvicorn
|
@@ -62,6 +63,41 @@ update_db_data_parser = subparsers.add_parser(
|
|
62
63
|
description="Apply data-migration script to an existing database.",
|
63
64
|
)
|
64
65
|
|
66
|
+
# fractalctl email-settings
|
67
|
+
email_settings_parser = subparsers.add_parser(
|
68
|
+
"email-settings",
|
69
|
+
description=(
|
70
|
+
"Generate valid values for environment variables "
|
71
|
+
"`FRACTAL_EMAIL_SETTINGS` and `FRACTAL_EMAIL_SETTINGS_KEY`."
|
72
|
+
),
|
73
|
+
)
|
74
|
+
email_settings_parser.add_argument(
|
75
|
+
"sender",
|
76
|
+
type=str,
|
77
|
+
help="Email of the sender",
|
78
|
+
)
|
79
|
+
email_settings_parser.add_argument(
|
80
|
+
"server",
|
81
|
+
type=str,
|
82
|
+
help="SMPT server used to send emails",
|
83
|
+
)
|
84
|
+
email_settings_parser.add_argument(
|
85
|
+
"port",
|
86
|
+
type=int,
|
87
|
+
help="Port of the SMPT server",
|
88
|
+
)
|
89
|
+
email_settings_parser.add_argument(
|
90
|
+
"instance",
|
91
|
+
type=str,
|
92
|
+
help="Name of the Fractal instance sending emails",
|
93
|
+
)
|
94
|
+
email_settings_parser.add_argument(
|
95
|
+
"--skip-starttls",
|
96
|
+
action="store_true",
|
97
|
+
default=False,
|
98
|
+
help="If set, skip the execution of `starttls` when sending emails",
|
99
|
+
)
|
100
|
+
|
65
101
|
|
66
102
|
def save_openapi(dest="openapi.json"):
|
67
103
|
from fractal_server.main import start_application
|
@@ -188,6 +224,33 @@ def update_db_data():
|
|
188
224
|
current_update_db_data_module.fix_db()
|
189
225
|
|
190
226
|
|
227
|
+
def print_mail_settings(
|
228
|
+
sender: str,
|
229
|
+
server: str,
|
230
|
+
port: int,
|
231
|
+
instance: str,
|
232
|
+
skip_starttls: bool,
|
233
|
+
):
|
234
|
+
from cryptography.fernet import Fernet
|
235
|
+
|
236
|
+
password = input(f"Insert email password for sender '{sender}': ")
|
237
|
+
key = Fernet.generate_key().decode("utf-8")
|
238
|
+
fractal_mail_settings = json.dumps(
|
239
|
+
dict(
|
240
|
+
sender=sender,
|
241
|
+
password=password,
|
242
|
+
smtp_server=server,
|
243
|
+
port=port,
|
244
|
+
instance_name=instance,
|
245
|
+
use_starttls=(not skip_starttls),
|
246
|
+
)
|
247
|
+
).encode("utf-8")
|
248
|
+
email_settings = Fernet(key).encrypt(fractal_mail_settings).decode("utf-8")
|
249
|
+
|
250
|
+
print(f"\nFRACTAL_EMAIL_SETTINGS: {email_settings}")
|
251
|
+
print(f"FRACTAL_EMAIL_SETTINGS_KEY: {key}")
|
252
|
+
|
253
|
+
|
191
254
|
def run():
|
192
255
|
args = parser.parse_args(sys.argv[1:])
|
193
256
|
|
@@ -204,6 +267,14 @@ def run():
|
|
204
267
|
port=args.port,
|
205
268
|
reload=args.reload,
|
206
269
|
)
|
270
|
+
elif args.cmd == "email-settings":
|
271
|
+
print_mail_settings(
|
272
|
+
sender=args.sender,
|
273
|
+
server=args.server,
|
274
|
+
port=args.port,
|
275
|
+
instance=args.instance,
|
276
|
+
skip_starttls=args.skip_starttls,
|
277
|
+
)
|
207
278
|
else:
|
208
279
|
sys.exit(f"Error: invalid command '{args.cmd}'.")
|
209
280
|
|
@@ -56,6 +56,8 @@ router = APIRouter()
|
|
56
56
|
|
57
57
|
logger = set_logger(__name__)
|
58
58
|
|
59
|
+
FORBIDDEN_CHAR_WHEEL = [";", "/"]
|
60
|
+
|
59
61
|
|
60
62
|
class CollectionRequestData(BaseModel):
|
61
63
|
"""
|
@@ -90,6 +92,14 @@ class CollectionRequestData(BaseModel):
|
|
90
92
|
f"provided (given package_version='{package_version}')."
|
91
93
|
)
|
92
94
|
values["origin"] = TaskGroupV2OriginEnum.WHEELFILE
|
95
|
+
|
96
|
+
for forbidden_char in FORBIDDEN_CHAR_WHEEL:
|
97
|
+
if forbidden_char in file.filename:
|
98
|
+
raise ValueError(
|
99
|
+
"Wheel filename has forbidden characters, "
|
100
|
+
f"{FORBIDDEN_CHAR_WHEEL}"
|
101
|
+
)
|
102
|
+
|
93
103
|
return values
|
94
104
|
|
95
105
|
|
@@ -6,7 +6,6 @@ from fastapi import Depends
|
|
6
6
|
from fastapi import HTTPException
|
7
7
|
from fastapi import Response
|
8
8
|
from fastapi import status
|
9
|
-
from sqlalchemy.orm.attributes import flag_modified
|
10
9
|
|
11
10
|
from ....db import AsyncSession
|
12
11
|
from ....db import get_async_db
|
@@ -89,8 +88,7 @@ async def replace_workflowtask(
|
|
89
88
|
_args_parallel = replace.args_parallel
|
90
89
|
|
91
90
|
# If user's changes to `meta_non_parallel` are compatible with new task,
|
92
|
-
# keep them;
|
93
|
-
# else, get `meta_non_parallel` from new task
|
91
|
+
# keep them; else, get `meta_non_parallel` from new task
|
94
92
|
if (
|
95
93
|
old_workflow_task.meta_non_parallel
|
96
94
|
!= old_workflow_task.task.meta_non_parallel
|
@@ -107,12 +105,10 @@ async def replace_workflowtask(
|
|
107
105
|
_meta_parallel = task.meta_parallel
|
108
106
|
|
109
107
|
new_workflow_task = WorkflowTaskV2(
|
110
|
-
# new task
|
111
|
-
task_type=task.type,
|
112
108
|
task_id=task.id,
|
109
|
+
task_type=task.type,
|
113
110
|
task=task,
|
114
|
-
# old values
|
115
|
-
order=old_workflow_task.order,
|
111
|
+
# old-task values
|
116
112
|
input_filters=old_workflow_task.input_filters,
|
117
113
|
# possibly new values
|
118
114
|
args_non_parallel=_args_non_parallel,
|
@@ -121,11 +117,11 @@ async def replace_workflowtask(
|
|
121
117
|
meta_parallel=_meta_parallel,
|
122
118
|
)
|
123
119
|
|
124
|
-
|
125
|
-
workflow.task_list.
|
126
|
-
|
120
|
+
workflow_task_order = old_workflow_task.order
|
121
|
+
workflow.task_list.remove(old_workflow_task)
|
122
|
+
workflow.task_list.insert(workflow_task_order, new_workflow_task)
|
127
123
|
await db.commit()
|
128
|
-
|
124
|
+
await db.refresh(new_workflow_task)
|
129
125
|
return new_workflow_task
|
130
126
|
|
131
127
|
|
@@ -9,6 +9,7 @@ from cfut import FileWaitThread
|
|
9
9
|
|
10
10
|
from ......logger import set_logger
|
11
11
|
from ._check_jobs_status import _jobs_finished
|
12
|
+
from fractal_server.app.runner.exceptions import JobExecutionError
|
12
13
|
|
13
14
|
logger = set_logger(__name__)
|
14
15
|
|
@@ -56,6 +57,10 @@ class FractalFileWaitThread(FileWaitThread):
|
|
56
57
|
Note that (with respect to clusterfutures) we replaced `filename` with
|
57
58
|
`filenames`.
|
58
59
|
"""
|
60
|
+
if self.shutdown:
|
61
|
+
error_msg = "Cannot call `wait` method after executor shutdown."
|
62
|
+
logger.warning(error_msg)
|
63
|
+
raise JobExecutionError(info=error_msg)
|
59
64
|
with self.lock:
|
60
65
|
self.waiting[filenames] = jobid
|
61
66
|
|
@@ -331,6 +331,12 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
331
331
|
Future representing the execution of the current SLURM job.
|
332
332
|
"""
|
333
333
|
|
334
|
+
# Do not continue if auxiliary thread was shut down
|
335
|
+
if self.wait_thread.shutdown:
|
336
|
+
error_msg = "Cannot call `submit` method after executor shutdown"
|
337
|
+
logger.warning(error_msg)
|
338
|
+
raise JobExecutionError(info=error_msg)
|
339
|
+
|
334
340
|
# Set slurm_file_prefix
|
335
341
|
slurm_file_prefix = task_files.file_prefix
|
336
342
|
|
@@ -395,6 +401,12 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
395
401
|
|
396
402
|
"""
|
397
403
|
|
404
|
+
# Do not continue if auxiliary thread was shut down
|
405
|
+
if self.wait_thread.shutdown:
|
406
|
+
error_msg = "Cannot call `map` method after executor shutdown"
|
407
|
+
logger.warning(error_msg)
|
408
|
+
raise JobExecutionError(info=error_msg)
|
409
|
+
|
398
410
|
def _result_or_cancel(fut):
|
399
411
|
"""
|
400
412
|
This function is based on the Python Standard Library 3.11.
|
@@ -546,6 +558,15 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
546
558
|
Returns:
|
547
559
|
Future representing the execution of the current SLURM job.
|
548
560
|
"""
|
561
|
+
|
562
|
+
# Prevent calling sbatch if auxiliary thread was shut down
|
563
|
+
if self.wait_thread.shutdown:
|
564
|
+
error_msg = (
|
565
|
+
"Cannot call `_submit_job` method after executor shutdown"
|
566
|
+
)
|
567
|
+
logger.warning(error_msg)
|
568
|
+
raise JobExecutionError(info=error_msg)
|
569
|
+
|
549
570
|
fut: Future = Future()
|
550
571
|
|
551
572
|
# Inject SLURM account (if set) into slurm_config
|
@@ -1207,6 +1228,9 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
1207
1228
|
logger.error(error_msg)
|
1208
1229
|
raise JobExecutionError(info=error_msg)
|
1209
1230
|
|
1231
|
+
# Redudantly set thread shutdown attribute to True
|
1232
|
+
self.wait_thread.shutdown = True
|
1233
|
+
|
1210
1234
|
logger.debug("Executor shutdown: end")
|
1211
1235
|
|
1212
1236
|
def _stop_and_join_wait_thread(self):
|
@@ -59,7 +59,10 @@ from fractal_server.app.models import UserGroup
|
|
59
59
|
from fractal_server.app.models import UserOAuth
|
60
60
|
from fractal_server.app.models import UserSettings
|
61
61
|
from fractal_server.app.schemas.user import UserCreate
|
62
|
+
from fractal_server.app.security.signup_email import mail_new_oauth_signup
|
63
|
+
from fractal_server.config import get_settings
|
62
64
|
from fractal_server.logger import set_logger
|
65
|
+
from fractal_server.syringe import Inject
|
63
66
|
|
64
67
|
logger = set_logger(__name__)
|
65
68
|
|
@@ -211,8 +214,6 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
|
211
214
|
async def on_after_register(
|
212
215
|
self, user: UserOAuth, request: Optional[Request] = None
|
213
216
|
):
|
214
|
-
logger = set_logger("fractal_server.on_after_register")
|
215
|
-
|
216
217
|
logger.info(
|
217
218
|
f"New-user registration completed ({user.id=}, {user.email=})."
|
218
219
|
)
|
@@ -248,6 +249,22 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
|
248
249
|
f"to '{this_user.email}'."
|
249
250
|
)
|
250
251
|
|
252
|
+
# Send mail section
|
253
|
+
settings = Inject(get_settings)
|
254
|
+
|
255
|
+
if this_user.oauth_accounts and settings.MAIL_SETTINGS is not None:
|
256
|
+
try:
|
257
|
+
mail_new_oauth_signup(
|
258
|
+
msg=f"New user registered: '{this_user.email}'.",
|
259
|
+
mail_settings=settings.MAIL_SETTINGS,
|
260
|
+
)
|
261
|
+
except Exception as e:
|
262
|
+
logger.error(
|
263
|
+
"ERROR sending notification email after oauth "
|
264
|
+
f"registration of {this_user.email}. "
|
265
|
+
f"Original error: '{e}'."
|
266
|
+
)
|
267
|
+
|
251
268
|
|
252
269
|
async def get_user_manager(
|
253
270
|
user_db: SQLModelUserDatabaseAsync = Depends(get_user_db),
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import smtplib
|
2
|
+
from email.message import EmailMessage
|
3
|
+
from email.utils import formataddr
|
4
|
+
|
5
|
+
from fractal_server.config import MailSettings
|
6
|
+
|
7
|
+
|
8
|
+
def mail_new_oauth_signup(msg: str, mail_settings: MailSettings):
|
9
|
+
"""
|
10
|
+
Send an email using the specified settings to notify a new OAuth signup.
|
11
|
+
"""
|
12
|
+
|
13
|
+
mail_msg = EmailMessage()
|
14
|
+
mail_msg.set_content(msg)
|
15
|
+
mail_msg["From"] = formataddr((mail_settings.sender, mail_settings.sender))
|
16
|
+
mail_msg["To"] = ",".join(
|
17
|
+
[
|
18
|
+
formataddr((recipient, recipient))
|
19
|
+
for recipient in mail_settings.recipients
|
20
|
+
]
|
21
|
+
)
|
22
|
+
mail_msg[
|
23
|
+
"Subject"
|
24
|
+
] = f"[Fractal, {mail_settings.instance_name}] New OAuth signup"
|
25
|
+
|
26
|
+
with smtplib.SMTP(mail_settings.smtp_server, mail_settings.port) as server:
|
27
|
+
server.ehlo()
|
28
|
+
if mail_settings.use_starttls:
|
29
|
+
server.starttls()
|
30
|
+
server.ehlo()
|
31
|
+
|
32
|
+
server.login(
|
33
|
+
user=mail_settings.sender, password=mail_settings.password
|
34
|
+
)
|
35
|
+
server.sendmail(
|
36
|
+
from_addr=mail_settings.sender,
|
37
|
+
to_addrs=mail_settings.recipients,
|
38
|
+
msg=mail_msg.as_string(),
|
39
|
+
)
|
fractal_server/config.py
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
# <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
|
12
12
|
# Institute for Biomedical Research and Pelkmans Lab from the University of
|
13
13
|
# Zurich.
|
14
|
+
import json
|
14
15
|
import logging
|
15
16
|
import shutil
|
16
17
|
import sys
|
@@ -21,9 +22,11 @@ from typing import Literal
|
|
21
22
|
from typing import Optional
|
22
23
|
from typing import TypeVar
|
23
24
|
|
25
|
+
from cryptography.fernet import Fernet
|
24
26
|
from dotenv import load_dotenv
|
25
27
|
from pydantic import BaseModel
|
26
28
|
from pydantic import BaseSettings
|
29
|
+
from pydantic import EmailStr
|
27
30
|
from pydantic import Field
|
28
31
|
from pydantic import root_validator
|
29
32
|
from pydantic import validator
|
@@ -32,6 +35,29 @@ from sqlalchemy.engine import URL
|
|
32
35
|
import fractal_server
|
33
36
|
|
34
37
|
|
38
|
+
class MailSettings(BaseModel):
|
39
|
+
"""
|
40
|
+
Schema for `MailSettings`
|
41
|
+
|
42
|
+
Attributes:
|
43
|
+
sender: Sender email address
|
44
|
+
recipients: List of recipients email address
|
45
|
+
smtp_server: SMTP server address
|
46
|
+
port: SMTP server port
|
47
|
+
password: Sender password
|
48
|
+
instance_name: Name of SMTP server instance
|
49
|
+
use_starttls: Using or not security protocol
|
50
|
+
"""
|
51
|
+
|
52
|
+
sender: EmailStr
|
53
|
+
recipients: list[EmailStr] = Field(min_items=1)
|
54
|
+
smtp_server: str
|
55
|
+
port: int
|
56
|
+
password: str
|
57
|
+
instance_name: str
|
58
|
+
use_starttls: bool
|
59
|
+
|
60
|
+
|
35
61
|
class FractalConfigurationError(RuntimeError):
|
36
62
|
pass
|
37
63
|
|
@@ -492,6 +518,39 @@ class Settings(BaseSettings):
|
|
492
518
|
Whether to include the v1 API.
|
493
519
|
"""
|
494
520
|
|
521
|
+
FRACTAL_PIP_CACHE_DIR: Optional[str] = None
|
522
|
+
"""
|
523
|
+
Absolute path to the cache directory for `pip`; if unset,
|
524
|
+
`--no-cache-dir` is used.
|
525
|
+
"""
|
526
|
+
|
527
|
+
@validator("FRACTAL_PIP_CACHE_DIR", always=True)
|
528
|
+
def absolute_FRACTAL_PIP_CACHE_DIR(cls, v):
|
529
|
+
"""
|
530
|
+
If `FRACTAL_PIP_CACHE_DIR` is a relative path, fail.
|
531
|
+
"""
|
532
|
+
if v is None:
|
533
|
+
return None
|
534
|
+
elif not Path(v).is_absolute():
|
535
|
+
raise FractalConfigurationError(
|
536
|
+
f"Non-absolute value for FRACTAL_PIP_CACHE_DIR={v}"
|
537
|
+
)
|
538
|
+
else:
|
539
|
+
return v
|
540
|
+
|
541
|
+
@property
|
542
|
+
def PIP_CACHE_DIR_ARG(self) -> str:
|
543
|
+
"""
|
544
|
+
Option for `pip install`, based on `FRACTAL_PIP_CACHE_DIR` value.
|
545
|
+
|
546
|
+
If `FRACTAL_PIP_CACHE_DIR` is set, then return
|
547
|
+
`--cache-dir /somewhere`; else return `--no-cache-dir`.
|
548
|
+
"""
|
549
|
+
if self.FRACTAL_PIP_CACHE_DIR is not None:
|
550
|
+
return f"--cache-dir {self.FRACTAL_PIP_CACHE_DIR}"
|
551
|
+
else:
|
552
|
+
return "--no-cache-dir"
|
553
|
+
|
495
554
|
FRACTAL_MAX_PIP_VERSION: str = "24.0"
|
496
555
|
"""
|
497
556
|
Maximum value at which to update `pip` before performing task collection.
|
@@ -527,9 +586,69 @@ class Settings(BaseSettings):
|
|
527
586
|
FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
|
528
587
|
"""
|
529
588
|
|
589
|
+
###########################################################################
|
590
|
+
# SMTP SERVICE
|
591
|
+
###########################################################################
|
592
|
+
FRACTAL_EMAIL_SETTINGS: Optional[str] = None
|
593
|
+
"""
|
594
|
+
Encrypted version of settings dictionary, with keys `sender`, `password`,
|
595
|
+
`smtp_server`, `port`, `instance_name`, `use_starttls`.
|
596
|
+
"""
|
597
|
+
FRACTAL_EMAIL_SETTINGS_KEY: Optional[str] = None
|
598
|
+
"""
|
599
|
+
Key value for `cryptography.fernet` decrypt
|
600
|
+
"""
|
601
|
+
FRACTAL_EMAIL_RECIPIENTS: Optional[str] = None
|
602
|
+
"""
|
603
|
+
List of email receivers, separated with commas
|
604
|
+
"""
|
605
|
+
|
606
|
+
@property
|
607
|
+
def MAIL_SETTINGS(self) -> Optional[MailSettings]:
|
608
|
+
if (
|
609
|
+
self.FRACTAL_EMAIL_SETTINGS is not None
|
610
|
+
and self.FRACTAL_EMAIL_SETTINGS_KEY is not None
|
611
|
+
and self.FRACTAL_EMAIL_RECIPIENTS is not None
|
612
|
+
):
|
613
|
+
smpt_settings = (
|
614
|
+
Fernet(self.FRACTAL_EMAIL_SETTINGS_KEY)
|
615
|
+
.decrypt(self.FRACTAL_EMAIL_SETTINGS)
|
616
|
+
.decode("utf-8")
|
617
|
+
)
|
618
|
+
recipients = self.FRACTAL_EMAIL_RECIPIENTS.split(",")
|
619
|
+
mail_settings = MailSettings(
|
620
|
+
**json.loads(smpt_settings), recipients=recipients
|
621
|
+
)
|
622
|
+
return mail_settings
|
623
|
+
elif not all(
|
624
|
+
[
|
625
|
+
self.FRACTAL_EMAIL_RECIPIENTS is None,
|
626
|
+
self.FRACTAL_EMAIL_SETTINGS_KEY is None,
|
627
|
+
self.FRACTAL_EMAIL_SETTINGS is None,
|
628
|
+
]
|
629
|
+
):
|
630
|
+
raise ValueError(
|
631
|
+
"You must set all SMPT config variables: "
|
632
|
+
f"{self.FRACTAL_EMAIL_SETTINGS=}, "
|
633
|
+
f"{self.FRACTAL_EMAIL_RECIPIENTS=}, "
|
634
|
+
f"{self.FRACTAL_EMAIL_SETTINGS_KEY=}, "
|
635
|
+
)
|
636
|
+
|
530
637
|
###########################################################################
|
531
638
|
# BUSINESS LOGIC
|
532
639
|
###########################################################################
|
640
|
+
|
641
|
+
def check_fractal_mail_settings(self):
|
642
|
+
"""
|
643
|
+
Checks that the mail settings are properly set.
|
644
|
+
"""
|
645
|
+
try:
|
646
|
+
self.MAIL_SETTINGS
|
647
|
+
except Exception as e:
|
648
|
+
raise FractalConfigurationError(
|
649
|
+
f"Invalid email configuration settings. Original error: {e}"
|
650
|
+
)
|
651
|
+
|
533
652
|
def check_db(self) -> None:
|
534
653
|
"""
|
535
654
|
Checks that db environment variables are properly set.
|
@@ -538,7 +657,6 @@ class Settings(BaseSettings):
|
|
538
657
|
raise FractalConfigurationError("POSTGRES_DB cannot be None.")
|
539
658
|
|
540
659
|
def check_runner(self) -> None:
|
541
|
-
|
542
660
|
if not self.FRACTAL_RUNNER_WORKING_BASE_DIR:
|
543
661
|
raise FractalConfigurationError(
|
544
662
|
"FRACTAL_RUNNER_WORKING_BASE_DIR cannot be None."
|
@@ -546,7 +664,6 @@ class Settings(BaseSettings):
|
|
546
664
|
|
547
665
|
info = f"FRACTAL_RUNNER_BACKEND={self.FRACTAL_RUNNER_BACKEND}"
|
548
666
|
if self.FRACTAL_RUNNER_BACKEND == "slurm":
|
549
|
-
|
550
667
|
from fractal_server.app.runner.executors.slurm._slurm_config import ( # noqa: E501
|
551
668
|
load_slurm_config_file,
|
552
669
|
)
|
@@ -637,12 +754,13 @@ class Settings(BaseSettings):
|
|
637
754
|
|
638
755
|
self.check_db()
|
639
756
|
self.check_runner()
|
757
|
+
self.check_fractal_mail_settings()
|
640
758
|
|
641
759
|
def get_sanitized(self) -> dict:
|
642
760
|
def _must_be_sanitized(string) -> bool:
|
643
761
|
if not string.upper().startswith("FRACTAL") or any(
|
644
762
|
s in string.upper()
|
645
|
-
for s in ["PASSWORD", "SECRET", "PWD", "TOKEN"]
|
763
|
+
for s in ["PASSWORD", "SECRET", "PWD", "TOKEN", "KEY"]
|
646
764
|
):
|
647
765
|
return True
|
648
766
|
else:
|
fractal_server/tasks/utils.py
CHANGED
@@ -6,6 +6,7 @@ from fractal_server.syringe import Inject
|
|
6
6
|
COLLECTION_FILENAME = "collection.json"
|
7
7
|
COLLECTION_LOG_FILENAME = "collection.log"
|
8
8
|
COLLECTION_FREEZE_FILENAME = "collection_freeze.txt"
|
9
|
+
FORBIDDEN_DEPENDENCY_STRINGS = ["github.com"]
|
9
10
|
|
10
11
|
|
11
12
|
def get_absolute_venv_path_v1(venv_path: Path) -> Path:
|
@@ -15,6 +15,7 @@ from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
|
|
15
15
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
16
16
|
from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
|
17
17
|
from fractal_server.logger import set_logger
|
18
|
+
from fractal_server.tasks.utils import FORBIDDEN_DEPENDENCY_STRINGS
|
18
19
|
from fractal_server.tasks.utils import get_log_path
|
19
20
|
from fractal_server.tasks.v2.utils_background import get_current_log
|
20
21
|
from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
|
@@ -189,6 +190,16 @@ def deactivate_local(
|
|
189
190
|
"task-group attributes."
|
190
191
|
)
|
191
192
|
|
193
|
+
# Fail if `pip_freeze` includes "github.com", see
|
194
|
+
# https://github.com/fractal-analytics-platform/fractal-server/issues/2142
|
195
|
+
for forbidden_string in FORBIDDEN_DEPENDENCY_STRINGS:
|
196
|
+
if forbidden_string in task_group.pip_freeze:
|
197
|
+
raise ValueError(
|
198
|
+
"Deactivation and reactivation of task packages "
|
199
|
+
f"with direct {forbidden_string} dependencies "
|
200
|
+
"are not currently supported. Exit."
|
201
|
+
)
|
202
|
+
|
192
203
|
# We now have all required information for reactivating the
|
193
204
|
# virtual environment at a later point
|
194
205
|
|
@@ -119,7 +119,6 @@ def reactivate_local(
|
|
119
119
|
)
|
120
120
|
logger.debug("end - create venv")
|
121
121
|
activity.log = get_current_log(log_file_path)
|
122
|
-
activity.timestamp_ended = get_timestamp()
|
123
122
|
activity = add_commit_refresh(obj=activity, db=db)
|
124
123
|
|
125
124
|
logger.debug("start - install from pip freeze")
|
@@ -16,6 +16,7 @@ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
|
16
16
|
from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
|
17
17
|
from fractal_server.logger import set_logger
|
18
18
|
from fractal_server.ssh._fabric import FractalSSH
|
19
|
+
from fractal_server.tasks.utils import FORBIDDEN_DEPENDENCY_STRINGS
|
19
20
|
from fractal_server.tasks.utils import get_log_path
|
20
21
|
from fractal_server.tasks.v2.utils_background import get_current_log
|
21
22
|
from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
|
@@ -221,6 +222,16 @@ def deactivate_ssh(
|
|
221
222
|
"task-group attributes."
|
222
223
|
)
|
223
224
|
|
225
|
+
# Fail if `pip_freeze` includes "github", see
|
226
|
+
# https://github.com/fractal-analytics-platform/fractal-server/issues/2142
|
227
|
+
for forbidden_string in FORBIDDEN_DEPENDENCY_STRINGS:
|
228
|
+
if forbidden_string in task_group.pip_freeze:
|
229
|
+
raise ValueError(
|
230
|
+
"Deactivation and reactivation of task packages "
|
231
|
+
f"with direct {forbidden_string} dependencies "
|
232
|
+
"are not currently supported. Exit."
|
233
|
+
)
|
234
|
+
|
224
235
|
# We now have all required information for reactivating the
|
225
236
|
# virtual environment at a later point
|
226
237
|
|
@@ -159,7 +159,6 @@ def reactivate_ssh(
|
|
159
159
|
)
|
160
160
|
logger.debug("end - create venv")
|
161
161
|
activity.log = get_current_log(log_file_path)
|
162
|
-
activity.timestamp_ended = get_timestamp()
|
163
162
|
activity = add_commit_refresh(obj=activity, db=db)
|
164
163
|
|
165
164
|
logger.debug("start - install from pip freeze")
|
@@ -7,9 +7,10 @@ write_log(){
|
|
7
7
|
|
8
8
|
# Variables to be filled within fractal-server
|
9
9
|
PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
|
10
|
-
INSTALL_STRING=__INSTALL_STRING__
|
10
|
+
INSTALL_STRING="__INSTALL_STRING__"
|
11
11
|
PINNED_PACKAGE_LIST="__PINNED_PACKAGE_LIST__"
|
12
12
|
FRACTAL_MAX_PIP_VERSION="__FRACTAL_MAX_PIP_VERSION__"
|
13
|
+
FRACTAL_PIP_CACHE_DIR_ARG="__FRACTAL_PIP_CACHE_DIR_ARG__"
|
13
14
|
|
14
15
|
TIME_START=$(date +%s)
|
15
16
|
|
@@ -17,8 +18,8 @@ VENVPYTHON=${PACKAGE_ENV_DIR}/bin/python
|
|
17
18
|
|
18
19
|
# Upgrade `pip` and install `setuptools`
|
19
20
|
write_log "START upgrade pip and install setuptools"
|
20
|
-
"$VENVPYTHON" -m pip install
|
21
|
-
"$VENVPYTHON" -m pip install
|
21
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} "pip<=${FRACTAL_MAX_PIP_VERSION}" --upgrade
|
22
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} setuptools
|
22
23
|
write_log "END upgrade pip and install setuptools"
|
23
24
|
echo
|
24
25
|
|
@@ -41,7 +42,7 @@ if [ "$PINNED_PACKAGE_LIST" != "" ]; then
|
|
41
42
|
done
|
42
43
|
|
43
44
|
write_log "All packages in ${PINNED_PACKAGE_LIST} are already installed, proceed with specific versions."
|
44
|
-
"$VENVPYTHON" -m pip install
|
45
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} "$PINNED_PACKAGE_LIST"
|
45
46
|
write_log "END installing pinned versions $PINNED_PACKAGE_LIST"
|
46
47
|
else
|
47
48
|
write_log "SKIP installing pinned versions $PINNED_PACKAGE_LIST (empty list)"
|
@@ -9,6 +9,7 @@ write_log(){
|
|
9
9
|
PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
|
10
10
|
PIP_FREEZE_FILE=__PIP_FREEZE_FILE__
|
11
11
|
FRACTAL_MAX_PIP_VERSION=__FRACTAL_MAX_PIP_VERSION__
|
12
|
+
FRACTAL_PIP_CACHE_DIR_ARG="__FRACTAL_PIP_CACHE_DIR_ARG__"
|
12
13
|
|
13
14
|
TIME_START=$(date +%s)
|
14
15
|
|
@@ -16,14 +17,14 @@ VENVPYTHON=${PACKAGE_ENV_DIR}/bin/python
|
|
16
17
|
|
17
18
|
# Upgrade `pip` and install `setuptools`
|
18
19
|
write_log "START upgrade pip and install setuptools"
|
19
|
-
"$VENVPYTHON" -m pip install
|
20
|
-
"$VENVPYTHON" -m pip install
|
20
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} "pip<=${FRACTAL_MAX_PIP_VERSION}" --upgrade
|
21
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} setuptools
|
21
22
|
write_log "END upgrade pip and install setuptools"
|
22
23
|
echo
|
23
24
|
|
24
25
|
# Install from pip-freeze file
|
25
26
|
write_log "START installing requirements from ${PIP_FREEZE_FILE}"
|
26
|
-
"$VENVPYTHON" -m pip install -r "${PIP_FREEZE_FILE}"
|
27
|
+
"$VENVPYTHON" -m pip install ${FRACTAL_PIP_CACHE_DIR_ARG} -r "${PIP_FREEZE_FILE}"
|
27
28
|
write_log "END installing requirements from ${PIP_FREEZE_FILE}"
|
28
29
|
echo
|
29
30
|
|
fractal_server/urls.py
CHANGED
@@ -2,12 +2,13 @@ from os.path import normpath
|
|
2
2
|
|
3
3
|
|
4
4
|
def normalize_url(url: str) -> str:
|
5
|
+
url = url.strip()
|
5
6
|
if url.startswith("/"):
|
6
7
|
return normpath(url)
|
7
8
|
elif url.startswith("s3"):
|
8
9
|
# It would be better to have a NotImplementedError
|
9
10
|
# but Pydantic Validation + FastAPI require
|
10
11
|
# ValueError, TypeError or AssertionError
|
11
|
-
raise ValueError("S3 handling not implemented yet")
|
12
|
+
raise ValueError("S3 handling not implemented yet.")
|
12
13
|
else:
|
13
14
|
raise ValueError("URLs must begin with '/' or 's3'.")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fractal-server
|
3
|
-
Version: 2.10.
|
3
|
+
Version: 2.10.3
|
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
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Requires-Dist: alembic (>=1.13.1,<2.0.0)
|
16
16
|
Requires-Dist: cloudpickle (>=3.0.0,<3.1.0)
|
17
17
|
Requires-Dist: clusterfutures (>=0.5,<0.6)
|
18
|
+
Requires-Dist: cryptography (>=44.0.0,<44.1.0)
|
18
19
|
Requires-Dist: fabric (>=3.2.2,<4.0.0)
|
19
20
|
Requires-Dist: fastapi (>=0.115.0,<0.116.0)
|
20
21
|
Requires-Dist: fastapi-users[oauth] (>=14,<15)
|
@@ -1,5 +1,5 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
2
|
-
fractal_server/__main__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=rYclpU8ceRdk5UXfl7ivOwKeHGWqQLFzTFUQXjy6mvA,23
|
2
|
+
fractal_server/__main__.py,sha256=D2YTmSowmXNyvqOjW_HeItCZT2UliWlySl_owicaZg0,8026
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
fractal_server/app/db/__init__.py,sha256=wup2wcOkyOh8Vd0Xm76PZn_naxeMqaL4eF8DHHXTGlI,2889
|
@@ -53,13 +53,13 @@ fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhz
|
|
53
53
|
fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
|
54
54
|
fractal_server/app/routes/api/v2/submit.py,sha256=cQwt0oK8xjHMGA_bQrw4Um8jd_aCvgmWfoqSQDh12hQ,8246
|
55
55
|
fractal_server/app/routes/api/v2/task.py,sha256=K0ik33t7vL8BAK5S7fqyJDNdRK4stGqb_73bSa8tvPE,7159
|
56
|
-
fractal_server/app/routes/api/v2/task_collection.py,sha256=
|
56
|
+
fractal_server/app/routes/api/v2/task_collection.py,sha256=9p8w9UnN6RFszC1ohy9Uo3I4HIMVdfD8fYGWuQqzxMU,12682
|
57
57
|
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=cctW61-C2QYF2KXluS15lLhZJS_kt30Ca6UGLFO32z0,6207
|
58
58
|
fractal_server/app/routes/api/v2/task_group.py,sha256=4o2N0z7jK7VUVlJZMM4GveCCc4JKxYJx9-PMmsYIlJQ,8256
|
59
59
|
fractal_server/app/routes/api/v2/task_group_lifecycle.py,sha256=3o9bCC8ubMwffQPPaxQZy-CjH9IB2RkIReIecI6L2_w,9300
|
60
60
|
fractal_server/app/routes/api/v2/workflow.py,sha256=vjCNRzMHaAB4YWbAEWGlELHXDN4GjtE26IkIiB15RGM,8682
|
61
61
|
fractal_server/app/routes/api/v2/workflow_import.py,sha256=-7Er3FWGF_1xI2qHFO9gfLVQAok5bojd7mbzQxa9Ofw,10858
|
62
|
-
fractal_server/app/routes/api/v2/workflowtask.py,sha256=
|
62
|
+
fractal_server/app/routes/api/v2/workflowtask.py,sha256=dh8IxFSx50dY7kXldr9vC_NdQrFqv_heefOuZBX-7XE,10714
|
63
63
|
fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
|
64
64
|
fractal_server/app/routes/auth/_aux_auth.py,sha256=ifkNocTYatBSMYGwiR14qohmvR9SfMldceiEj6uJBrU,4783
|
65
65
|
fractal_server/app/routes/auth/current_user.py,sha256=I3aVY5etWAJ_SH6t65Mj5TjvB2X8sAGuu1KG7FxLyPU,5883
|
@@ -90,9 +90,9 @@ fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py,sha256=rwlqZzoGo4SAb
|
|
90
90
|
fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=U2-tNE_5ECHFIoXjEvBlaSXKaIf-1IXZlDs0c34mab8,54110
|
91
91
|
fractal_server/app/runner/executors/slurm/sudo/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
|
92
92
|
fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py,sha256=wAgwpVcr6JIslKHOuS0FhRa_6T1KCManyRJqA-fifzw,1909
|
93
|
-
fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py,sha256=
|
93
|
+
fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py,sha256=uRRyVHQtK9McHCB6OsjYfDnQsu2E8At9K_UYb_pe2pg,4682
|
94
94
|
fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py,sha256=g8wqUjSicN17UZVXlfaMomYZ-xOIbBu1oE7HdJTzfvw,5218
|
95
|
-
fractal_server/app/runner/executors/slurm/sudo/executor.py,sha256=
|
95
|
+
fractal_server/app/runner/executors/slurm/sudo/executor.py,sha256=FVgx2mxqCLOhSoH3UTAeNc0BT0eJaxHMglGzGYePGPM,47439
|
96
96
|
fractal_server/app/runner/executors/slurm/utils_executors.py,sha256=naPyJI0I3lD-sYHbSXbMFGUBK4h_SggA5V91Z1Ch1Xg,1416
|
97
97
|
fractal_server/app/runner/extract_archive.py,sha256=tLpjDrX47OjTNhhoWvm6iNukg8KoieWyTb7ZfvE9eWU,2483
|
98
98
|
fractal_server/app/runner/filenames.py,sha256=9lwu3yB4C67yiijYw8XIKaLFn3mJUt6_TCyVFM_aZUQ,206
|
@@ -161,9 +161,10 @@ fractal_server/app/schemas/v2/task_collection.py,sha256=9c_yyFcVBXdAZpQQniy1bROh
|
|
161
161
|
fractal_server/app/schemas/v2/task_group.py,sha256=EPQ1WHjIA8WDrpsTfvfRESjwUVzu6jKiaKZx45b36N4,3215
|
162
162
|
fractal_server/app/schemas/v2/workflow.py,sha256=-KWvXnbHBFA3pj5n7mfSyLKJQSqkJmoziIEe7mpLl3M,1875
|
163
163
|
fractal_server/app/schemas/v2/workflowtask.py,sha256=FthKErVgx3a-k7WVk3nqJe1G-fl_iHND4rVrDXJ0F84,5942
|
164
|
-
fractal_server/app/security/__init__.py,sha256=
|
164
|
+
fractal_server/app/security/__init__.py,sha256=UmFnFFGM9WB_7b0itBi0b9uOIUWx_tcA2rCRaTNXErU,13778
|
165
|
+
fractal_server/app/security/signup_email.py,sha256=hzzHxoEizl6IPVeB0j9Ek_tKIalGRxH6npzWUyGkCc4,1164
|
165
166
|
fractal_server/app/user_settings.py,sha256=OP1yiYKtPadxwM51_Q0hdPk3z90TCN4z1BLpQsXyWiU,1316
|
166
|
-
fractal_server/config.py,sha256=
|
167
|
+
fractal_server/config.py,sha256=9rAzw7OO6ZeHEz-I8NJHuGoHf4xCHxfFLyRNZQD9ytY,27019
|
167
168
|
fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
|
168
169
|
fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
|
169
170
|
fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
|
@@ -208,7 +209,7 @@ fractal_server/ssh/_fabric.py,sha256=lNy4IX1I4We6VoWa4Bz4fUPuApLMSoejpyE6I3jDZeM
|
|
208
209
|
fractal_server/string_tools.py,sha256=XtMNsr5R7GmgzmFi68zkKMedHs8vjGoVMMCXqWhIk9k,2568
|
209
210
|
fractal_server/syringe.py,sha256=3qSMW3YaMKKnLdgnooAINOPxnCOxP7y2jeAQYB21Gdo,2786
|
210
211
|
fractal_server/tasks/__init__.py,sha256=kadmVUoIghl8s190_Tt-8f-WBqMi8u8oU4Pvw39NHE8,23
|
211
|
-
fractal_server/tasks/utils.py,sha256=
|
212
|
+
fractal_server/tasks/utils.py,sha256=gA9nYAviWKAMJmaF5RtoT2InddU6dCT2qA6fZTYNGO4,1105
|
212
213
|
fractal_server/tasks/v1/_TaskCollectPip.py,sha256=ARq5AoHYXH0hziEsb-nFAqbsLA-VIddXOdXL38O6_zA,3746
|
213
214
|
fractal_server/tasks/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
214
215
|
fractal_server/tasks/v1/background_operations.py,sha256=0Zm8TT_RV0BxJCXbruYJCu1eXOkEcHpqnWn_BEe7huw,11829
|
@@ -219,29 +220,29 @@ fractal_server/tasks/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
219
220
|
fractal_server/tasks/v2/local/__init__.py,sha256=9RVItnS7OyLsJOuJjWMCicaky4ASUPQEYD4SzDs0hOE,141
|
220
221
|
fractal_server/tasks/v2/local/_utils.py,sha256=EvhmVwYjqaNyDCUMEsTWYOUXLgEwR1xr6bu32apCEI8,2491
|
221
222
|
fractal_server/tasks/v2/local/collect.py,sha256=JuMplfREqrPvVEGlT5kJhcmZXC_iYlwvNlkgFrCaCC0,12107
|
222
|
-
fractal_server/tasks/v2/local/deactivate.py,sha256=
|
223
|
-
fractal_server/tasks/v2/local/reactivate.py,sha256=
|
223
|
+
fractal_server/tasks/v2/local/deactivate.py,sha256=SOFtOaR5yYm3IkbOw48TrQgzEpONQ9647KvyD_zImr8,9899
|
224
|
+
fractal_server/tasks/v2/local/reactivate.py,sha256=MeUZHx8IKrfTEf-pXlfYms8I4o-26co3jdNgSNAvw60,6053
|
224
225
|
fractal_server/tasks/v2/ssh/__init__.py,sha256=aSQbVi6Ummt9QzcSLWNmSqYjfdxrn9ROmqgH6bDpI7k,135
|
225
226
|
fractal_server/tasks/v2/ssh/_utils.py,sha256=LjaEYVUJDChilu3YuhxuGWYRNnVJ_zqNE9SDHdRTIHY,2824
|
226
227
|
fractal_server/tasks/v2/ssh/collect.py,sha256=2XXEPpl4LS22A75v_k4Bd46k46tmnLNZfceHyPi3kXo,13457
|
227
|
-
fractal_server/tasks/v2/ssh/deactivate.py,sha256=
|
228
|
-
fractal_server/tasks/v2/ssh/reactivate.py,sha256=
|
228
|
+
fractal_server/tasks/v2/ssh/deactivate.py,sha256=D8rfnC46davmDKZCipPdWZHDD4TIZ-4nr9vxZSV2aC0,11261
|
229
|
+
fractal_server/tasks/v2/ssh/reactivate.py,sha256=cmdT2P1J0FwS1NYYRrhxHsSRyUZ5uu78hS3fDrSVbKo,7837
|
229
230
|
fractal_server/tasks/v2/templates/1_create_venv.sh,sha256=PK0jdHKtQpda1zULebBaVPORt4t6V17wa4N1ohcj5ac,548
|
230
|
-
fractal_server/tasks/v2/templates/2_pip_install.sh,sha256=
|
231
|
+
fractal_server/tasks/v2/templates/2_pip_install.sh,sha256=Gpk2io8u9YaflFUlQu2NgkDQw5AA4m4AOVG1sB4yrHQ,1822
|
231
232
|
fractal_server/tasks/v2/templates/3_pip_freeze.sh,sha256=JldREScEBI4cD_qjfX4UK7V4aI-FnX9ZvVNxgpSOBFc,168
|
232
233
|
fractal_server/tasks/v2/templates/4_pip_show.sh,sha256=84NGHlg6JIbrQktgGKyfGsggPFzy6RBJuOmIpPUhsrw,1747
|
233
234
|
fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh,sha256=q-6ZUvA6w6FDVEoSd9O63LaJ9tKZc7qAFH72SGPrd_k,284
|
234
|
-
fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh,sha256=
|
235
|
+
fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh,sha256=A2y8RngEjAcRhG-_owA6P7tAdrS_AszFuGXnaeMV8u0,1122
|
235
236
|
fractal_server/tasks/v2/utils_background.py,sha256=tikXhggqxdU7EnKdx2co3UwinlDazEjfOPQOXtO58zs,4240
|
236
237
|
fractal_server/tasks/v2/utils_database.py,sha256=g5m3sNPZKQ3AjflhPURDlAppQcIS5T1A8a1macdswBA,1268
|
237
238
|
fractal_server/tasks/v2/utils_package_names.py,sha256=RDg__xrvQs4ieeVzmVdMcEh95vGQYrv9Hfal-5EDBM8,2393
|
238
239
|
fractal_server/tasks/v2/utils_python_interpreter.py,sha256=5_wrlrTqXyo1YuLZvAW9hrSoh5MyLOzdPVUlUwM7uDQ,955
|
239
|
-
fractal_server/tasks/v2/utils_templates.py,sha256=
|
240
|
-
fractal_server/urls.py,sha256=
|
240
|
+
fractal_server/tasks/v2/utils_templates.py,sha256=07TZpJ0Mh_A4lXVXrrH2o1VLFFGwxeRumA6DdgMgCWk,2947
|
241
|
+
fractal_server/urls.py,sha256=QjIKAC1a46bCdiPMu3AlpgFbcv6a4l3ABcd5xz190Og,471
|
241
242
|
fractal_server/utils.py,sha256=utvmBx8K9I8hRWFquxna2pBaOqe0JifDL_NVPmihEJI,3525
|
242
243
|
fractal_server/zip_tools.py,sha256=GjDgo_sf6V_DDg6wWeBlZu5zypIxycn_l257p_YVKGc,4876
|
243
|
-
fractal_server-2.10.
|
244
|
-
fractal_server-2.10.
|
245
|
-
fractal_server-2.10.
|
246
|
-
fractal_server-2.10.
|
247
|
-
fractal_server-2.10.
|
244
|
+
fractal_server-2.10.3.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
245
|
+
fractal_server-2.10.3.dist-info/METADATA,sha256=VeMNzjxlKvRgkyxQMzNMhNUArzpYkydFa7S4Fgcpaaw,4591
|
246
|
+
fractal_server-2.10.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
247
|
+
fractal_server-2.10.3.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
248
|
+
fractal_server-2.10.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|