fractal-server 2.17.0a10__py3-none-any.whl → 2.17.0a11__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.17.0a10"
1
+ __VERSION__ = "2.17.0a11"
@@ -180,7 +180,9 @@ async def get_resource_profiles(
180
180
  await _get_resource_or_404(resource_id=resource_id, db=db)
181
181
 
182
182
  res = await db.execute(
183
- select(Profile).where(Profile.resource_id == resource_id)
183
+ select(Profile)
184
+ .where(Profile.resource_id == resource_id)
185
+ .order_by(Profile.id)
184
186
  )
185
187
  profiles = res.scalars().all()
186
188
 
@@ -0,0 +1,321 @@
1
+ """
2
+
3
+ PRELIMINARY CHECKS (TO DO WITH 2.16)
4
+ * All users who are meant to actually use Fractal must be marked as active and verified.
5
+ * All users who are not active and verified will still be able to log in, but they won't have access to the rest of the API.
6
+ * All active users must have `project_dir` set, in their user settings.
7
+ * `FRACTAL_SLURM_WORKER_PYTHON` must be included explicitly in the old env file.
8
+
9
+ DATA-MIGRATION REQUIREMENTS
10
+ * Old `.fractal_server.env`, renamed into `.fractal_server.env.old`.
11
+ * New `.fractal_server.env` - see XXX for list of changes.
12
+ * Old JSON file with SLURM configuration.
13
+ * Old JSON file with pixi configuration - if applicable.
14
+
15
+
16
+ MANUAL FIXES POST DATA MIGRATION:
17
+ * Rename resource
18
+ * Rename profiles - if needed
19
+ """
20
+ import json
21
+ import logging
22
+ import sys
23
+ from typing import Any
24
+
25
+ from devtools import debug
26
+ from dotenv.main import DotEnv
27
+ from pydantic import BaseModel
28
+ from sqlalchemy.orm import Session
29
+ from sqlalchemy.sql.operators import is_
30
+ from sqlalchemy.sql.operators import is_not
31
+ from sqlmodel import select
32
+
33
+ from fractal_server.app.db import get_sync_db
34
+ from fractal_server.app.models import Profile
35
+ from fractal_server.app.models import ProjectV2
36
+ from fractal_server.app.models import Resource
37
+ from fractal_server.app.models import TaskGroupV2
38
+ from fractal_server.app.models import UserOAuth
39
+ from fractal_server.app.models import UserSettings
40
+ from fractal_server.app.schemas.v2.profile import cast_serialize_profile
41
+ from fractal_server.app.schemas.v2.resource import cast_serialize_resource
42
+ from fractal_server.config import get_settings
43
+ from fractal_server.runner.config import JobRunnerConfigSLURM
44
+ from fractal_server.tasks.config import TasksPixiSettings
45
+ from fractal_server.tasks.config import TasksPythonSettings
46
+ from fractal_server.types import AbsolutePathStr
47
+ from fractal_server.types import ListUniqueNonEmptyString
48
+ from fractal_server.urls import normalize_url
49
+
50
+ logger = logging.getLogger("fix_db")
51
+ logger.setLevel(logging.INFO)
52
+
53
+
54
+ class UserUpdateInfo(BaseModel):
55
+ user_id: int
56
+ project_dir: AbsolutePathStr
57
+ slurm_accounts: ListUniqueNonEmptyString
58
+
59
+
60
+ class ProfileUsersUpdateInfo(BaseModel):
61
+ data: dict[str, Any]
62
+ user_updates: list[UserUpdateInfo]
63
+
64
+
65
+ def _get_user_settings(user: UserOAuth, db: Session) -> UserSettings:
66
+ if user.user_settings_id is None:
67
+ sys.exit(f"User {user.email} is active but {user.user_settings_id=}.")
68
+ user_settings = db.get(UserSettings, user.user_settings_id)
69
+ return user_settings
70
+
71
+
72
+ def assert_user_setting_key(
73
+ user: UserOAuth,
74
+ user_settings: UserSettings,
75
+ keys: list[str],
76
+ ) -> None:
77
+ for key in keys:
78
+ if getattr(user_settings, key) is None:
79
+ sys.exit(
80
+ f"User {user.email} is active and verified but their "
81
+ f"user settings have {key}=None."
82
+ )
83
+
84
+
85
+ def prepare_profile_and_user_updates() -> dict[str, ProfileUsersUpdateInfo]:
86
+ settings = get_settings()
87
+ profiles_and_users: dict[str, ProfileUsersUpdateInfo] = {}
88
+ with next(get_sync_db()) as db:
89
+ # Get active&verified users
90
+ res = db.execute(
91
+ select(UserOAuth)
92
+ .where(is_(UserOAuth.is_active, True))
93
+ .where(is_(UserOAuth.is_verified, True))
94
+ .order_by(UserOAuth.id)
95
+ )
96
+ for user in res.unique().scalars().all():
97
+ # Get user settings
98
+ user_settings = _get_user_settings(user=user, db=db)
99
+ assert_user_setting_key(user, user_settings, ["project_dir"])
100
+
101
+ # Prepare profile data and user update
102
+ new_profile_data = dict()
103
+ if settings.FRACTAL_RUNNER_BACKEND == "slurm_sudo":
104
+ assert_user_setting_key(user, user_settings, ["slurm_user"])
105
+ username = user_settings.slurm_user
106
+ elif settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
107
+ assert_user_setting_key(
108
+ user,
109
+ user_settings,
110
+ [
111
+ "ssh_username",
112
+ "ssh_private_key_path",
113
+ "ssh_tasks_dir",
114
+ "ssh_jobs_dir",
115
+ ],
116
+ )
117
+ username = user_settings.ssh_username
118
+ new_profile_data.update(
119
+ ssh_key_path=user_settings.ssh_private_key_path,
120
+ tasks_remote_dir=normalize_url(
121
+ user_settings.ssh_tasks_dir
122
+ ),
123
+ jobs_remote_dir=normalize_url(user_settings.ssh_jobs_dir),
124
+ )
125
+
126
+ new_profile_data.update(
127
+ name=f"Profile {username}",
128
+ username=username,
129
+ resource_type=settings.FRACTAL_RUNNER_BACKEND,
130
+ )
131
+ debug(new_profile_data)
132
+ cast_serialize_profile(new_profile_data)
133
+
134
+ user_update_info = UserUpdateInfo(
135
+ user_id=user.id,
136
+ project_dir=normalize_url(user_settings.project_dir),
137
+ slurm_accounts=user_settings.slurm_accounts or [],
138
+ )
139
+
140
+ if username in profiles_and_users.keys():
141
+ if profiles_and_users[username].data != new_profile_data:
142
+ # FIXME
143
+ debug(new_profile_data)
144
+ debug(profiles_and_users[username].data)
145
+ raise ValueError()
146
+ profiles_and_users[username].user_updates.append(
147
+ user_update_info
148
+ )
149
+ else:
150
+ profiles_and_users[username] = ProfileUsersUpdateInfo(
151
+ data=new_profile_data,
152
+ user_updates=[user_update_info],
153
+ )
154
+
155
+ return profiles_and_users
156
+
157
+
158
+ def get_old_dotenv_variables() -> dict[str, str | None]:
159
+ """
160
+ See
161
+ https://github.com/fractal-analytics-platform/fractal-server/blob/2.16.x/fractal_server/config.py
162
+ """
163
+ OLD_DOTENV_FILE = ".fractal_server.env.old"
164
+ return dict(
165
+ **DotEnv(
166
+ dotenv_path=OLD_DOTENV_FILE,
167
+ override=False,
168
+ ).dict()
169
+ )
170
+
171
+
172
+ def get_TasksPythonSettings(
173
+ old_config: dict[str, str | None]
174
+ ) -> dict[str, Any]:
175
+ versions = {}
176
+ for version_underscore in ["3_9", "3_10", "3_11", "3_12"]:
177
+ key = f"FRACTAL_TASKS_PYTHON_{version_underscore}"
178
+ version_dot = version_underscore.replace("_", ".")
179
+ value = old_config.get(key, None)
180
+ if value is not None:
181
+ versions[version_dot] = value
182
+ obj = TasksPythonSettings(
183
+ default_version=old_config["FRACTAL_TASKS_PYTHON_DEFAULT_VERSION"],
184
+ versions=versions,
185
+ pip_cache_dir=old_config.get("FRACTAL_PIP_CACHE_DIR", None),
186
+ )
187
+ return obj.model_dump()
188
+
189
+
190
+ def get_TasksPixiSettings(old_config: dict[str, str | None]) -> dict[str, Any]:
191
+ pixi_file = old_config.get("FRACTAL_PIXI_CONFIG_FILE", None)
192
+ if pixi_file is None:
193
+ return {}
194
+ with open(pixi_file) as f:
195
+ old_pixi_config = json.load(f)
196
+ TasksPixiSettings(**old_pixi_config)
197
+ return old_pixi_config
198
+
199
+
200
+ def get_JobRunnerConfigSLURM(
201
+ old_config: dict[str, str | None]
202
+ ) -> dict[str, Any]:
203
+ slurm_file = old_config["FRACTAL_SLURM_CONFIG_FILE"]
204
+ with open(slurm_file) as f:
205
+ old_slurm_config = json.load(f)
206
+ JobRunnerConfigSLURM(**old_slurm_config)
207
+ return old_slurm_config
208
+
209
+
210
+ def get_ssh_host() -> str:
211
+ with next(get_sync_db()) as db:
212
+ res = db.execute(
213
+ select(UserSettings.ssh_host).where(
214
+ is_not(UserSettings.ssh_host, None)
215
+ )
216
+ )
217
+ hosts = res.scalars().all()
218
+ if len(set(hosts)) > 1:
219
+ host = max(set(hosts), key=hosts.count)
220
+ print(f"MOST FREQUENT HOST: {host}")
221
+ else:
222
+ host = hosts[0]
223
+ return host
224
+
225
+
226
+ def prepare_resource_data(old_config: dict[str, str | None]) -> dict[str, Any]:
227
+ settings = get_settings()
228
+
229
+ resource_data = dict(
230
+ type=settings.FRACTAL_RUNNER_BACKEND,
231
+ name="Resource Name",
232
+ tasks_python_config=get_TasksPythonSettings(old_config),
233
+ tasks_pixi_config=get_TasksPixiSettings(old_config),
234
+ jobs_runner_config=get_JobRunnerConfigSLURM(old_config),
235
+ tasks_local_dir=old_config["FRACTAL_TASKS_DIR"],
236
+ jobs_local_dir=old_config["FRACTAL_RUNNER_WORKING_BASE_DIR"],
237
+ jobs_slurm_python_worker=old_config["FRACTAL_SLURM_WORKER_PYTHON"],
238
+ jobs_poll_interval=int(
239
+ old_config.get("FRACTAL_SLURM_POLL_INTERVAL", 15)
240
+ ),
241
+ )
242
+ if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
243
+ resource_data["host"] = get_ssh_host()
244
+
245
+ resource_data = cast_serialize_resource(resource_data)
246
+
247
+ return resource_data
248
+
249
+
250
+ def fix_db():
251
+ # READ-ONLY CHECK
252
+
253
+ settings = get_settings()
254
+
255
+ # Verify that we are in a SLURM instance
256
+ if settings.FRACTAL_RUNNER_BACKEND == "local":
257
+ sys.exit(
258
+ "ERROR: FRACTAL_RUNNER_BACKEND='local' is not "
259
+ "supported for this data migration."
260
+ )
261
+
262
+ # Read old env file
263
+ old_config = get_old_dotenv_variables()
264
+
265
+ # Prepare resource data
266
+ resource_data = prepare_resource_data(old_config)
267
+
268
+ # Prepare profile/users data
269
+ profile_and_user_updates = prepare_profile_and_user_updates()
270
+
271
+ # ---------------------------------------
272
+
273
+ # WRITES
274
+
275
+ with next(get_sync_db()) as db:
276
+ # Create new resource
277
+ resource = Resource(**resource_data)
278
+ db.add(resource)
279
+ db.commit()
280
+ db.refresh(resource)
281
+ db.expunge(resource)
282
+ resource_id = resource.id
283
+ debug(f"CREATED RESOURCE with {resource_id=}")
284
+
285
+ # Update task groups
286
+ res = db.execute(select(TaskGroupV2).order_by(TaskGroupV2.id))
287
+ for taskgroup in res.scalars().all():
288
+ taskgroup.resource_id = resource_id
289
+ db.add(taskgroup)
290
+ db.commit()
291
+
292
+ # Update projects
293
+ res = db.execute(select(ProjectV2).order_by(ProjectV2.id))
294
+ for project in res.scalars().all():
295
+ project.resource_id = resource_id
296
+ db.add(project)
297
+ db.commit()
298
+
299
+ db.expunge_all()
300
+
301
+ for _, info in profile_and_user_updates.items():
302
+ debug(info)
303
+
304
+ # Create profile
305
+ profile_data = info.data
306
+ profile_data["resource_id"] = resource_id
307
+ profile = Profile(**profile_data)
308
+ db.add(profile)
309
+ db.commit()
310
+ db.refresh(profile)
311
+ db.expunge(profile)
312
+ profile_id = profile.id
313
+
314
+ # Update users
315
+ for user_update in info.user_updates:
316
+ user = db.get(UserOAuth, user_update.user_id)
317
+ user.profile_id = profile_id
318
+ user.project_dir = user_update.project_dir
319
+ user.slurm_accounts = user_update.slurm_accounts
320
+ db.add(user)
321
+ db.commit()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fractal-server
3
- Version: 2.17.0a10
3
+ Version: 2.17.0a11
4
4
  Summary: Backend component of the Fractal analytics platform
5
5
  License-Expression: BSD-3-Clause
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- fractal_server/__init__.py,sha256=G5BYCg1etHirfVvA4-6BLaLisXMafnQOALbQLrNgQAU,26
1
+ fractal_server/__init__.py,sha256=Pj-IcBWFE8pRiluuKSO6LdRI9wotfOFmP9J3PnyZ0GE,26
2
2
  fractal_server/__main__.py,sha256=68FlTuST3zbzVofFI8JSYsSBrBQ07Bv3Mu3PsZX9Fw0,11423
3
3
  fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
4
4
  fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,7 +29,7 @@ fractal_server/app/routes/admin/v2/impersonate.py,sha256=ictDjuvBr3iLv3YtwkVRMNQ
29
29
  fractal_server/app/routes/admin/v2/job.py,sha256=sFgMbOtUCIJ-ri6YD3ZWP7XETZZDQsLqPfT1kaH9RHQ,8577
30
30
  fractal_server/app/routes/admin/v2/profile.py,sha256=0Y_1Qv-BA6cHVrxPTDDBOpttpfuJN8g1FqFlG6JiOD8,3164
31
31
  fractal_server/app/routes/admin/v2/project.py,sha256=rRq7ZDngr_29skASnte1xfycZCjK-WPdeTf7siBXiCU,1182
32
- fractal_server/app/routes/admin/v2/resource.py,sha256=eLK3PxvpibwQgVfgpMb_CdqkiB7hz8-RtPqqtP9ujz8,6310
32
+ fractal_server/app/routes/admin/v2/resource.py,sha256=UWimApUcL9HPu8NN0ccDbReuKly3aCqff2SA-Y1iEzs,6349
33
33
  fractal_server/app/routes/admin/v2/task.py,sha256=9MMUI2PnyHQx08Xmt93O5rM60C_tlic27mP6t7ljpYo,4655
34
34
  fractal_server/app/routes/admin/v2/task_group.py,sha256=EDY9oliXq_xYVJ2HgRuE4-5MbL85j-y4LbWwupZxy38,6249
35
35
  fractal_server/app/routes/admin/v2/task_group_lifecycle.py,sha256=W7LjIBAheyjrn0fEz0SsWINqcZK5HMB5GRGMjPrc6a4,9994
@@ -102,6 +102,7 @@ fractal_server/config/_email.py,sha256=j1QmZCyspNbD1xxkypc9Kv299tU3vTO1AqDFJ8-LZ
102
102
  fractal_server/config/_main.py,sha256=9v64gJsvY1oGP70_AoJMnyMIeRo7FcIg6T8NDV-p9as,1992
103
103
  fractal_server/config/_oauth.py,sha256=7J4FphGVFfVmtQycCkas6scEJQJGZUGEzQ-t2PZiqSo,1934
104
104
  fractal_server/config/_settings_config.py,sha256=tsyXQOnn9QKCFJD6hRo_dJXlQQyl70DbqgHMJoZ1xnY,144
105
+ fractal_server/data_migrations/2_17_0.py,sha256=ltMrezWWma-KPnZpOzQ12_yebBrjrdu0m7pQQh_RGWM,10894
105
106
  fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
106
107
  fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
107
108
  fractal_server/exceptions.py,sha256=7ftpWwNsTQmNonWCynhH5ErUh1haPPhIaVPrNHla7-o,53
@@ -254,8 +255,8 @@ fractal_server/types/validators/_workflow_task_arguments_validators.py,sha256=HL
254
255
  fractal_server/urls.py,sha256=QjIKAC1a46bCdiPMu3AlpgFbcv6a4l3ABcd5xz190Og,471
255
256
  fractal_server/utils.py,sha256=SYVVUuXe_nWyrJLsy7QA-KJscwc5PHEXjvsW4TK7XQI,2180
256
257
  fractal_server/zip_tools.py,sha256=H0w7wS5yE4ebj7hw1_77YQ959dl2c-L0WX6J_ro1TY4,4884
257
- fractal_server-2.17.0a10.dist-info/METADATA,sha256=-hVkYe_goWyUvm96LujeOSDCXsmkYPVPlojm9jxLHGs,4227
258
- fractal_server-2.17.0a10.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
259
- fractal_server-2.17.0a10.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
260
- fractal_server-2.17.0a10.dist-info/licenses/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
261
- fractal_server-2.17.0a10.dist-info/RECORD,,
258
+ fractal_server-2.17.0a11.dist-info/METADATA,sha256=7ma0Jk6lM9eGlYkmJYO3Y_RTw3oVhJoNfVfGJ5Ex1Wk,4227
259
+ fractal_server-2.17.0a11.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
260
+ fractal_server-2.17.0a11.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
261
+ fractal_server-2.17.0a11.dist-info/licenses/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
262
+ fractal_server-2.17.0a11.dist-info/RECORD,,