fractal-server 2.10.2__py3-none-any.whl → 2.10.4__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.2"
1
+ __VERSION__ = "2.10.4"
@@ -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
 
@@ -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
 
@@ -560,9 +586,69 @@ class Settings(BaseSettings):
560
586
  FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
561
587
  """
562
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
+
563
637
  ###########################################################################
564
638
  # BUSINESS LOGIC
565
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
+
566
652
  def check_db(self) -> None:
567
653
  """
568
654
  Checks that db environment variables are properly set.
@@ -668,12 +754,13 @@ class Settings(BaseSettings):
668
754
 
669
755
  self.check_db()
670
756
  self.check_runner()
757
+ self.check_fractal_mail_settings()
671
758
 
672
759
  def get_sanitized(self) -> dict:
673
760
  def _must_be_sanitized(string) -> bool:
674
761
  if not string.upper().startswith("FRACTAL") or any(
675
762
  s in string.upper()
676
- for s in ["PASSWORD", "SECRET", "PWD", "TOKEN"]
763
+ for s in ["PASSWORD", "SECRET", "PWD", "TOKEN", "KEY"]
677
764
  ):
678
765
  return True
679
766
  else:
@@ -7,7 +7,7 @@ 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
13
  FRACTAL_PIP_CACHE_DIR_ARG="__FRACTAL_PIP_CACHE_DIR_ARG__"
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,12 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: fractal-server
3
- Version: 2.10.2
4
- Summary: Server component of the Fractal analytics platform
3
+ Version: 2.10.4
4
+ Summary: Backend component of the Fractal analytics platform
5
5
  Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
6
  License: BSD-3-Clause
7
7
  Author: Tommaso Comparin
8
8
  Author-email: tommaso.comparin@exact-lab.it
9
- Requires-Python: >=3.10,<4.0
9
+ Requires-Python: >=3.10,<3.13
10
10
  Classifier: License :: OSI Approved :: BSD License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
@@ -14,23 +14,24 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  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
- Requires-Dist: clusterfutures (>=0.5,<0.6)
18
- Requires-Dist: fabric (>=3.2.2,<4.0.0)
17
+ Requires-Dist: clusterfutures (==0.5)
18
+ Requires-Dist: cryptography (>=44.0.0,<44.1.0)
19
+ Requires-Dist: fabric (>=3.2.2,<3.3.0)
19
20
  Requires-Dist: fastapi (>=0.115.0,<0.116.0)
20
21
  Requires-Dist: fastapi-users[oauth] (>=14,<15)
21
22
  Requires-Dist: gunicorn (>=21.2,<23.0)
22
- Requires-Dist: packaging (>=23.2,<24.0)
23
- Requires-Dist: psutil (>=5.9.8,<6.0.0)
23
+ Requires-Dist: packaging (>=23.2.0,<24.0.0)
24
+ Requires-Dist: psutil (==5.9.8)
24
25
  Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0)
25
26
  Requires-Dist: pydantic (>=1.10.8,<2)
26
- Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
27
+ Requires-Dist: python-dotenv (>=1.0.0,<1.1.0)
27
28
  Requires-Dist: sqlalchemy[asyncio] (>=2.0.23,<2.1)
28
- Requires-Dist: sqlmodel (>=0.0.21,<0.0.22)
29
- Requires-Dist: uvicorn (>=0.29.0,<0.30.0)
30
- Requires-Dist: uvicorn-worker (>=0.2.0,<0.3.0)
31
- Project-URL: Changelog, https://github.com/fractal-analytics-platform/fractal-server/blob/main/CHANGELOG.md
29
+ Requires-Dist: sqlmodel (==0.0.21)
30
+ Requires-Dist: uvicorn (==0.29.0)
31
+ Requires-Dist: uvicorn-worker (==0.2.0)
32
32
  Project-URL: Documentation, https://fractal-analytics-platform.github.io/fractal-server
33
33
  Project-URL: Repository, https://github.com/fractal-analytics-platform/fractal-server
34
+ Project-URL: changelog, https://github.com/fractal-analytics-platform/fractal-server/blob/main/CHANGELOG.md
34
35
  Description-Content-Type: text/markdown
35
36
 
36
37
  # Fractal Server
@@ -1,5 +1,5 @@
1
- fractal_server/__init__.py,sha256=tZW5fVNCJEAwS9Hds76myD5Wbu6QwRk6i6vry6Km0qQ,23
2
- fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
1
+ fractal_server/__init__.py,sha256=SpR6dFPN1o2qOtKLrw5k3LkAdAJmWRd66Z2nXQfCN_4,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
@@ -73,7 +73,6 @@ fractal_server/app/routes/aux/__init__.py,sha256=LR4bR7RunHAK6jc9IR2bReQd-BdXADd
73
73
  fractal_server/app/routes/aux/_job.py,sha256=q-RCiW17yXnZKAC_0La52RLvhqhxuvbgQJ2MlGXOj8A,702
74
74
  fractal_server/app/routes/aux/_runner.py,sha256=FdCVla5DxGAZ__aB7Z8dEJzD_RIeh5tftjrPyqkr8N8,895
75
75
  fractal_server/app/routes/aux/validate_user_settings.py,sha256=Y8eubau0julkwVYB5nA83nDtxh_7RU9Iq0zAhb_dXLA,2351
76
- fractal_server/app/runner/.gitignore,sha256=ytzN_oyHWXrGU7iFAtoHSTUbM6Rn6kG0Zkddg0xZk6s,16
77
76
  fractal_server/app/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
77
  fractal_server/app/runner/async_wrap.py,sha256=_O6f8jftKYXG_DozkmlrDBhoiK9QhE9MablOyECq2_M,829
79
78
  fractal_server/app/runner/components.py,sha256=ZF8ct_Ky5k8IAcrmpYOZ-bc6OBgdELEighYVqFDEbZg,119
@@ -161,9 +160,10 @@ fractal_server/app/schemas/v2/task_collection.py,sha256=9c_yyFcVBXdAZpQQniy1bROh
161
160
  fractal_server/app/schemas/v2/task_group.py,sha256=EPQ1WHjIA8WDrpsTfvfRESjwUVzu6jKiaKZx45b36N4,3215
162
161
  fractal_server/app/schemas/v2/workflow.py,sha256=-KWvXnbHBFA3pj5n7mfSyLKJQSqkJmoziIEe7mpLl3M,1875
163
162
  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
163
+ fractal_server/app/security/__init__.py,sha256=UmFnFFGM9WB_7b0itBi0b9uOIUWx_tcA2rCRaTNXErU,13778
164
+ fractal_server/app/security/signup_email.py,sha256=hzzHxoEizl6IPVeB0j9Ek_tKIalGRxH6npzWUyGkCc4,1164
165
165
  fractal_server/app/user_settings.py,sha256=OP1yiYKtPadxwM51_Q0hdPk3z90TCN4z1BLpQsXyWiU,1316
166
- fractal_server/config.py,sha256=y9dxlHg1_BY6grec3mlpjutEiakQlsPJr3ERU-FlRfE,24200
166
+ fractal_server/config.py,sha256=9rAzw7OO6ZeHEz-I8NJHuGoHf4xCHxfFLyRNZQD9ytY,27019
167
167
  fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
168
168
  fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
169
169
  fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
@@ -172,10 +172,8 @@ fractal_server/images/models.py,sha256=UlWazUOFQtpS3pZuROjcJXviG_Ai453jqUDHdzuvD
172
172
  fractal_server/images/tools.py,sha256=gxeniYy4Z-cp_ToK2LHPJUTVVUUrdpogYdcBUvBuLiY,2209
173
173
  fractal_server/logger.py,sha256=zwg_AjIHkNP0ruciXjm5lI5UFP3n6tMHullsM9lDjz4,5039
174
174
  fractal_server/main.py,sha256=gStLT9Du5QMpc9SyvRvtKU21EKwp-dG4HL3zGHzE06A,4908
175
- fractal_server/migrations/README,sha256=4rQvyDfqodGhpJw74VYijRmgFP49ji5chyEemWGHsuw,59
176
175
  fractal_server/migrations/env.py,sha256=9t_OeKVlhM8WRcukmTrLbWNup-imiBGP_9xNgwCbtpI,2730
177
176
  fractal_server/migrations/naming_convention.py,sha256=htbKrVdetx3pklowb_9Cdo5RqeF0fJ740DNecY5de_M,265
178
- fractal_server/migrations/script.py.mako,sha256=oMXw9LC3zRbinWWPPDgeZ4z9FJrV2zhRWiYdS5YgNbI,526
179
177
  fractal_server/migrations/versions/034a469ec2eb_task_groups.py,sha256=vrPhC8hfFu1c4HmLHNZyCuqEfecFD8-bWc49bXMNes0,6199
180
178
  fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py,sha256=-BSS9AFTPcu3gYC-sYbawSy4MWQQx8TfMb5BW5EBKmQ,1450
181
179
  fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py,sha256=Q1Gj1cJ0UrdLBJ5AXfFK9QpxTtmcv-4Z3NEGDnxOme4,961
@@ -227,7 +225,7 @@ fractal_server/tasks/v2/ssh/collect.py,sha256=2XXEPpl4LS22A75v_k4Bd46k46tmnLNZfc
227
225
  fractal_server/tasks/v2/ssh/deactivate.py,sha256=D8rfnC46davmDKZCipPdWZHDD4TIZ-4nr9vxZSV2aC0,11261
228
226
  fractal_server/tasks/v2/ssh/reactivate.py,sha256=cmdT2P1J0FwS1NYYRrhxHsSRyUZ5uu78hS3fDrSVbKo,7837
229
227
  fractal_server/tasks/v2/templates/1_create_venv.sh,sha256=PK0jdHKtQpda1zULebBaVPORt4t6V17wa4N1ohcj5ac,548
230
- fractal_server/tasks/v2/templates/2_pip_install.sh,sha256=0NLELYgEDOrbfcjMamWN91Cw90bdl9wvWjlgXeLHT98,1820
228
+ fractal_server/tasks/v2/templates/2_pip_install.sh,sha256=Gpk2io8u9YaflFUlQu2NgkDQw5AA4m4AOVG1sB4yrHQ,1822
231
229
  fractal_server/tasks/v2/templates/3_pip_freeze.sh,sha256=JldREScEBI4cD_qjfX4UK7V4aI-FnX9ZvVNxgpSOBFc,168
232
230
  fractal_server/tasks/v2/templates/4_pip_show.sh,sha256=84NGHlg6JIbrQktgGKyfGsggPFzy6RBJuOmIpPUhsrw,1747
233
231
  fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh,sha256=q-6ZUvA6w6FDVEoSd9O63LaJ9tKZc7qAFH72SGPrd_k,284
@@ -237,11 +235,11 @@ fractal_server/tasks/v2/utils_database.py,sha256=g5m3sNPZKQ3AjflhPURDlAppQcIS5T1
237
235
  fractal_server/tasks/v2/utils_package_names.py,sha256=RDg__xrvQs4ieeVzmVdMcEh95vGQYrv9Hfal-5EDBM8,2393
238
236
  fractal_server/tasks/v2/utils_python_interpreter.py,sha256=5_wrlrTqXyo1YuLZvAW9hrSoh5MyLOzdPVUlUwM7uDQ,955
239
237
  fractal_server/tasks/v2/utils_templates.py,sha256=07TZpJ0Mh_A4lXVXrrH2o1VLFFGwxeRumA6DdgMgCWk,2947
240
- fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
238
+ fractal_server/urls.py,sha256=QjIKAC1a46bCdiPMu3AlpgFbcv6a4l3ABcd5xz190Og,471
241
239
  fractal_server/utils.py,sha256=utvmBx8K9I8hRWFquxna2pBaOqe0JifDL_NVPmihEJI,3525
242
240
  fractal_server/zip_tools.py,sha256=GjDgo_sf6V_DDg6wWeBlZu5zypIxycn_l257p_YVKGc,4876
243
- fractal_server-2.10.2.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
244
- fractal_server-2.10.2.dist-info/METADATA,sha256=qVhVwFIf7b6tlYACZ6HCT621DPVmF0YxwepFRoTkPU0,4544
245
- fractal_server-2.10.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
246
- fractal_server-2.10.2.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
247
- fractal_server-2.10.2.dist-info/RECORD,,
241
+ fractal_server-2.10.4.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
242
+ fractal_server-2.10.4.dist-info/METADATA,sha256=oEdEOyPOLPqYkCxgyXF1S_RKX5k4PGQWC7ZpwBmikU0,4562
243
+ fractal_server-2.10.4.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
244
+ fractal_server-2.10.4.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
245
+ fractal_server-2.10.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,2 +0,0 @@
1
- test*
2
- mypy-scan
@@ -1 +0,0 @@
1
- Generic single-database configuration with an async dbapi.
@@ -1,25 +0,0 @@
1
- """${message}
2
-
3
- Revision ID: ${up_revision}
4
- Revises: ${down_revision | comma,n}
5
- Create Date: ${create_date}
6
-
7
- """
8
- from alembic import op
9
- import sqlalchemy as sa
10
- import sqlmodel
11
- ${imports if imports else ""}
12
-
13
- # revision identifiers, used by Alembic.
14
- revision = ${repr(up_revision)}
15
- down_revision = ${repr(down_revision)}
16
- branch_labels = ${repr(branch_labels)}
17
- depends_on = ${repr(depends_on)}
18
-
19
-
20
- def upgrade() -> None:
21
- ${upgrades if upgrades else "pass"}
22
-
23
-
24
- def downgrade() -> None:
25
- ${downgrades if downgrades else "pass"}