fractal-server 2.5.2__py3-none-any.whl → 2.6.0a1__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.
Files changed (34) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +24 -9
  3. fractal_server/app/models/__init__.py +1 -0
  4. fractal_server/app/models/security.py +8 -0
  5. fractal_server/app/models/user_settings.py +38 -0
  6. fractal_server/app/routes/api/v1/_aux_functions.py +6 -1
  7. fractal_server/app/routes/api/v1/project.py +11 -24
  8. fractal_server/app/routes/api/v1/task.py +12 -9
  9. fractal_server/app/routes/api/v2/_aux_functions.py +6 -1
  10. fractal_server/app/routes/api/v2/submit.py +19 -23
  11. fractal_server/app/routes/api/v2/task.py +12 -9
  12. fractal_server/app/routes/api/v2/task_collection.py +10 -4
  13. fractal_server/app/routes/api/v2/task_collection_custom.py +6 -1
  14. fractal_server/app/routes/auth/_aux_auth.py +5 -5
  15. fractal_server/app/routes/auth/current_user.py +41 -0
  16. fractal_server/app/routes/auth/users.py +42 -0
  17. fractal_server/app/routes/aux/validate_user_settings.py +74 -0
  18. fractal_server/app/runner/v2/__init__.py +5 -7
  19. fractal_server/app/schemas/__init__.py +2 -0
  20. fractal_server/app/schemas/user.py +1 -62
  21. fractal_server/app/schemas/user_settings.py +94 -0
  22. fractal_server/app/security/__init__.py +22 -9
  23. fractal_server/app/user_settings.py +42 -0
  24. fractal_server/config.py +0 -16
  25. fractal_server/data_migrations/2_6_0.py +49 -0
  26. fractal_server/data_migrations/tools.py +17 -0
  27. fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +74 -0
  28. fractal_server/tasks/v2/background_operations_ssh.py +14 -5
  29. {fractal_server-2.5.2.dist-info → fractal_server-2.6.0a1.dist-info}/METADATA +1 -1
  30. {fractal_server-2.5.2.dist-info → fractal_server-2.6.0a1.dist-info}/RECORD +33 -27
  31. fractal_server/data_migrations/2_4_0.py +0 -61
  32. {fractal_server-2.5.2.dist-info → fractal_server-2.6.0a1.dist-info}/LICENSE +0 -0
  33. {fractal_server-2.5.2.dist-info → fractal_server-2.6.0a1.dist-info}/WHEEL +0 -0
  34. {fractal_server-2.5.2.dist-info → fractal_server-2.6.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,74 @@
1
+ from fastapi import HTTPException
2
+ from fastapi import status
3
+ from pydantic import BaseModel
4
+ from pydantic import ValidationError
5
+
6
+ from fractal_server.app.db import AsyncSession
7
+ from fractal_server.app.models import UserOAuth
8
+ from fractal_server.app.models import UserSettings
9
+ from fractal_server.app.user_settings import SlurmSshUserSettings
10
+ from fractal_server.app.user_settings import SlurmSudoUserSettings
11
+ from fractal_server.logger import set_logger
12
+
13
+ logger = set_logger(__name__)
14
+
15
+
16
+ def verify_user_has_settings(user: UserOAuth) -> None:
17
+ """
18
+ Check that the `user.user_settings_id` foreign-key is set.
19
+
20
+ NOTE: This check will become useless when we make the foreign-key column
21
+ required, but for the moment (as of v2.6.0) we have to keep it in place.
22
+
23
+ Arguments:
24
+ user: The user to be checked.
25
+ """
26
+ if user.user_settings_id is None:
27
+ raise HTTPException(
28
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
29
+ detail=f"Error: user '{user.email}' has no settings.",
30
+ )
31
+
32
+
33
+ async def validate_user_settings(
34
+ *, user: UserOAuth, backend: str, db: AsyncSession
35
+ ) -> UserSettings:
36
+ """
37
+ Get a UserSettings object and validate it based on a given Fractal backend.
38
+
39
+ Arguments:
40
+ user: The user whose settings we should validate.
41
+ backend: The value of `FRACTAL_RUNNER_BACKEND`
42
+ db: An async DB session
43
+
44
+ Returns:
45
+ `UserSetting` object associated to `user`, if valid.
46
+ """
47
+
48
+ verify_user_has_settings(user)
49
+
50
+ user_settings = await db.get(UserSettings, user.user_settings_id)
51
+
52
+ if backend == "slurm_ssh":
53
+ UserSettingsValidationModel = SlurmSshUserSettings
54
+ elif backend == "slurm":
55
+ UserSettingsValidationModel = SlurmSudoUserSettings
56
+ else:
57
+ # For other backends, we don't validate anything
58
+ UserSettingsValidationModel = BaseModel
59
+
60
+ try:
61
+ UserSettingsValidationModel(**user_settings.model_dump())
62
+ except ValidationError as e:
63
+ error_msg = (
64
+ "User settings are not valid for "
65
+ f"FRACTAL_RUNNER_BACKEND='{backend}'. "
66
+ f"Original error: {str(e)}"
67
+ )
68
+ logger.warning(error_msg)
69
+ raise HTTPException(
70
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
71
+ detail=error_msg,
72
+ )
73
+
74
+ return user_settings
@@ -42,6 +42,7 @@ from .handle_failed_job import assemble_filters_failed_job
42
42
  from .handle_failed_job import assemble_history_failed_job
43
43
  from .handle_failed_job import assemble_images_failed_job
44
44
  from fractal_server import __VERSION__
45
+ from fractal_server.app.models import UserSettings
45
46
 
46
47
  _backends = {}
47
48
  _backends["local"] = local_process_workflow
@@ -76,6 +77,7 @@ async def submit_workflow(
76
77
  workflow_id: int,
77
78
  dataset_id: int,
78
79
  job_id: int,
80
+ user_settings: UserSettings,
79
81
  worker_init: Optional[str] = None,
80
82
  slurm_user: Optional[str] = None,
81
83
  user_cache_dir: Optional[str] = None,
@@ -196,8 +198,7 @@ async def submit_workflow(
196
198
  elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
197
199
  # Folder creation is deferred to _process_workflow
198
200
  WORKFLOW_DIR_REMOTE = (
199
- Path(settings.FRACTAL_SLURM_SSH_WORKING_BASE_DIR)
200
- / WORKFLOW_DIR_LOCAL.name
201
+ Path(user_settings.ssh_jobs_dir) / WORKFLOW_DIR_LOCAL.name
201
202
  )
202
203
  else:
203
204
  logger.error(
@@ -270,11 +271,8 @@ async def submit_workflow(
270
271
  logger.debug(f"slurm_account: {job.slurm_account}")
271
272
  logger.debug(f"worker_init: {worker_init}")
272
273
  elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
273
- logger.debug(f"ssh_host: {settings.FRACTAL_SLURM_SSH_HOST}")
274
- logger.debug(f"ssh_user: {settings.FRACTAL_SLURM_SSH_USER}")
275
- logger.debug(
276
- f"base dir: {settings.FRACTAL_SLURM_SSH_WORKING_BASE_DIR}"
277
- )
274
+ logger.debug(f"ssh_user: {user_settings.ssh_username}")
275
+ logger.debug(f"base dir: {user_settings.ssh_tasks_dir}")
278
276
  logger.debug(f"worker_init: {worker_init}")
279
277
  logger.debug(f"job.id: {job.id}")
280
278
  logger.debug(f"job.working_dir: {job.working_dir}")
@@ -1 +1,3 @@
1
1
  from .user import * # noqa: F401, F403
2
+ from .user_group import * # noqa: F401, F403
3
+ from .user_settings import * # noqa: F401, F403
@@ -3,14 +3,10 @@ from typing import Optional
3
3
  from fastapi_users import schemas
4
4
  from pydantic import BaseModel
5
5
  from pydantic import Extra
6
- from pydantic import Field
7
6
  from pydantic import validator
8
- from pydantic.types import StrictStr
9
7
 
10
- from ._validators import val_absolute_path
11
8
  from ._validators import val_unique_list
12
9
  from ._validators import valstr
13
- from fractal_server.string_tools import validate_cmd
14
10
 
15
11
  __all__ = (
16
12
  "UserRead",
@@ -41,16 +37,10 @@ class UserRead(schemas.BaseUser[int]):
41
37
  Schema for `User` read from database.
42
38
 
43
39
  Attributes:
44
- slurm_user:
45
- cache_dir:
46
40
  username:
47
- slurm_accounts:
48
41
  """
49
42
 
50
- slurm_user: Optional[str]
51
- cache_dir: Optional[str]
52
43
  username: Optional[str]
53
- slurm_accounts: list[str]
54
44
  group_names: Optional[list[str]] = None
55
45
  group_ids: Optional[list[int]] = None
56
46
  oauth_accounts: list[OAuthAccountRead]
@@ -61,32 +51,14 @@ class UserUpdate(schemas.BaseUserUpdate):
61
51
  Schema for `User` update.
62
52
 
63
53
  Attributes:
64
- slurm_user:
65
- cache_dir:
66
54
  username:
67
- slurm_accounts:
68
55
  """
69
56
 
70
- slurm_user: Optional[str]
71
- cache_dir: Optional[str]
72
57
  username: Optional[str]
73
- slurm_accounts: Optional[list[StrictStr]]
74
58
 
75
59
  # Validators
76
- _slurm_user = validator("slurm_user", allow_reuse=True)(
77
- valstr("slurm_user")
78
- )
79
60
  _username = validator("username", allow_reuse=True)(valstr("username"))
80
61
 
81
- _slurm_accounts = validator("slurm_accounts", allow_reuse=True)(
82
- val_unique_list("slurm_accounts")
83
- )
84
-
85
- @validator("cache_dir")
86
- def cache_dir_validator(cls, value):
87
- validate_cmd(value)
88
- return val_absolute_path("cache_dir")(value)
89
-
90
62
  @validator(
91
63
  "is_active",
92
64
  "is_verified",
@@ -106,21 +78,9 @@ class UserUpdateStrict(BaseModel, extra=Extra.forbid):
106
78
  Schema for `User` self-editing.
107
79
 
108
80
  Attributes:
109
- cache_dir:
110
- slurm_accounts:
111
81
  """
112
82
 
113
- cache_dir: Optional[str]
114
- slurm_accounts: Optional[list[StrictStr]]
115
-
116
- _slurm_accounts = validator("slurm_accounts", allow_reuse=True)(
117
- val_unique_list("slurm_accounts")
118
- )
119
-
120
- @validator("cache_dir")
121
- def cache_dir_validator(cls, value):
122
- validate_cmd(value)
123
- return val_absolute_path("cache_dir")(value)
83
+ pass
124
84
 
125
85
 
126
86
  class UserUpdateWithNewGroupIds(UserUpdate):
@@ -136,32 +96,11 @@ class UserCreate(schemas.BaseUserCreate):
136
96
  Schema for `User` creation.
137
97
 
138
98
  Attributes:
139
- slurm_user:
140
- cache_dir:
141
99
  username:
142
- slurm_accounts:
143
100
  """
144
101
 
145
- slurm_user: Optional[str]
146
- cache_dir: Optional[str]
147
102
  username: Optional[str]
148
- slurm_accounts: list[StrictStr] = Field(default_factory=list)
149
103
 
150
104
  # Validators
151
105
 
152
- @validator("slurm_accounts")
153
- def slurm_accounts_validator(cls, value):
154
- for i, element in enumerate(value):
155
- value[i] = valstr(attribute=f"slurm_accounts[{i}]")(element)
156
- val_unique_list("slurm_accounts")(value)
157
- return value
158
-
159
- _slurm_user = validator("slurm_user", allow_reuse=True)(
160
- valstr("slurm_user")
161
- )
162
106
  _username = validator("username", allow_reuse=True)(valstr("username"))
163
-
164
- @validator("cache_dir")
165
- def cache_dir_validator(cls, value):
166
- validate_cmd(value)
167
- return val_absolute_path("cache_dir")(value)
@@ -0,0 +1,94 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic import Extra
5
+ from pydantic import validator
6
+ from pydantic.types import StrictStr
7
+
8
+ from ._validators import val_absolute_path
9
+ from ._validators import val_unique_list
10
+ from ._validators import valstr
11
+ from fractal_server.string_tools import validate_cmd
12
+
13
+ __all__ = (
14
+ "UserSettingsRead",
15
+ "UserSettingsReadStrict",
16
+ "UserSettingsUpdate",
17
+ "UserSettingsUpdateStrict",
18
+ )
19
+
20
+
21
+ class UserSettingsRead(BaseModel):
22
+ id: int
23
+ ssh_host: Optional[str] = None
24
+ ssh_username: Optional[str] = None
25
+ ssh_private_key_path: Optional[str] = None
26
+ ssh_tasks_dir: Optional[str] = None
27
+ ssh_jobs_dir: Optional[str] = None
28
+ slurm_user: Optional[str] = None
29
+ slurm_accounts: list[str]
30
+ cache_dir: Optional[str] = None
31
+
32
+
33
+ class UserSettingsReadStrict(BaseModel):
34
+ slurm_user: Optional[str] = None
35
+ slurm_accounts: list[str]
36
+ cache_dir: Optional[str] = None
37
+ ssh_username: Optional[str] = None
38
+
39
+
40
+ class UserSettingsUpdate(BaseModel, extra=Extra.forbid):
41
+ ssh_host: Optional[str] = None
42
+ ssh_username: Optional[str] = None
43
+ ssh_private_key_path: Optional[str] = None
44
+ ssh_tasks_dir: Optional[str] = None
45
+ ssh_jobs_dir: Optional[str] = None
46
+ slurm_user: Optional[str] = None
47
+ slurm_accounts: Optional[list[StrictStr]] = None
48
+ cache_dir: Optional[str] = None
49
+
50
+ _ssh_host = validator("ssh_host", allow_reuse=True)(valstr("ssh_host"))
51
+ _ssh_username = validator("ssh_username", allow_reuse=True)(
52
+ valstr("ssh_username")
53
+ )
54
+ _ssh_private_key_path = validator(
55
+ "ssh_private_key_path", allow_reuse=True
56
+ )(val_absolute_path("ssh_private_key_path"))
57
+
58
+ _ssh_tasks_dir = validator("ssh_tasks_dir", allow_reuse=True)(
59
+ val_absolute_path("ssh_tasks_dir")
60
+ )
61
+ _ssh_jobs_dir = validator("ssh_jobs_dir", allow_reuse=True)(
62
+ val_absolute_path("ssh_jobs_dir")
63
+ )
64
+
65
+ _slurm_user = validator("slurm_user", allow_reuse=True)(
66
+ valstr("slurm_user")
67
+ )
68
+
69
+ @validator("slurm_accounts")
70
+ def slurm_accounts_validator(cls, value):
71
+ if value is None:
72
+ return value
73
+ for i, item in enumerate(value):
74
+ value[i] = valstr(f"slurm_accounts[{i}]")(item)
75
+ return val_unique_list("slurm_accounts")(value)
76
+
77
+ @validator("cache_dir")
78
+ def cache_dir_validator(cls, value):
79
+ validate_cmd(value)
80
+ return val_absolute_path("cache_dir")(value)
81
+
82
+
83
+ class UserSettingsUpdateStrict(BaseModel, extra=Extra.forbid):
84
+ slurm_accounts: Optional[list[StrictStr]] = None
85
+ cache_dir: Optional[str] = None
86
+
87
+ _slurm_accounts = validator("slurm_accounts", allow_reuse=True)(
88
+ val_unique_list("slurm_accounts")
89
+ )
90
+
91
+ @validator("cache_dir")
92
+ def cache_dir_validator(cls, value):
93
+ validate_cmd(value)
94
+ return val_absolute_path("cache_dir")(value)
@@ -54,6 +54,7 @@ from fractal_server.app.models import LinkUserGroup
54
54
  from fractal_server.app.models import OAuthAccount
55
55
  from fractal_server.app.models import UserGroup
56
56
  from fractal_server.app.models import UserOAuth
57
+ from fractal_server.app.models import UserSettings
57
58
  from fractal_server.app.schemas.user import UserCreate
58
59
  from fractal_server.logger import set_logger
59
60
 
@@ -193,6 +194,8 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
193
194
  async def on_after_register(
194
195
  self, user: UserOAuth, request: Optional[Request] = None
195
196
  ):
197
+ logger = set_logger("fractal_server.on_after_register")
198
+
196
199
  logger.info(
197
200
  f"New-user registration completed ({user.id=}, {user.email=})."
198
201
  )
@@ -204,24 +207,30 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
204
207
  res = await db.execute(stm)
205
208
  default_group = res.scalar_one_or_none()
206
209
  if default_group is None:
207
- logger.error(
210
+ logger.warning(
208
211
  f"No group found with name {FRACTAL_DEFAULT_GROUP_NAME}"
209
212
  )
210
213
  else:
211
- logger.warning(
212
- f"START adding {user.email} user to group "
213
- f"{default_group.id=}."
214
- )
215
214
  link = LinkUserGroup(
216
215
  user_id=user.id, group_id=default_group.id
217
216
  )
218
217
  db.add(link)
219
218
  await db.commit()
220
- logger.warning(
221
- f"END adding {user.email} user to group "
222
- f"{default_group.id=}."
219
+ logger.info(
220
+ f"Added {user.email} user to group {default_group.id=}."
223
221
  )
224
222
 
223
+ this_user = await db.get(UserOAuth, user.id)
224
+
225
+ this_user.settings = UserSettings()
226
+ await db.merge(this_user)
227
+ await db.commit()
228
+ await db.refresh(this_user)
229
+ logger.info(
230
+ f"Associated empty settings (id={this_user.user_settings_id}) "
231
+ f"to '{this_user.email}'."
232
+ )
233
+
225
234
 
226
235
  async def get_user_manager(
227
236
  user_db: SQLModelUserDatabaseAsync = Depends(get_user_db),
@@ -294,9 +303,13 @@ async def _create_first_user(
294
303
  kwargs["username"] = username
295
304
  user = await user_manager.create(UserCreate(**kwargs))
296
305
  function_logger.info(f"User '{user.email}' created")
297
-
298
306
  except UserAlreadyExists:
299
307
  function_logger.warning(f"User '{email}' already exists")
308
+ except Exception as e:
309
+ function_logger.error(
310
+ f"ERROR in _create_first_user, original error {str(e)}"
311
+ )
312
+ raise e
300
313
  finally:
301
314
  function_logger.info(f"END _create_first_user, with email '{email}'")
302
315
 
@@ -0,0 +1,42 @@
1
+ # TODO: move this file to the appropriate path
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class SlurmSshUserSettings(BaseModel):
6
+ """
7
+ Subset of user settings which must be present for task collection and job
8
+ execution when using the Slurm-SSH runner.
9
+
10
+ Attributes:
11
+ ssh_host: SSH-reachable host where a SLURM client is available.
12
+ ssh_username: User on `ssh_host`.
13
+ ssh_private_key_path: Path of private SSH key for `ssh_username`.
14
+ ssh_tasks_dir: Task-venvs base folder on `ssh_host`.
15
+ ssh_jobs_dir: Jobs base folder on `ssh_host`.
16
+ slurm_accounts:
17
+ List of SLURM accounts, to be used upon Fractal job submission.
18
+ """
19
+
20
+ ssh_host: str
21
+ ssh_username: str
22
+ ssh_private_key_path: str
23
+ ssh_tasks_dir: str
24
+ ssh_jobs_dir: str
25
+ slurm_accounts: list[str]
26
+
27
+
28
+ class SlurmSudoUserSettings(BaseModel):
29
+ """
30
+ Subset of user settings which must be present for task collection and job
31
+ execution when using the Slurm-sudo runner.
32
+
33
+ Attributes:
34
+ slurm_user: User to be impersonated via `sudo -u`.
35
+ cache_dir: Folder where `slurm_user` can write.
36
+ slurm_accounts:
37
+ List of SLURM accounts, to be used upon Fractal job submission.
38
+ """
39
+
40
+ slurm_user: str
41
+ cache_dir: str
42
+ slurm_accounts: list[str]
fractal_server/config.py CHANGED
@@ -631,22 +631,6 @@ class Settings(BaseSettings):
631
631
  raise FractalConfigurationError(
632
632
  f"Must set FRACTAL_SLURM_WORKER_PYTHON when {info}"
633
633
  )
634
- if self.FRACTAL_SLURM_SSH_USER is None:
635
- raise FractalConfigurationError(
636
- f"Must set FRACTAL_SLURM_SSH_USER when {info}"
637
- )
638
- if self.FRACTAL_SLURM_SSH_HOST is None:
639
- raise FractalConfigurationError(
640
- f"Must set FRACTAL_SLURM_SSH_HOST when {info}"
641
- )
642
- if self.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH is None:
643
- raise FractalConfigurationError(
644
- f"Must set FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH when {info}"
645
- )
646
- if self.FRACTAL_SLURM_SSH_WORKING_BASE_DIR is None:
647
- raise FractalConfigurationError(
648
- f"Must set FRACTAL_SLURM_SSH_WORKING_BASE_DIR when {info}"
649
- )
650
634
 
651
635
  from fractal_server.app.runner.executors.slurm._slurm_config import ( # noqa: E501
652
636
  load_slurm_config_file,
@@ -0,0 +1,49 @@
1
+ import logging
2
+
3
+ from sqlalchemy import select
4
+
5
+ from fractal_server.app.db import get_sync_db
6
+ from fractal_server.app.models import UserOAuth
7
+ from fractal_server.app.models import UserSettings
8
+ from fractal_server.config import get_settings
9
+ from fractal_server.data_migrations.tools import _check_current_version
10
+ from fractal_server.syringe import Inject
11
+
12
+
13
+ def fix_db():
14
+ logger = logging.getLogger("fix_db")
15
+ logger.warning("START execution of fix_db function")
16
+ _check_current_version("2.6.0")
17
+
18
+ global_settings = Inject(get_settings)
19
+
20
+ with next(get_sync_db()) as db:
21
+ users = db.execute(select(UserOAuth)).scalars().unique().all()
22
+ for user in sorted(users, key=lambda x: x.id):
23
+ logger.warning(f"START handling user {user.id}: '{user.email}'")
24
+ user_settings = UserSettings(
25
+ # SSH
26
+ ssh_host=global_settings.FRACTAL_SLURM_SSH_HOST,
27
+ ssh_username=global_settings.FRACTAL_SLURM_SSH_USER,
28
+ ssh_private_key_path=(
29
+ global_settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH
30
+ ),
31
+ ssh_tasks_dir=(
32
+ global_settings.FRACTAL_SLURM_SSH_WORKING_BASE_DIR
33
+ ),
34
+ ssh_jobs_dir=(
35
+ global_settings.FRACTAL_SLURM_SSH_WORKING_BASE_DIR
36
+ ),
37
+ # SUDO
38
+ slurm_user=user.slurm_user,
39
+ slurm_accounts=user.slurm_accounts,
40
+ cache_dir=user.cache_dir,
41
+ )
42
+ user.settings = user_settings
43
+ db.add(user)
44
+ db.commit()
45
+ db.refresh(user)
46
+ logger.warning(f"New user {user.id} settings:\n{user.settings}")
47
+ logger.warning(f"END handling user {user.id}: '{user.email}'")
48
+
49
+ logger.warning("END of execution of fix_db function")
@@ -0,0 +1,17 @@
1
+ from packaging.version import parse
2
+
3
+ import fractal_server
4
+
5
+
6
+ def _check_current_version(expected_version: str):
7
+ # Check that this module matches with the current version
8
+ module_version = parse(expected_version)
9
+ current_version = parse(fractal_server.__VERSION__)
10
+ if (
11
+ current_version.major != module_version.major
12
+ or current_version.minor != module_version.minor
13
+ or current_version.micro != module_version.micro
14
+ ):
15
+ raise RuntimeError(
16
+ f"{fractal_server.__VERSION__=} not matching with {__file__=}"
17
+ )
@@ -0,0 +1,74 @@
1
+ """Add user_settings table
2
+
3
+ Revision ID: 9c5ae74c9b98
4
+ Revises: d9a140db5d42
5
+ Create Date: 2024-09-24 12:01:13.393326
6
+
7
+ """
8
+ import sqlalchemy as sa
9
+ import sqlmodel
10
+ from alembic import op
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "9c5ae74c9b98"
15
+ down_revision = "d9a140db5d42"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+ # Manually define constraint name, see issue #1777
20
+ CONSTRAINT_NAME = "fk_user_oauth_user_settings_id_user_settings"
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ op.create_table(
26
+ "user_settings",
27
+ sa.Column("id", sa.Integer(), nullable=False),
28
+ sa.Column(
29
+ "slurm_accounts", sa.JSON(), server_default="[]", nullable=False
30
+ ),
31
+ sa.Column(
32
+ "ssh_host", sqlmodel.sql.sqltypes.AutoString(), nullable=True
33
+ ),
34
+ sa.Column(
35
+ "ssh_username", sqlmodel.sql.sqltypes.AutoString(), nullable=True
36
+ ),
37
+ sa.Column(
38
+ "ssh_private_key_path",
39
+ sqlmodel.sql.sqltypes.AutoString(),
40
+ nullable=True,
41
+ ),
42
+ sa.Column(
43
+ "ssh_tasks_dir", sqlmodel.sql.sqltypes.AutoString(), nullable=True
44
+ ),
45
+ sa.Column(
46
+ "ssh_jobs_dir", sqlmodel.sql.sqltypes.AutoString(), nullable=True
47
+ ),
48
+ sa.Column(
49
+ "slurm_user", sqlmodel.sql.sqltypes.AutoString(), nullable=True
50
+ ),
51
+ sa.Column(
52
+ "cache_dir", sqlmodel.sql.sqltypes.AutoString(), nullable=True
53
+ ),
54
+ sa.PrimaryKeyConstraint("id"),
55
+ )
56
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
57
+ batch_op.add_column(
58
+ sa.Column("user_settings_id", sa.Integer(), nullable=True)
59
+ )
60
+ batch_op.create_foreign_key(
61
+ CONSTRAINT_NAME, "user_settings", ["user_settings_id"], ["id"]
62
+ )
63
+
64
+ # ### end Alembic commands ###
65
+
66
+
67
+ def downgrade() -> None:
68
+ # ### commands auto generated by Alembic - please adjust! ###
69
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
70
+ batch_op.drop_constraint(CONSTRAINT_NAME, type_="foreignkey")
71
+ batch_op.drop_column("user_settings_id")
72
+
73
+ op.drop_table("user_settings")
74
+ # ### end Alembic commands ###