fractal-server 2.7.0a10__py3-none-any.whl → 2.7.1__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.7.0a10"
1
+ __VERSION__ = "2.7.1"
@@ -29,7 +29,6 @@ class TaskV2(SQLModel, table=True):
29
29
  sa_column=Column(JSON, server_default="{}", default={}, nullable=False)
30
30
  )
31
31
 
32
- owner: Optional[str] = None
33
32
  version: Optional[str] = None
34
33
  args_schema_non_parallel: Optional[dict[str, Any]] = Field(
35
34
  sa_column=Column(JSON), default=None
@@ -44,7 +43,7 @@ class TaskV2(SQLModel, table=True):
44
43
  input_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
45
44
  output_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
46
45
 
47
- taskgroupv2_id: Optional[int] = Field(foreign_key="taskgroupv2.id")
46
+ taskgroupv2_id: int = Field(foreign_key="taskgroupv2.id")
48
47
 
49
48
  category: Optional[str] = None
50
49
  modality: Optional[str] = None
@@ -21,6 +21,7 @@ from fractal_server.app.routes.auth._aux_auth import (
21
21
  )
22
22
  from fractal_server.app.schemas.v2 import TaskGroupReadV2
23
23
  from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
24
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
24
25
  from fractal_server.logger import set_logger
25
26
 
26
27
  router = APIRouter()
@@ -50,6 +51,8 @@ async def query_task_group_list(
50
51
  user_group_id: Optional[int] = None,
51
52
  private: Optional[bool] = None,
52
53
  active: Optional[bool] = None,
54
+ pkg_name: Optional[str] = None,
55
+ origin: Optional[TaskGroupV2OriginEnum] = None,
53
56
  user: UserOAuth = Depends(current_active_superuser),
54
57
  db: AsyncSession = Depends(get_async_db),
55
58
  ) -> list[TaskGroupReadV2]:
@@ -75,6 +78,10 @@ async def query_task_group_list(
75
78
  stm = stm.where(is_(TaskGroupV2.active, True))
76
79
  else:
77
80
  stm = stm.where(is_(TaskGroupV2.active, False))
81
+ if origin is not None:
82
+ stm = stm.where(TaskGroupV2.origin == origin)
83
+ if pkg_name is not None:
84
+ stm = stm.where(TaskGroupV2.pkg_name.icontains(pkg_name))
78
85
 
79
86
  res = await db.execute(stm)
80
87
  task_groups_list = res.scalars().all()
@@ -24,6 +24,7 @@ from fractal_server.app.models.v2 import TaskV2
24
24
  from fractal_server.app.routes.auth import current_active_user
25
25
  from fractal_server.app.routes.auth import current_active_verified_user
26
26
  from fractal_server.app.schemas.v2 import TaskCreateV2
27
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
27
28
  from fractal_server.app.schemas.v2 import TaskReadV2
28
29
  from fractal_server.app.schemas.v2 import TaskUpdateV2
29
30
  from fractal_server.logger import set_logger
@@ -200,7 +201,7 @@ async def create_task(
200
201
  user_group_id=user_group_id,
201
202
  active=True,
202
203
  task_list=[db_task],
203
- origin="other",
204
+ origin=TaskGroupV2OriginEnum.OTHER,
204
205
  version=db_task.version,
205
206
  pkg_name=pkg_name,
206
207
  )
@@ -30,6 +30,7 @@ from ._aux_functions_tasks import _verify_non_duplication_user_constraint
30
30
  from fractal_server.app.models import UserOAuth
31
31
  from fractal_server.app.routes.auth import current_active_user
32
32
  from fractal_server.app.routes.auth import current_active_verified_user
33
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
33
34
  from fractal_server.tasks.utils import _normalize_package_name
34
35
  from fractal_server.tasks.utils import get_collection_log_v2
35
36
  from fractal_server.tasks.v2.background_operations import (
@@ -121,11 +122,11 @@ async def collect_tasks_pip(
121
122
  wheel_info["distribution"]
122
123
  )
123
124
  task_group_attrs["version"] = wheel_info["version"]
124
- task_group_attrs["origin"] = "wheel-file"
125
+ task_group_attrs["origin"] = TaskGroupV2OriginEnum.WHEELFILE
125
126
  else:
126
127
  pkg_name = task_collect.package
127
128
  task_group_attrs["pkg_name"] = _normalize_package_name(pkg_name)
128
- task_group_attrs["origin"] = "pypi"
129
+ task_group_attrs["origin"] = TaskGroupV2OriginEnum.PYPI
129
130
  latest_version = await get_package_version_from_pypi(
130
131
  task_collect.package,
131
132
  task_collect.package_version,
@@ -21,6 +21,7 @@ from fractal_server.app.routes.auth import current_active_verified_user
21
21
  from fractal_server.app.schemas.v2 import TaskCollectCustomV2
22
22
  from fractal_server.app.schemas.v2 import TaskCreateV2
23
23
  from fractal_server.app.schemas.v2 import TaskGroupCreateV2
24
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
24
25
  from fractal_server.app.schemas.v2 import TaskReadV2
25
26
  from fractal_server.config import get_settings
26
27
  from fractal_server.logger import set_logger
@@ -140,7 +141,7 @@ async def collect_task_custom(
140
141
 
141
142
  # Prepare task-group attributes
142
143
  task_group_attrs = dict(
143
- origin="other",
144
+ origin=TaskGroupV2OriginEnum.OTHER,
144
145
  pkg_name=task_collect.label,
145
146
  user_id=user.id,
146
147
  user_group_id=user_group_id,
@@ -241,7 +241,10 @@ async def _get_task_by_taskimport(
241
241
  "Found many task groups, after filtering by version."
242
242
  )
243
243
  final_task_group = await _disambiguate_task_groups(
244
- matching_task_groups, user_id, db, default_group_id
244
+ matching_task_groups=matching_task_groups,
245
+ user_id=user_id,
246
+ db=db,
247
+ default_group_id=default_group_id,
245
248
  )
246
249
  if final_task_group is None:
247
250
  logger.info(
@@ -1055,55 +1055,59 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1055
1055
  Arguments:
1056
1056
  jobid: ID of the SLURM job
1057
1057
  """
1058
-
1059
- # Loop over all job_ids, and fetch future and job objects
1060
- futures: list[Future] = []
1061
- jobs: list[SlurmJob] = []
1062
- with self.jobs_lock:
1063
- for job_id in job_ids:
1064
- future, job = self.jobs.pop(job_id)
1065
- futures.append(future)
1066
- jobs.append(job)
1067
- if not self.jobs:
1068
- self.jobs_empty_cond.notify_all()
1069
-
1070
- # Fetch subfolder from remote host
1058
+ # Handle all uncaught exceptions in this broad try/except block
1071
1059
  try:
1072
- self._get_subfolder_sftp(jobs=jobs)
1073
- except NoValidConnectionsError as e:
1074
- logger.error("NoValidConnectionError")
1075
- logger.error(f"{str(e)=}")
1076
- logger.error(f"{e.errors=}")
1077
- for err in e.errors:
1078
- logger.error(f"{str(err)}")
1079
-
1080
- raise e
1081
-
1082
- # First round of checking whether all output files exist
1083
- missing_out_paths = []
1084
- for job in jobs:
1085
- for ind_out_path, out_path in enumerate(
1086
- job.output_pickle_files_local
1087
- ):
1088
- if not out_path.exists():
1089
- missing_out_paths.append(out_path)
1090
- num_missing = len(missing_out_paths)
1091
- if num_missing > 0:
1092
- # Output pickle files may be missing e.g. because of some slow
1093
- # filesystem operation; wait some time before re-trying
1094
- settings = Inject(get_settings)
1095
- sleep_time = settings.FRACTAL_SLURM_ERROR_HANDLING_INTERVAL
1096
1060
  logger.info(
1097
- f"{num_missing} output pickle files are missing; "
1098
- f"sleep {sleep_time} seconds."
1061
+ f"[FractalSlurmSSHExecutor._completion] START, for {job_ids=}."
1099
1062
  )
1100
- for missing_file in missing_out_paths:
1101
- logger.debug(f"Missing output pickle file: {missing_file}")
1102
- time.sleep(sleep_time)
1103
1063
 
1104
- # Handle all jobs
1105
- for ind_job, job_id in enumerate(job_ids):
1064
+ # Loop over all job_ids, and fetch future and job objects
1065
+ futures: list[Future] = []
1066
+ jobs: list[SlurmJob] = []
1067
+ with self.jobs_lock:
1068
+ for job_id in job_ids:
1069
+ future, job = self.jobs.pop(job_id)
1070
+ futures.append(future)
1071
+ jobs.append(job)
1072
+ if not self.jobs:
1073
+ self.jobs_empty_cond.notify_all()
1074
+
1075
+ # Fetch subfolder from remote host
1106
1076
  try:
1077
+ self._get_subfolder_sftp(jobs=jobs)
1078
+ except NoValidConnectionsError as e:
1079
+ logger.error("NoValidConnectionError")
1080
+ logger.error(f"{str(e)=}")
1081
+ logger.error(f"{e.errors=}")
1082
+ for err in e.errors:
1083
+ logger.error(f"{str(err)}")
1084
+
1085
+ raise e
1086
+
1087
+ # First round of checking whether all output files exist
1088
+ missing_out_paths = []
1089
+ for job in jobs:
1090
+ for ind_out_path, out_path in enumerate(
1091
+ job.output_pickle_files_local
1092
+ ):
1093
+ if not out_path.exists():
1094
+ missing_out_paths.append(out_path)
1095
+ num_missing = len(missing_out_paths)
1096
+ if num_missing > 0:
1097
+ # Output pickle files may be missing e.g. because of some slow
1098
+ # filesystem operation; wait some time before re-trying
1099
+ settings = Inject(get_settings)
1100
+ sleep_time = settings.FRACTAL_SLURM_ERROR_HANDLING_INTERVAL
1101
+ logger.info(
1102
+ f"{num_missing} output pickle files are missing; "
1103
+ f"sleep {sleep_time} seconds."
1104
+ )
1105
+ for missing_file in missing_out_paths:
1106
+ logger.debug(f"Missing output pickle file: {missing_file}")
1107
+ time.sleep(sleep_time)
1108
+
1109
+ # Handle all jobs
1110
+ for ind_job, job_id in enumerate(job_ids):
1107
1111
  # Retrieve job and future objects
1108
1112
  job = jobs[ind_job]
1109
1113
  future = futures[ind_job]
@@ -1128,6 +1132,11 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1128
1132
  remaining_futures=remaining_futures,
1129
1133
  remaining_job_ids=remaining_job_ids,
1130
1134
  )
1135
+ logger.info(
1136
+ "[FractalSlurmSSHExecutor._completion] END, "
1137
+ f"for {job_ids=}, with JobExecutionError due "
1138
+ f"to missing {out_path.as_posix()}."
1139
+ )
1131
1140
  return
1132
1141
  except InvalidStateError:
1133
1142
  logger.warning(
@@ -1141,6 +1150,12 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1141
1150
  remaining_futures=remaining_futures,
1142
1151
  remaining_job_ids=remaining_job_ids,
1143
1152
  )
1153
+ logger.info(
1154
+ "[FractalSlurmSSHExecutor._completion] END, "
1155
+ f"for {job_ids=}, with JobExecutionError/"
1156
+ "InvalidStateError due to "
1157
+ f"missing {out_path.as_posix()}."
1158
+ )
1144
1159
  return
1145
1160
 
1146
1161
  # Read the task output
@@ -1217,16 +1232,22 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1217
1232
  else:
1218
1233
  future.set_result(outputs)
1219
1234
 
1220
- except Exception as e:
1235
+ except Exception as e:
1236
+ logger.warning(
1237
+ "[FractalSlurmSSHExecutor._completion] "
1238
+ f"An exception took place: {str(e)}."
1239
+ )
1240
+ for future in futures:
1221
1241
  try:
1242
+ logger.info(f"Set exception for {future=}")
1222
1243
  future.set_exception(e)
1223
- return
1224
1244
  except InvalidStateError:
1225
- logger.warning(
1226
- f"Future {future} (SLURM job ID: {job_id}) was already"
1227
- " cancelled, exit from"
1228
- " FractalSlurmSSHExecutor._completion."
1229
- )
1245
+ logger.info(f"Future {future} was already cancelled.")
1246
+ logger.info(
1247
+ f"[FractalSlurmSSHExecutor._completion] END, for {job_ids=}, "
1248
+ "from within exception handling."
1249
+ )
1250
+ return
1230
1251
 
1231
1252
  def _get_subfolder_sftp(self, jobs: list[SlurmJob]) -> None:
1232
1253
  """
@@ -30,6 +30,7 @@ from .task_collection import TaskCollectPipV2 # noqa F401
30
30
  from .task_group import TaskGroupCreateV2 # noqa F401
31
31
  from .task_group import TaskGroupReadV2 # noqa F401
32
32
  from .task_group import TaskGroupUpdateV2 # noqa F401
33
+ from .task_group import TaskGroupV2OriginEnum # noqa F401
33
34
  from .workflow import WorkflowCreateV2 # noqa F401
34
35
  from .workflow import WorkflowExportV2 # noqa F401
35
36
  from .workflow import WorkflowImportV2 # noqa F401
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime
2
+ from enum import Enum
2
3
  from typing import Literal
3
4
  from typing import Optional
4
5
 
@@ -13,11 +14,17 @@ from .._validators import valstr
13
14
  from .task import TaskReadV2
14
15
 
15
16
 
17
+ class TaskGroupV2OriginEnum(str, Enum):
18
+ PYPI = "pypi"
19
+ WHEELFILE = "wheel-file"
20
+ OTHER = "other"
21
+
22
+
16
23
  class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
17
24
  user_id: int
18
25
  user_group_id: Optional[int] = None
19
26
  active: bool = True
20
- origin: Literal["pypi", "wheel-file", "other"]
27
+ origin: TaskGroupV2OriginEnum
21
28
  pkg_name: str
22
29
  version: Optional[str] = None
23
30
  python_version: Optional[str] = None
@@ -315,13 +315,20 @@ async def _create_first_user(
315
315
 
316
316
 
317
317
  def _create_first_group():
318
+ """
319
+ Create a `UserGroup` with `name=FRACTAL_DEFAULT_GROUP_NAME`, if missing.
320
+ """
318
321
  function_logger = set_logger("fractal_server.create_first_group")
319
322
 
320
323
  function_logger.info(
321
324
  f"START _create_first_group, with name '{FRACTAL_DEFAULT_GROUP_NAME}'"
322
325
  )
323
326
  with next(get_sync_db()) as db:
324
- group_all = db.execute(select(UserGroup))
327
+ group_all = db.execute(
328
+ select(UserGroup).where(
329
+ UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
330
+ )
331
+ )
325
332
  if group_all.scalars().one_or_none() is None:
326
333
  first_group = UserGroup(name=FRACTAL_DEFAULT_GROUP_NAME)
327
334
  db.add(first_group)
fractal_server/config.py CHANGED
@@ -167,9 +167,9 @@ class Settings(BaseSettings):
167
167
  ###########################################################################
168
168
  # DATABASE
169
169
  ###########################################################################
170
- DB_ENGINE: Literal["sqlite", "postgres", "postgres-psycopg"] = "sqlite"
170
+ DB_ENGINE: Literal["sqlite", "postgres-psycopg"] = "sqlite"
171
171
  """
172
- Select which database engine to use (supported: `sqlite` and `postgres`).
172
+ Database engine to use (supported: `sqlite`, `postgres-psycopg`).
173
173
  """
174
174
  DB_ECHO: bool = False
175
175
  """
@@ -203,16 +203,7 @@ class Settings(BaseSettings):
203
203
 
204
204
  @property
205
205
  def DATABASE_ASYNC_URL(self) -> URL:
206
- if self.DB_ENGINE == "postgres":
207
- url = URL.create(
208
- drivername="postgresql+asyncpg",
209
- username=self.POSTGRES_USER,
210
- password=self.POSTGRES_PASSWORD,
211
- host=self.POSTGRES_HOST,
212
- port=self.POSTGRES_PORT,
213
- database=self.POSTGRES_DB,
214
- )
215
- elif self.DB_ENGINE == "postgres-psycopg":
206
+ if self.DB_ENGINE == "postgres-psycopg":
216
207
  url = URL.create(
217
208
  drivername="postgresql+psycopg",
218
209
  username=self.POSTGRES_USER,
@@ -235,11 +226,7 @@ class Settings(BaseSettings):
235
226
 
236
227
  @property
237
228
  def DATABASE_SYNC_URL(self):
238
- if self.DB_ENGINE == "postgres":
239
- return self.DATABASE_ASYNC_URL.set(
240
- drivername="postgresql+psycopg2"
241
- )
242
- elif self.DB_ENGINE == "postgres-psycopg":
229
+ if self.DB_ENGINE == "postgres-psycopg":
243
230
  return self.DATABASE_ASYNC_URL.set(drivername="postgresql+psycopg")
244
231
  else:
245
232
  if not self.SQLITE_PATH:
@@ -546,20 +533,13 @@ class Settings(BaseSettings):
546
533
  """
547
534
  Checks that db environment variables are properly set.
548
535
  """
549
- if self.DB_ENGINE == "postgres":
536
+ if self.DB_ENGINE == "postgres-psycopg":
550
537
  if not self.POSTGRES_DB:
551
538
  raise FractalConfigurationError(
552
- "POSTGRES_DB cannot be None when DB_ENGINE=postgres."
553
- )
554
- try:
555
- import psycopg2 # noqa: F401
556
- import asyncpg # noqa: F401
557
- except ModuleNotFoundError:
558
- raise FractalConfigurationError(
559
- "DB engine is `postgres` but `psycopg2` or `asyncpg` "
560
- "are not available"
539
+ "POSTGRES_DB cannot be None when DB_ENGINE="
540
+ "postgres-psycopg."
561
541
  )
562
- elif self.DB_ENGINE == "postgres-psycopg":
542
+
563
543
  try:
564
544
  import psycopg # noqa: F401
565
545
  except ModuleNotFoundError:
@@ -0,0 +1,42 @@
1
+ """Update TaskV2 post 2.7.0
2
+
3
+ Revision ID: 8e8f227a3e36
4
+ Revises: 034a469ec2eb
5
+ Create Date: 2024-10-29 09:01:33.075251
6
+
7
+ """
8
+ import sqlalchemy as sa
9
+ from alembic import op
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "8e8f227a3e36"
14
+ down_revision = "034a469ec2eb"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table("taskv2", schema=None) as batch_op:
22
+ batch_op.alter_column(
23
+ "taskgroupv2_id", existing_type=sa.INTEGER(), nullable=False
24
+ )
25
+ batch_op.drop_column("owner")
26
+
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("taskv2", schema=None) as batch_op:
33
+ batch_op.add_column(
34
+ sa.Column(
35
+ "owner", sa.VARCHAR(), autoincrement=False, nullable=True
36
+ )
37
+ )
38
+ batch_op.alter_column(
39
+ "taskgroupv2_id", existing_type=sa.INTEGER(), nullable=True
40
+ )
41
+
42
+ # ### end Alembic commands ###
@@ -73,6 +73,11 @@ async def _pip_install(
73
73
  ),
74
74
  logger_name=logger_name,
75
75
  )
76
+ await execute_command(
77
+ cwd=Path(task_group.venv_path),
78
+ command=f"{python_bin} -m pip install setuptools",
79
+ logger_name=logger_name,
80
+ )
76
81
  await execute_command(
77
82
  cwd=Path(task_group.venv_path),
78
83
  command=f"{python_bin} -m pip install {pip_install_str}",
@@ -205,7 +205,7 @@ def background_collect_pip_ssh(
205
205
  remove_venv_folder_upon_failure = True
206
206
 
207
207
  stdout = _customize_and_run_template(
208
- script_filename="_2_upgrade_pip.sh",
208
+ script_filename="_2_preliminary_pip_operations.sh",
209
209
  **common_args,
210
210
  )
211
211
  stdout = _customize_and_run_template(
@@ -15,6 +15,7 @@ VENVPYTHON=${PACKAGE_ENV_DIR}/bin/python
15
15
  # Upgrade pip
16
16
  write_log "START upgrade pip"
17
17
  "$VENVPYTHON" -m pip install "pip<=__FRACTAL_MAX_PIP_VERSION__" --upgrade
18
+ "$VENVPYTHON" -m pip install setuptools
18
19
  write_log "END upgrade pip"
19
20
  echo
20
21
 
@@ -1,35 +1,29 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.7.0a10
3
+ Version: 2.7.1
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
7
7
  Author: Tommaso Comparin
8
8
  Author-email: tommaso.comparin@exact-lab.it
9
- Requires-Python: >=3.9,<4.0
9
+ Requires-Python: >=3.10,<4.0
10
10
  Classifier: License :: OSI Approved :: BSD License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
- Provides-Extra: gunicorn
17
- Provides-Extra: postgres
18
- Provides-Extra: postgres-psycopg-binary
19
15
  Requires-Dist: aiosqlite (>=0.19.0,<0.20.0)
20
16
  Requires-Dist: alembic (>=1.13.1,<2.0.0)
21
- Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "postgres"
22
17
  Requires-Dist: bcrypt (==4.0.1)
23
18
  Requires-Dist: cloudpickle (>=3.0.0,<3.1.0)
24
19
  Requires-Dist: clusterfutures (>=0.5,<0.6)
25
20
  Requires-Dist: fabric (>=3.2.2,<4.0.0)
26
21
  Requires-Dist: fastapi (>=0.115.0,<0.116.0)
27
22
  Requires-Dist: fastapi-users[oauth] (>=12.1.0,<13.0.0)
28
- Requires-Dist: gunicorn (>=21.2,<23.0) ; extra == "gunicorn"
23
+ Requires-Dist: gunicorn (>=21.2,<23.0)
29
24
  Requires-Dist: packaging (>=23.2,<24.0)
30
25
  Requires-Dist: psutil (>=5.9.8,<6.0.0)
31
- Requires-Dist: psycopg2 (>=2.9.5,<3.0.0) ; extra == "postgres"
32
- Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0) ; extra == "postgres-psycopg-binary"
26
+ Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0)
33
27
  Requires-Dist: pydantic (>=1.10.8,<2)
34
28
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
35
29
  Requires-Dist: sqlalchemy[asyncio] (>=2.0.23,<2.1)
@@ -1,4 +1,4 @@
1
- fractal_server/__init__.py,sha256=UeQbqAPJgZpXFlyJ7lX8wCpu8MkVzO3P6a4ALxAP1aE,25
1
+ fractal_server/__init__.py,sha256=e5_mOoPeW4Rbnylz3g2qQviWIKwVN-scjNbLROol4Xc,22
2
2
  fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
3
3
  fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
4
4
  fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -20,7 +20,7 @@ fractal_server/app/models/v2/collection_state.py,sha256=Yx18ZbywjraOdlHFyRVlb3VP
20
20
  fractal_server/app/models/v2/dataset.py,sha256=-7sxHEw4IIAvF_uSan7tA3o8hvoakBkQ0SRvqS2iOQU,1455
21
21
  fractal_server/app/models/v2/job.py,sha256=ypJmN-qspkKBGhBG7Mt-HypSQqcQ2EmB4Bzzb2-y550,1535
22
22
  fractal_server/app/models/v2/project.py,sha256=rAHoh5KfYwIaW7rTX0_O0jvWmxEvfo1BafvmcXuSSRk,786
23
- fractal_server/app/models/v2/task.py,sha256=8cT_YBA5dyjBjunqCg23I-t3XZ62xJgiR-6brXISuLA,3337
23
+ fractal_server/app/models/v2/task.py,sha256=YIqPCQY4y0DbgWpDxYkZNApElFoHMEz_9GIP1nEGvUI,3295
24
24
  fractal_server/app/models/v2/workflow.py,sha256=YBgFGCziUgU0aJ5EM3Svu9W2c46AewZO9VBlFCHiSps,1069
25
25
  fractal_server/app/models/v2/workflowtask.py,sha256=iDuJYk8kp4PNqGmbKRtGI7y-QsbjkNd_gDsbMzL4i-g,1274
26
26
  fractal_server/app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,7 +30,7 @@ fractal_server/app/routes/admin/v2/__init__.py,sha256=zkdrk3mSQWfgTJugS8Sc_Q_xCA
30
30
  fractal_server/app/routes/admin/v2/job.py,sha256=JmNmF5MeHtcDQzGadCJWwFNDQvB5G8SwVABaL11S1Vc,8268
31
31
  fractal_server/app/routes/admin/v2/project.py,sha256=luy-yiGX1JYTdPm1hpIdDUUqPm8xHuipLy9k2X6zu74,1223
32
32
  fractal_server/app/routes/admin/v2/task.py,sha256=Y0eujBgGhVapNXfW9azDxw4EBzLmEmCdh70y1RNQcb0,3895
33
- fractal_server/app/routes/admin/v2/task_group.py,sha256=loxjJe9C2BBsG8gWkhHgrn6ReeE4mnipQ21PMP3MrKI,5398
33
+ fractal_server/app/routes/admin/v2/task_group.py,sha256=KAp7QJ6AmrNLnYb8nXEEZj5AN8MqTUM6G2PpWEYKL7s,5726
34
34
  fractal_server/app/routes/api/__init__.py,sha256=2IDheFi0OFdsUg7nbUiyahqybvpgXqeHUXIL2QtWrQQ,641
35
35
  fractal_server/app/routes/api/v1/__init__.py,sha256=Y2HQdG197J0a7DyQEE2jn53IfxD0EHGhzK1I2JZuEck,958
36
36
  fractal_server/app/routes/api/v1/_aux_functions.py,sha256=P9Q48thGH95w0h5cacYoibxqgiiLW4oqZ8rNJ2LIISY,13219
@@ -50,12 +50,12 @@ fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhK
50
50
  fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhzYBsVGYFhHZZXAE,6653
51
51
  fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
52
52
  fractal_server/app/routes/api/v2/submit.py,sha256=tq-NGnUlpIcm_MRN47rJRHkRcIJ5HiL4Wj1wItJy3o8,8185
53
- fractal_server/app/routes/api/v2/task.py,sha256=R_1bCinQvNrkEh6uAguNNfimduz1uJzgN_iTDwjnVF4,7209
54
- fractal_server/app/routes/api/v2/task_collection.py,sha256=gCxOwigT_tfs8lCDNoE7nxl9-9iuRp1gW__3YXqsioc,11478
55
- fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=9T0U_4gqrQbJCy6uFDCMSZ-b1sfNIzyz_qm4P41W2Gs,6133
53
+ fractal_server/app/routes/api/v2/task.py,sha256=hrYuVT6XDb3eFFUkQJrAf0O9eMDlE4bi0v5iJyQ5J5E,7293
54
+ fractal_server/app/routes/api/v2/task_collection.py,sha256=URtHWrUy83v17uJyIciILYPuZXbGRryw-xO2FC5J9PQ,11581
55
+ fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=kWctxgNi20uwsfHx9l_mwhI50OIxWRESjU9UiCDVVFU,6217
56
56
  fractal_server/app/routes/api/v2/task_group.py,sha256=P32EUYbtGThexSWe5zI9WUFrgoOMof035fJBILTNnfQ,5580
57
57
  fractal_server/app/routes/api/v2/workflow.py,sha256=PyvkrUHHzFGUGZE5X0VW5u3DPQA7wtXXNcEpG7-N66I,8687
58
- fractal_server/app/routes/api/v2/workflow_import.py,sha256=3qX3iHnLJb62TQFLQKakcC_mYh-tWXkYL-4PuyEkwRs,10895
58
+ fractal_server/app/routes/api/v2/workflow_import.py,sha256=rD26vZ-ztjehvglrERixTeHtXuzepAtgAuPiKRNz84Q,10981
59
59
  fractal_server/app/routes/api/v2/workflowtask.py,sha256=ciHTwXXFiFnMF7ZpJ3Xs0q6YfuZrFvIjqndlzAEdZpo,6969
60
60
  fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
61
61
  fractal_server/app/routes/auth/_aux_auth.py,sha256=ifkNocTYatBSMYGwiR14qohmvR9SfMldceiEj6uJBrU,4783
@@ -84,7 +84,7 @@ fractal_server/app/runner/executors/slurm/remote.py,sha256=wLziIsGdSMiO-jIXM8x77
84
84
  fractal_server/app/runner/executors/slurm/ssh/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
85
85
  fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py,sha256=bKo5Ja0IGxJWpPWyh9dN0AG-PwzTDZzD5LyaEHB3YU4,3742
86
86
  fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py,sha256=rwlqZzoGo4SAb4nSlFjsQJdaCgfM1J6YGcjb8yYxlqc,4506
87
- fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=9nR5aaJdsz3uHu9CQkOXb7IYibA74R0kKkf17Xq6JDo,57470
87
+ fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=si_RHAMnXwQorQ_gWeZ_hQ_cNQbbAuYPjg7nwFQoPVg,58709
88
88
  fractal_server/app/runner/executors/slurm/sudo/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
89
89
  fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py,sha256=wAgwpVcr6JIslKHOuS0FhRa_6T1KCManyRJqA-fifzw,1909
90
90
  fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py,sha256=z5LlhaiqAb8pHsF1WwdzXN39C5anQmwjo1rSQgtRAYE,4422
@@ -145,7 +145,7 @@ fractal_server/app/schemas/v1/state.py,sha256=GYeOE_1PtDOgu5W4t_3gw3DBHXH2aCGzIN
145
145
  fractal_server/app/schemas/v1/task.py,sha256=7BxOZ_qoRQ8n3YbQpDvB7VMcxB5fSYQmR5RLIWhuJ5U,3704
146
146
  fractal_server/app/schemas/v1/task_collection.py,sha256=uvq9bcMaGD_qHsh7YtcpoSAkVAbw12eY4DocIO3MKOg,3057
147
147
  fractal_server/app/schemas/v1/workflow.py,sha256=tuOs5E5Q_ozA8if7YPZ07cQjzqB_QMkBS4u92qo4Ro0,4618
148
- fractal_server/app/schemas/v2/__init__.py,sha256=G44JgD_i_zCpV7yjXcoS5ygOS3IfsIWoktLVZao6TaE,2323
148
+ fractal_server/app/schemas/v2/__init__.py,sha256=97y6QY0I4322CPXQCt3WO3QBWhVkmFgwLn8y2ZwWNR0,2382
149
149
  fractal_server/app/schemas/v2/dataset.py,sha256=865ia13E9mWu1DaYyppKW2csNYglaInrScrprdVYX7A,2552
150
150
  fractal_server/app/schemas/v2/dumps.py,sha256=s6dg-pHZFui6t2Ktm0SMxjKDN-v-ZqBHz9iTsBQF3eU,1712
151
151
  fractal_server/app/schemas/v2/job.py,sha256=oYSLYkQ0HL83QyjEGIaggtZ117FndzFlONMKWd9sTXM,3270
@@ -154,13 +154,12 @@ fractal_server/app/schemas/v2/project.py,sha256=UXEA0UUUe0bFFOVLLmVtvDFLBO5vmD1J
154
154
  fractal_server/app/schemas/v2/status.py,sha256=SQaUpQkjFq5c5k5J4rOjNhuQaDOEg8lksPhkKmPU5VU,332
155
155
  fractal_server/app/schemas/v2/task.py,sha256=FFAbYwDlqowB8gVMdjFVPVHvAM0T89PYLixUth49xfQ,6870
156
156
  fractal_server/app/schemas/v2/task_collection.py,sha256=Ddw_7QaQ93kdEIwWQvzLQDu03gho_OHdhah3n0ioK3M,6296
157
- fractal_server/app/schemas/v2/task_group.py,sha256=F40u64z-wXHNPFjx9RHozzl_SySTHfKFc-sBFyn_e0I,2352
157
+ fractal_server/app/schemas/v2/task_group.py,sha256=oWy7NNsw8Co85qpLyK8FPNTgpAMvx0ZuXjIOVZuLEpM,2466
158
158
  fractal_server/app/schemas/v2/workflow.py,sha256=HSNQSrBRdoBzh8Igr76FUWCAWvVzykrqmUv1vGv-8og,2026
159
159
  fractal_server/app/schemas/v2/workflowtask.py,sha256=vDdMktYbHeYBgB5OuWSv6wRPRXWqvetkeqQ7IC5YtfA,5751
160
- fractal_server/app/security/__init__.py,sha256=V1NOWlmaFZHMR6SrkMl62jyAuqYONyo8lyGvR6UZesM,12312
160
+ fractal_server/app/security/__init__.py,sha256=8Xd4GxumZgvxEH1Vli3ULehwdesEPiaAbtffJvAEgNo,12509
161
161
  fractal_server/app/user_settings.py,sha256=aZgQ3i0JkHfgwLGW1ee6Gzr1ae3IioFfJKKSsSS8Svk,1312
162
- fractal_server/config.py,sha256=gX0aYwDwbC5y7JNorifON84YMveubb7XTb4sH14N3KM,23667
163
- fractal_server/data_migrations/2_7_0.py,sha256=DQQJ_tLYFteH3Jw246ovIh3Dac_9SaAefoy7FLw5Cso,11145
162
+ fractal_server/config.py,sha256=-G1RvmaeSb6_wffUFuaAmhJV3u1q3HRpMLEfpGXBrz4,22797
164
163
  fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
165
164
  fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
166
165
  fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
@@ -183,6 +182,7 @@ fractal_server/migrations/versions/5bf02391cfef_v2.py,sha256=axhNkr_H6R4rRbY7oGY
183
182
  fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py,sha256=Q-DsMzG3IcUV2Ol1dhJWosDvKERamBE6QvA2zzS5zpQ,1632
184
183
  fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py,sha256=mbWuCkTpRAdGbRhW7lhXs_e5S6O37UAcCN6JfoY5H8A,1353
185
184
  fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py,sha256=NSCuhANChsg76vBkShBl-9tQ4VEHubOjtAv1etHhlvY,2684
185
+ fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py,sha256=68y9-fpSuKx6KPtM_9n8Ho0I1qwa8IoG-yJqXUYQrGg,1111
186
186
  fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py,sha256=6pgODDtyAxevZvAJBj9IJ41inhV1RpwbpZr_qfPPu1A,1115
187
187
  fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py,sha256=yL3-Hvzw5jBLKj4LFP1z5ofZE9L9W3tLwYtPNW7z4ko,1508
188
188
  fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py,sha256=eKTZm3EgUgapXBxO0RuHkEfTKic-TZG3ADaMpGLuc0k,1057
@@ -209,13 +209,13 @@ fractal_server/tasks/v1/endpoint_operations.py,sha256=YyMU1Y3Xt7D9WOKqaMLuwEoIaA
209
209
  fractal_server/tasks/v1/get_collection_data.py,sha256=5C22jp356rCH5IIC0J57wOu-DCC_kp3B6p68JooN7IM,508
210
210
  fractal_server/tasks/v1/utils.py,sha256=J9oKys-82OehBxOon5wWl3CxjVBgYWeVEEyWGVFnreI,1759
211
211
  fractal_server/tasks/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
- fractal_server/tasks/v2/_venv_pip.py,sha256=FOD20yKfTsW7sim3h7CsB6pgp85JBhELyYvbkpaiDKA,6390
212
+ fractal_server/tasks/v2/_venv_pip.py,sha256=EaauUkFIk4wa79B4OudhV8CYOzgWftTdfz8u4bEdjgo,6555
213
213
  fractal_server/tasks/v2/background_operations.py,sha256=jWmg_XkBmcXQfPKJu_eu0wtDL4sMp1QP2bIZ9QtMj_Y,15411
214
- fractal_server/tasks/v2/background_operations_ssh.py,sha256=W-w_a7FgrSc9FAVSXoVBzvXSCmH9oQkaDJx2S9hQPlc,13616
214
+ fractal_server/tasks/v2/background_operations_ssh.py,sha256=_G0rOohkkHLGcvCQyfoMObnII-sxVwrLDW2PKz8IfCQ,13631
215
215
  fractal_server/tasks/v2/database_operations.py,sha256=6r56yyFPnEBrXl6ncmO6D76znzISQCFZqCYcD-Ummd4,1213
216
216
  fractal_server/tasks/v2/endpoint_operations.py,sha256=MtUoI0XWHuPSousDeH2IC2WU--AUKQVup6Q6AbHiNUA,4102
217
217
  fractal_server/tasks/v2/templates/_1_create_venv.sh,sha256=7tt-B6n8KRN-pannZ0enE6XSxyq-hKRYRGY63CvtINI,1151
218
- fractal_server/tasks/v2/templates/_2_upgrade_pip.sh,sha256=ca5Yng6JgJYu-a4QrsIsatwUmrLdRWBKw7_VJrY7WLY,555
218
+ fractal_server/tasks/v2/templates/_2_preliminary_pip_operations.sh,sha256=NwkfDtNeT4YjUsqZuK4uN71bTe-wHIn2wmNhflimRo8,595
219
219
  fractal_server/tasks/v2/templates/_3_pip_install.sh,sha256=T9sabeB9iQzVZpLfuLkKGz9EpfHkUrJHKWO4HNij6yM,595
220
220
  fractal_server/tasks/v2/templates/_4_pip_freeze.sh,sha256=qHdDKu1svXi1VQKGePciEJK4_uEKuwAvwaDCcGxSvNk,274
221
221
  fractal_server/tasks/v2/templates/_5_pip_show.sh,sha256=GrJ19uHYQxANEy9JaeNJZVTquY9c8Ww9eCdnC7eLVr0,1754
@@ -223,8 +223,8 @@ fractal_server/tasks/v2/utils.py,sha256=MnY6MhcxDRo4rPuXo2tQ252eWEPZF3OlCGe-p5Mr
223
223
  fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
224
224
  fractal_server/utils.py,sha256=jrlCBPmC7F0ptBVcDac-EbZNsdYTLbHfX9oxkXthS5Q,2193
225
225
  fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
226
- fractal_server-2.7.0a10.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
227
- fractal_server-2.7.0a10.dist-info/METADATA,sha256=rD95AUBtyVqOfXOvyO_HG4mXGow5mhKpX3znot1LGBk,4631
228
- fractal_server-2.7.0a10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
229
- fractal_server-2.7.0a10.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
230
- fractal_server-2.7.0a10.dist-info/RECORD,,
226
+ fractal_server-2.7.1.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
227
+ fractal_server-2.7.1.dist-info/METADATA,sha256=L3ZYv96IVgdninD7rHRXpJ6M7HqL_zh2KMG86cqHtlE,4303
228
+ fractal_server-2.7.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
229
+ fractal_server-2.7.1.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
230
+ fractal_server-2.7.1.dist-info/RECORD,,
@@ -1,323 +0,0 @@
1
- import asyncio
2
- import logging
3
- import os
4
- import sys
5
- from pathlib import Path
6
- from typing import Any
7
- from typing import Optional
8
-
9
- from fastapi import HTTPException
10
- from sqlalchemy import select
11
- from sqlalchemy.orm import Session
12
-
13
- from fractal_server.app.db import get_async_db
14
- from fractal_server.app.db import get_sync_db
15
- from fractal_server.app.models import TaskGroupV2
16
- from fractal_server.app.models import TaskV2
17
- from fractal_server.app.models import UserGroup
18
- from fractal_server.app.models import UserOAuth
19
- from fractal_server.app.models import UserSettings
20
- from fractal_server.app.routes.api.v2._aux_functions_tasks import (
21
- _verify_non_duplication_group_constraint,
22
- )
23
- from fractal_server.app.routes.api.v2._aux_functions_tasks import (
24
- _verify_non_duplication_user_constraint,
25
- )
26
- from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
27
- from fractal_server.data_migrations.tools import _check_current_version
28
- from fractal_server.tasks.utils import _normalize_package_name
29
- from fractal_server.utils import get_timestamp
30
-
31
- logger = logging.getLogger("fix_db")
32
-
33
-
34
- async def check_non_duplication_constraints(
35
- *,
36
- user_id: int,
37
- pkg_name: str,
38
- version: Optional[str] = None,
39
- user_group_id: Optional[int] = None,
40
- ):
41
- try:
42
- async for db_async in get_async_db():
43
- await _verify_non_duplication_user_constraint(
44
- user_id=user_id,
45
- pkg_name=pkg_name,
46
- version=version,
47
- db=db_async,
48
- )
49
- await _verify_non_duplication_group_constraint(
50
- user_group_id=user_group_id,
51
- pkg_name=pkg_name,
52
- version=version,
53
- db=db_async,
54
- )
55
- except HTTPException as e:
56
- logger.error(
57
- "Adding a `TaskGroupV2` with "
58
- f"{user_id=}, {pkg_name=}, {version=} and {user_group_id=} "
59
- "would break the non-duplication constraint."
60
- )
61
- logger.error(f"Original error: {str(e)}")
62
-
63
- sys.exit("ERROR")
64
-
65
-
66
- def get_unique_value(list_of_objects: list[dict[str, Any]], key: str):
67
- """
68
- Loop over `list_of_objects` and extract (unique) value for `key`.
69
- """
70
- unique_values = set()
71
- for this_obj in list_of_objects:
72
- this_value = this_obj.get(key, None)
73
- unique_values.add(this_value)
74
- if len(unique_values) != 1:
75
- raise RuntimeError(
76
- f"There must be a single taskgroup `{key}`, but {unique_values=}"
77
- )
78
- return unique_values.pop()
79
-
80
-
81
- def get_users_mapping(db) -> dict[str, int]:
82
- logger.warning("START _check_users")
83
- print()
84
-
85
- stm_users = select(UserOAuth).order_by(UserOAuth.id)
86
- users = db.execute(stm_users).scalars().unique().all()
87
- name_to_user_id = {}
88
- for user in users:
89
- logger.warning(f"START handling user {user.id}: '{user.email}'")
90
- # Compute "name" attribute
91
- user_settings = db.get(UserSettings, user.user_settings_id)
92
- name = user.username or user_settings.slurm_user
93
- logger.warning(f"{name=}")
94
- # Fail for missing values
95
- if name is None:
96
- raise ValueError(
97
- f"User with {user.id=} and {user.email=} has no "
98
- "`username` or `slurm_user` set."
99
- "Please fix this issue manually."
100
- )
101
- # Fail for non-unique values
102
- existing_user = name_to_user_id.get(name, None)
103
- if existing_user is not None:
104
- raise ValueError(
105
- f"User with {user.id=} and {user.email=} has same "
106
- f"`(username or slurm_user)={name}` as another user. "
107
- "Please fix this issue manually."
108
- )
109
- # Update dictionary
110
- name_to_user_id[name] = user.id
111
- logger.warning(f"END handling user {user.id}: '{user.email}'")
112
- print()
113
- logger.warning("END _check_users")
114
- print()
115
- return name_to_user_id
116
-
117
-
118
- def get_default_user_group_id(db):
119
- stm = select(UserGroup.id).where(
120
- UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
121
- )
122
- res = db.execute(stm)
123
- default_group_id = res.scalars().one_or_none()
124
- if default_group_id is None:
125
- raise RuntimeError("Default user group is missing.")
126
- else:
127
- return default_group_id
128
-
129
-
130
- def get_default_user_id(db):
131
- DEFAULT_USER_EMAIL = os.getenv("FRACTAL_V27_DEFAULT_USER_EMAIL")
132
- if DEFAULT_USER_EMAIL is None:
133
- raise ValueError(
134
- "FRACTAL_V27_DEFAULT_USER_EMAIL env variable is not set. "
135
- "Please set it to be the email of the user who will own "
136
- "all previously-global tasks."
137
- )
138
-
139
- stm = select(UserOAuth.id).where(UserOAuth.email == DEFAULT_USER_EMAIL)
140
- res = db.execute(stm)
141
- default_user_id = res.scalars().one_or_none()
142
- if default_user_id is None:
143
- raise RuntimeError(
144
- f"Default user with email {DEFAULT_USER_EMAIL} is missing."
145
- )
146
- else:
147
- return default_user_id
148
-
149
-
150
- def prepare_task_groups(
151
- *,
152
- user_mapping: dict[str, int],
153
- default_user_group_id: int,
154
- default_user_id: int,
155
- db: Session,
156
- ):
157
- stm_tasks = select(TaskV2).order_by(TaskV2.id)
158
- res = db.execute(stm_tasks).scalars().all()
159
- task_groups = {}
160
- for task in res:
161
- if (
162
- task.source.startswith(("pip_remote", "pip_local"))
163
- and task.source.count(":") == 5
164
- ):
165
- source_fields = task.source.split(":")
166
- (
167
- collection_mode,
168
- pkg_name,
169
- version,
170
- extras,
171
- python_version,
172
- name,
173
- ) = source_fields
174
- pkg_name = _normalize_package_name(pkg_name)
175
- task_group_key = ":".join(
176
- [pkg_name, version, extras, python_version]
177
- )
178
- if collection_mode == "pip_remote":
179
- origin = "pypi"
180
- elif collection_mode == "pip_local":
181
- origin = "wheel-file"
182
- else:
183
- raise RuntimeError(
184
- f"Invalid {collection_mode=} for {task.source=}."
185
- )
186
- new_obj = dict(
187
- task=task,
188
- user_id=default_user_id,
189
- origin=origin,
190
- pkg_name=pkg_name,
191
- version=version,
192
- pip_extras=extras,
193
- python_version=python_version,
194
- )
195
-
196
- if task_group_key in task_groups:
197
- task_groups[task_group_key].append(new_obj)
198
- else:
199
- task_groups[task_group_key] = [new_obj]
200
- else:
201
- owner = task.owner
202
- if owner is None:
203
- raise RuntimeError(
204
- "Error: `owner` is `None` for "
205
- f"{task.id=}, {task.source=}, {task.owner=}."
206
- )
207
- user_id = user_mapping.get(owner, None)
208
- if user_id is None:
209
- raise RuntimeError(
210
- "Error: `user_id` is `None` for "
211
- f"{task.id=}, {task.source=}, {task.owner=}"
212
- )
213
- task_group_key = "-".join(
214
- [
215
- "NOT_PIP",
216
- str(task.id),
217
- str(task.version),
218
- task.source,
219
- str(task.owner),
220
- ]
221
- )
222
- if task_group_key in task_groups:
223
- raise RuntimeError(
224
- f"ERROR: Duplicated {task_group_key=} for "
225
- f"{task.id=}, {task.source=}, {task.owner=}"
226
- )
227
- else:
228
- task_groups[task_group_key] = [
229
- dict(
230
- task=task,
231
- user_id=user_id,
232
- origin="other",
233
- pkg_name=task.source,
234
- version=task.version,
235
- )
236
- ]
237
-
238
- for task_group_key, task_group_objects in task_groups.items():
239
- print("-" * 80)
240
- print(f"Start handling task group with key '{task_group_key}")
241
- task_group_task_list = [item["task"] for item in task_group_objects]
242
- print("List of tasks to be included")
243
- for task in task_group_task_list:
244
- print(f" {task.id=}, {task.source=}")
245
-
246
- task_group_attributes = dict(
247
- pkg_name=get_unique_value(task_group_objects, "pkg_name"),
248
- version=get_unique_value(task_group_objects, "version"),
249
- origin=get_unique_value(task_group_objects, "origin"),
250
- user_id=get_unique_value(task_group_objects, "user_id"),
251
- user_group_id=default_user_group_id,
252
- python_version=get_unique_value(
253
- task_group_objects, "python_version"
254
- ),
255
- pip_extras=get_unique_value(task_group_objects, "pip_extras"),
256
- task_list=task_group_task_list,
257
- active=True,
258
- timestamp_created=get_timestamp(),
259
- )
260
-
261
- if not task_group_key.startswith("NOT_PIP"):
262
- cmd = next(
263
- getattr(task_group_task_list[0], attr_name)
264
- for attr_name in ["command_non_parallel", "command_parallel"]
265
- if getattr(task_group_task_list[0], attr_name) is not None
266
- )
267
- python_bin = cmd.split()[0]
268
- venv_path = Path(python_bin).parents[1].as_posix()
269
- path = Path(python_bin).parents[2].as_posix()
270
- task_group_attributes["venv_path"] = venv_path
271
- task_group_attributes["path"] = path
272
-
273
- print()
274
- print("List of task-group attributes")
275
- for key, value in task_group_attributes.items():
276
- if key != "task_list":
277
- print(f" {key}: {value}")
278
-
279
- print()
280
-
281
- # Verify non-duplication constraints
282
- asyncio.run(
283
- check_non_duplication_constraints(
284
- user_id=task_group_attributes["user_id"],
285
- user_group_id=task_group_attributes["user_group_id"],
286
- pkg_name=task_group_attributes["pkg_name"],
287
- version=task_group_attributes["version"],
288
- )
289
- )
290
- logger.warning(
291
- "Non-duplication-constraint check is OK, "
292
- "proceed and create TaskGroupV2."
293
- )
294
-
295
- # Create the TaskGroupV2 object and commit it
296
- task_group = TaskGroupV2(**task_group_attributes)
297
- db.add(task_group)
298
- db.commit()
299
- db.refresh(task_group)
300
- logger.warning(f"Created task group {task_group.id=}")
301
- print()
302
-
303
- return
304
-
305
-
306
- def fix_db():
307
- logger.warning("START execution of fix_db function")
308
- _check_current_version("2.7.0")
309
-
310
- with next(get_sync_db()) as db:
311
- user_mapping = get_users_mapping(db)
312
- default_user_id = get_default_user_id(db)
313
- default_user_group_id = get_default_user_group_id(db)
314
-
315
- prepare_task_groups(
316
- user_mapping=user_mapping,
317
- default_user_id=default_user_id,
318
- default_user_group_id=default_user_group_id,
319
- db=db,
320
- )
321
-
322
- logger.warning("END of execution of fix_db function")
323
- print()