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.
@@ -1 +1 @@
1
- __VERSION__ = "2.10.1"
1
+ __VERSION__ = "2.10.3"
@@ -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
- await db.delete(old_workflow_task)
125
- workflow.task_list.insert(new_workflow_task.order, new_workflow_task)
126
- flag_modified(workflow, "task_list")
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:
@@ -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 --no-cache-dir "pip<=${FRACTAL_MAX_PIP_VERSION}" --upgrade
21
- "$VENVPYTHON" -m pip install --no-cache-dir setuptools
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 --no-cache-dir "$PINNED_PACKAGE_LIST"
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 --no-cache-dir "pip<=${FRACTAL_MAX_PIP_VERSION}" --upgrade
20
- "$VENVPYTHON" -m pip install --no-cache-dir setuptools
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
 
@@ -81,6 +81,7 @@ def get_collection_replacements(
81
81
  "__FRACTAL_MAX_PIP_VERSION__",
82
82
  settings.FRACTAL_MAX_PIP_VERSION,
83
83
  ),
84
+ ("__FRACTAL_PIP_CACHE_DIR_ARG__", settings.PIP_CACHE_DIR_ARG),
84
85
  (
85
86
  "__PINNED_PACKAGE_LIST__",
86
87
  task_group.pinned_package_versions_string,
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.1
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=mGoPL5QC9hw_tGv5lsEM4M_0NMrtig40cMpwnjY5kto,23
2
- fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
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=snX_E3OSBsgjbVwQMgKvV7pLmfNGD0OyqgAsxSGtB5E,12359
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=opA6hYfscPmoPhD-Xx1Z9DDeUf9Nnoo6jF2LUdNyGhM,10771
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=z5LlhaiqAb8pHsF1WwdzXN39C5anQmwjo1rSQgtRAYE,4422
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=CAIPFMmsjQLxmjN8Kdpq0OlZIX9PZIiRo0XO1quKWEM,46495
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=MlWVrLFPj9M2Gug-k8yATM-Cw066RugVU4KK6kMRbnQ,13019
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=wRWJqyEeH4j2puH-fGlCYKLoKFh9pzRsQkS6q1VtO9M,23173
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=2ShlUSOXx53IiZT72dTCF31xXoLX9P8bY18vMj2m-mc,1059
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=XR1nvJY3mKCRqwPwV79rVaQmtb3J83KdmJKjTOHD-cU,9250
223
- fractal_server/tasks/v2/local/reactivate.py,sha256=R3rArAzUpMGf6xa3dGVwwXHW9WVDi5ia28AFisZsqNc,6112
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=Ffk_UuQSBUBNBCiviuKNhEUGyZPQa4_erJKFdwgMcE8,10616
228
- fractal_server/tasks/v2/ssh/reactivate.py,sha256=jdO8iyzavzSVPcOpIZrYSEkGPYTvz5XJ5h_5-nz9yzA,7896
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=RDDfbFnGOK3aRuHyXqDOUNCGullzAr0zS7BFqG1CJeE,1720
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=n9C8w76YraLbeTe7NhuLzvAQiJCm_akL3Mc3EMfxrHo,1007
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=MS8zu24qimJSktZaHruPxkwIl81ZoUnIVGtnMHS4Y3o,2876
240
- fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
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.1.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
244
- fractal_server-2.10.1.dist-info/METADATA,sha256=r2HP2NsG-Jz9wcqob_X5XehPxQCzC8amUcW300aoPG4,4544
245
- fractal_server-2.10.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
246
- fractal_server-2.10.1.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
247
- fractal_server-2.10.1.dist-info/RECORD,,
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,,