fractal-server 2.17.0a4__py3-none-any.whl → 2.17.0a6__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 (67) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +22 -26
  3. fractal_server/app/models/security.py +19 -21
  4. fractal_server/app/models/user_settings.py +1 -0
  5. fractal_server/app/models/v2/task_group.py +1 -0
  6. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  7. fractal_server/app/routes/admin/v2/impersonate.py +2 -2
  8. fractal_server/app/routes/admin/v2/job.py +6 -6
  9. fractal_server/app/routes/admin/v2/profile.py +18 -4
  10. fractal_server/app/routes/admin/v2/project.py +2 -2
  11. fractal_server/app/routes/admin/v2/resource.py +8 -8
  12. fractal_server/app/routes/admin/v2/task.py +11 -2
  13. fractal_server/app/routes/admin/v2/task_group.py +16 -12
  14. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +4 -4
  15. fractal_server/app/routes/api/__init__.py +14 -5
  16. fractal_server/app/routes/api/v2/dataset.py +10 -19
  17. fractal_server/app/routes/api/v2/history.py +8 -8
  18. fractal_server/app/routes/api/v2/images.py +5 -5
  19. fractal_server/app/routes/api/v2/job.py +8 -8
  20. fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
  21. fractal_server/app/routes/api/v2/project.py +6 -6
  22. fractal_server/app/routes/api/v2/status_legacy.py +2 -2
  23. fractal_server/app/routes/api/v2/submit.py +25 -29
  24. fractal_server/app/routes/api/v2/task.py +6 -7
  25. fractal_server/app/routes/api/v2/task_collection.py +4 -3
  26. fractal_server/app/routes/api/v2/task_collection_custom.py +4 -3
  27. fractal_server/app/routes/api/v2/task_collection_pixi.py +2 -2
  28. fractal_server/app/routes/api/v2/task_group.py +6 -6
  29. fractal_server/app/routes/api/v2/task_group_lifecycle.py +4 -4
  30. fractal_server/app/routes/api/v2/task_version_update.py +3 -3
  31. fractal_server/app/routes/api/v2/workflow.py +9 -9
  32. fractal_server/app/routes/api/v2/workflow_import.py +2 -2
  33. fractal_server/app/routes/api/v2/workflowtask.py +5 -5
  34. fractal_server/app/routes/auth/__init__.py +34 -5
  35. fractal_server/app/routes/auth/current_user.py +22 -72
  36. fractal_server/app/routes/auth/group.py +8 -35
  37. fractal_server/app/routes/auth/oauth.py +1 -1
  38. fractal_server/app/routes/auth/register.py +2 -2
  39. fractal_server/app/routes/auth/users.py +6 -48
  40. fractal_server/app/schemas/__init__.py +0 -1
  41. fractal_server/app/schemas/user.py +23 -0
  42. fractal_server/app/schemas/v2/__init__.py +1 -0
  43. fractal_server/app/schemas/v2/task_group.py +5 -0
  44. fractal_server/app/security/__init__.py +134 -46
  45. fractal_server/app/security/signup_email.py +52 -34
  46. fractal_server/config/__init__.py +6 -0
  47. fractal_server/config/_data.py +68 -0
  48. fractal_server/config/_email.py +10 -47
  49. fractal_server/config/_main.py +3 -56
  50. fractal_server/config/_oauth.py +2 -2
  51. fractal_server/main.py +3 -2
  52. fractal_server/migrations/versions/f65ee53991e3_user_settings_related.py +67 -0
  53. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +1 -1
  54. fractal_server/runner/executors/slurm_common/slurm_config.py +5 -8
  55. fractal_server/runner/executors/slurm_ssh/runner.py +1 -1
  56. fractal_server/runner/executors/slurm_sudo/runner.py +1 -1
  57. fractal_server/runner/v2/_slurm_ssh.py +2 -1
  58. fractal_server/runner/v2/_slurm_sudo.py +1 -1
  59. fractal_server/runner/v2/submit_workflow.py +12 -12
  60. {fractal_server-2.17.0a4.dist-info → fractal_server-2.17.0a6.dist-info}/METADATA +4 -6
  61. {fractal_server-2.17.0a4.dist-info → fractal_server-2.17.0a6.dist-info}/RECORD +64 -65
  62. fractal_server/app/routes/aux/validate_user_settings.py +0 -76
  63. fractal_server/app/schemas/user_settings.py +0 -63
  64. fractal_server/app/user_settings.py +0 -32
  65. {fractal_server-2.17.0a4.dist-info → fractal_server-2.17.0a6.dist-info}/WHEEL +0 -0
  66. {fractal_server-2.17.0a4.dist-info → fractal_server-2.17.0a6.dist-info}/entry_points.txt +0 -0
  67. {fractal_server-2.17.0a4.dist-info → fractal_server-2.17.0a6.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __VERSION__ = "2.17.0a4"
1
+ __VERSION__ = "2.17.0a6"
@@ -74,13 +74,20 @@ init_db_data_parser.add_argument(
74
74
  init_db_data_parser.add_argument(
75
75
  "--admin-email",
76
76
  type=str,
77
- help="Email of the first admin user to create.",
77
+ help="Email of the first admin user.",
78
78
  required=False,
79
79
  )
80
80
  init_db_data_parser.add_argument(
81
81
  "--admin-pwd",
82
82
  type=str,
83
- help="Password of the first admin user to create.",
83
+ help="Password for the first admin user.",
84
+ required=False,
85
+ )
86
+
87
+ init_db_data_parser.add_argument(
88
+ "--admin-project-dir",
89
+ type=str,
90
+ help="Project_dir for the first admin user.",
84
91
  required=False,
85
92
  )
86
93
 
@@ -90,15 +97,6 @@ update_db_data_parser = subparsers.add_parser(
90
97
  description="Apply data-migration script to an existing database.",
91
98
  )
92
99
 
93
- # fractalctl encrypt-email-password
94
- encrypt_email_password_parser = subparsers.add_parser(
95
- "encrypt-email-password",
96
- description=(
97
- "Generate valid values for environment variables "
98
- "FRACTAL_EMAIL_PASSWORD and FRACTAL_EMAIL_PASSWORD_KEY."
99
- ),
100
- )
101
-
102
100
 
103
101
  def save_openapi(dest="openapi.json"):
104
102
  from fractal_server.main import start_application
@@ -136,10 +134,12 @@ def set_db():
136
134
 
137
135
 
138
136
  def init_db_data(
137
+ *,
139
138
  resource: str | None = None,
140
139
  profile: str | None = None,
141
140
  admin_email: str | None = None,
142
141
  admin_password: str | None = None,
142
+ admin_project_dir: str | None = None,
143
143
  ) -> None:
144
144
  from fractal_server.app.security import _create_first_user
145
145
  from fractal_server.app.security import _create_first_group
@@ -156,14 +156,22 @@ def init_db_data(
156
156
  print()
157
157
 
158
158
  # Create admin user if requested
159
- if (admin_email is None) != (admin_password is None):
160
- print("You must provide both --admin-email and --admin-pwd. Exit.")
159
+ if not (
160
+ (admin_email is None)
161
+ == (admin_password is None)
162
+ == (admin_project_dir is None)
163
+ ):
164
+ print(
165
+ "You must provide either or or none of `--admin-email`, "
166
+ "`--admin-pwd` and `--admin-project-dir`. Exit."
167
+ )
161
168
  sys.exit(1)
162
169
  if admin_password and admin_email:
163
170
  asyncio.run(
164
171
  _create_first_user(
165
172
  email=admin_email,
166
173
  password=admin_password,
174
+ project_dir=admin_project_dir,
167
175
  is_superuser=True,
168
176
  is_verified=True,
169
177
  )
@@ -325,17 +333,6 @@ def update_db_data():
325
333
  current_update_db_data_module.fix_db()
326
334
 
327
335
 
328
- def print_encrypted_password():
329
- from cryptography.fernet import Fernet
330
-
331
- password = input("Insert email password: ").encode("utf-8")
332
- key = Fernet.generate_key().decode("utf-8")
333
- encrypted_password = Fernet(key).encrypt(password).decode("utf-8")
334
-
335
- print(f"\nFRACTAL_EMAIL_PASSWORD={encrypted_password}")
336
- print(f"FRACTAL_EMAIL_PASSWORD_KEY={key}")
337
-
338
-
339
336
  def run():
340
337
  args = parser.parse_args(sys.argv[1:])
341
338
 
@@ -349,6 +346,7 @@ def run():
349
346
  profile=args.profile,
350
347
  admin_email=args.admin_email,
351
348
  admin_password=args.admin_pwd,
349
+ admin_project_dir=args.admin_project_dir,
352
350
  )
353
351
  elif args.cmd == "update-db-data":
354
352
  update_db_data()
@@ -359,8 +357,6 @@ def run():
359
357
  port=args.port,
360
358
  reload=args.reload,
361
359
  )
362
- elif args.cmd == "encrypt-email-password":
363
- print_encrypted_password()
364
360
  else:
365
361
  sys.exit(f"Error: invalid command '{args.cmd}'.")
366
362
 
@@ -1,27 +1,17 @@
1
- # This is based on fastapi_users_db_sqlmodel
2
- # <https://github.com/fastapi-users/fastapi-users-db-sqlmodel>
3
- # Original Copyright
4
- # Copyright 2022 François Voron
5
- # License: MIT
6
- #
7
- # Modified by:
8
- # Tommaso Comparin <tommaso.comparin@exact-lab.it>
9
- #
10
- # Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
11
- # University of Zurich
12
1
  from datetime import datetime
13
2
  from typing import Optional
14
3
 
15
4
  from pydantic import ConfigDict
16
5
  from pydantic import EmailStr
17
6
  from sqlalchemy import Column
7
+ from sqlalchemy import String
8
+ from sqlalchemy.dialects.postgresql import ARRAY
18
9
  from sqlalchemy.dialects.postgresql import JSONB
19
10
  from sqlalchemy.types import DateTime
20
11
  from sqlmodel import Field
21
12
  from sqlmodel import Relationship
22
13
  from sqlmodel import SQLModel
23
14
 
24
- from .user_settings import UserSettings
25
15
  from fractal_server.utils import get_timestamp
26
16
 
27
17
 
@@ -74,17 +64,20 @@ class UserOAuth(SQLModel, table=True):
74
64
  is_superuser:
75
65
  is_verified:
76
66
  oauth_accounts:
77
- user_settings_id:
78
67
  profile_id:
79
- settings:
68
+ project_dir:
69
+ slurm_accounts:
80
70
  """
81
71
 
72
+ model_config = ConfigDict(from_attributes=True)
73
+
82
74
  __tablename__ = "user_oauth"
83
75
 
84
76
  id: int | None = Field(default=None, primary_key=True)
85
77
 
86
78
  email: EmailStr = Field(
87
- sa_column_kwargs={"unique": True, "index": True}, nullable=False
79
+ sa_column_kwargs={"unique": True, "index": True},
80
+ nullable=False,
88
81
  )
89
82
  hashed_password: str
90
83
  is_active: bool = Field(default=True, nullable=False)
@@ -96,18 +89,23 @@ class UserOAuth(SQLModel, table=True):
96
89
  sa_relationship_kwargs={"lazy": "joined", "cascade": "all, delete"},
97
90
  )
98
91
 
99
- user_settings_id: int | None = Field(
100
- foreign_key="user_settings.id", default=None
101
- )
102
92
  profile_id: int | None = Field(
103
93
  foreign_key="profile.id",
104
94
  default=None,
105
95
  ondelete="SET NULL",
106
96
  )
107
- settings: UserSettings | None = Relationship(
108
- sa_relationship_kwargs=dict(lazy="selectin", cascade="all, delete")
97
+
98
+ # TODO-2.17.1: update to `project_dir: str`
99
+ project_dir: str = Field(
100
+ sa_column=Column(
101
+ String,
102
+ server_default="/PLACEHOLDER",
103
+ nullable=False,
104
+ )
105
+ )
106
+ slurm_accounts: list[str] = Field(
107
+ sa_column=Column(ARRAY(String), server_default="{}"),
109
108
  )
110
- model_config = ConfigDict(from_attributes=True)
111
109
 
112
110
 
113
111
  class UserGroup(SQLModel, table=True):
@@ -4,6 +4,7 @@ from sqlmodel import Field
4
4
  from sqlmodel import SQLModel
5
5
 
6
6
 
7
+ # TODO-2.17.1: Drop `UserSettings`
7
8
  class UserSettings(SQLModel, table=True):
8
9
  """
9
10
  Comprehensive list of user settings.
@@ -42,6 +42,7 @@ class TaskGroupV2(SQLModel, table=True):
42
42
  user_group_id: int | None = Field(
43
43
  foreign_key="usergroup.id", default=None, ondelete="SET NULL"
44
44
  )
45
+ # TODO-2.17.1: make `resource_id` not nullable
45
46
  resource_id: int | None = Field(
46
47
  foreign_key="resource.id", default=None, ondelete="SET NULL"
47
48
  )
@@ -13,7 +13,7 @@ from fractal_server.app.db import get_async_db
13
13
  from fractal_server.app.models import UserOAuth
14
14
  from fractal_server.app.models.v2 import AccountingRecord
15
15
  from fractal_server.app.models.v2 import AccountingRecordSlurm
16
- from fractal_server.app.routes.auth import current_active_superuser
16
+ from fractal_server.app.routes.auth import current_superuser_act
17
17
  from fractal_server.app.routes.pagination import get_pagination_params
18
18
  from fractal_server.app.routes.pagination import PaginationRequest
19
19
  from fractal_server.app.routes.pagination import PaginationResponse
@@ -34,7 +34,7 @@ async def query_accounting(
34
34
  query: AccountingQuery,
35
35
  # Dependencies
36
36
  pagination: PaginationRequest = Depends(get_pagination_params),
37
- superuser: UserOAuth = Depends(current_active_superuser),
37
+ superuser: UserOAuth = Depends(current_superuser_act),
38
38
  db: AsyncSession = Depends(get_async_db),
39
39
  ) -> PaginationResponse[AccountingRecordRead]:
40
40
  page = pagination.page
@@ -79,7 +79,7 @@ async def query_accounting(
79
79
  async def query_accounting_slurm(
80
80
  query: AccountingQuery,
81
81
  # dependencies
82
- superuser: UserOAuth = Depends(current_active_superuser),
82
+ superuser: UserOAuth = Depends(current_superuser_act),
83
83
  db: AsyncSession = Depends(get_async_db),
84
84
  ) -> JSONResponse:
85
85
  stm = select(AccountingRecordSlurm.slurm_job_ids)
@@ -6,7 +6,7 @@ from fastapi_users.authentication import JWTStrategy
6
6
  from fractal_server.app.db import AsyncSession
7
7
  from fractal_server.app.db import get_async_db
8
8
  from fractal_server.app.models import UserOAuth
9
- from fractal_server.app.routes.auth import current_active_superuser
9
+ from fractal_server.app.routes.auth import current_superuser_act
10
10
  from fractal_server.app.routes.auth._aux_auth import _user_or_404
11
11
  from fractal_server.config import get_settings
12
12
  from fractal_server.syringe import Inject
@@ -17,7 +17,7 @@ router = APIRouter()
17
17
  @router.get("/{user_id}/")
18
18
  async def impersonate_user(
19
19
  user_id: int,
20
- superuser: UserOAuth = Depends(current_active_superuser),
20
+ superuser: UserOAuth = Depends(current_superuser_act),
21
21
  db: AsyncSession = Depends(get_async_db),
22
22
  ) -> JSONResponse:
23
23
  user = await _user_or_404(user_id, db)
@@ -16,7 +16,7 @@ from fractal_server.app.models.v2 import HistoryRun
16
16
  from fractal_server.app.models.v2 import HistoryUnit
17
17
  from fractal_server.app.models.v2 import JobV2
18
18
  from fractal_server.app.models.v2 import ProjectV2
19
- from fractal_server.app.routes.auth import current_active_superuser
19
+ from fractal_server.app.routes.auth import current_superuser_act
20
20
  from fractal_server.app.routes.aux._job import _write_shutdown_file
21
21
  from fractal_server.app.routes.aux._runner import _check_shutdown_is_supported
22
22
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
@@ -43,7 +43,7 @@ async def view_job(
43
43
  end_timestamp_min: AwareDatetime | None = None,
44
44
  end_timestamp_max: AwareDatetime | None = None,
45
45
  log: bool = True,
46
- user: UserOAuth = Depends(current_active_superuser),
46
+ user: UserOAuth = Depends(current_superuser_act),
47
47
  db: AsyncSession = Depends(get_async_db),
48
48
  ) -> list[JobReadV2]:
49
49
  """
@@ -111,7 +111,7 @@ async def view_job(
111
111
  async def view_single_job(
112
112
  job_id: int,
113
113
  show_tmp_logs: bool = False,
114
- user: UserOAuth = Depends(current_active_superuser),
114
+ user: UserOAuth = Depends(current_superuser_act),
115
115
  db: AsyncSession = Depends(get_async_db),
116
116
  ) -> JobReadV2:
117
117
  job = await db.get(JobV2, job_id)
@@ -136,7 +136,7 @@ async def view_single_job(
136
136
  async def update_job(
137
137
  job_update: JobUpdateV2,
138
138
  job_id: int,
139
- user: UserOAuth = Depends(current_active_superuser),
139
+ user: UserOAuth = Depends(current_superuser_act),
140
140
  db: AsyncSession = Depends(get_async_db),
141
141
  ) -> JobReadV2 | None:
142
142
  """
@@ -200,7 +200,7 @@ async def update_job(
200
200
  @router.get("/{job_id}/stop/", status_code=202)
201
201
  async def stop_job(
202
202
  job_id: int,
203
- user: UserOAuth = Depends(current_active_superuser),
203
+ user: UserOAuth = Depends(current_superuser_act),
204
204
  db: AsyncSession = Depends(get_async_db),
205
205
  ) -> Response:
206
206
  """
@@ -224,7 +224,7 @@ async def stop_job(
224
224
  @router.get("/{job_id}/download/", response_class=StreamingResponse)
225
225
  async def download_job_logs(
226
226
  job_id: int,
227
- user: UserOAuth = Depends(current_active_superuser),
227
+ user: UserOAuth = Depends(current_superuser_act),
228
228
  db: AsyncSession = Depends(get_async_db),
229
229
  ) -> StreamingResponse:
230
230
  """
@@ -10,8 +10,9 @@ from ._aux_functions import _check_profile_name
10
10
  from ._aux_functions import _get_profile_or_404
11
11
  from fractal_server.app.db import AsyncSession
12
12
  from fractal_server.app.db import get_async_db
13
+ from fractal_server.app.models import Profile
13
14
  from fractal_server.app.models import UserOAuth
14
- from fractal_server.app.routes.auth import current_active_superuser
15
+ from fractal_server.app.routes.auth import current_superuser_act
15
16
  from fractal_server.app.schemas.v2 import ProfileCreate
16
17
  from fractal_server.app.schemas.v2 import ProfileRead
17
18
 
@@ -21,7 +22,7 @@ router = APIRouter()
21
22
  @router.get("/{profile_id}/", response_model=ProfileRead, status_code=200)
22
23
  async def get_single_profile(
23
24
  profile_id: int,
24
- superuser: UserOAuth = Depends(current_active_superuser),
25
+ superuser: UserOAuth = Depends(current_superuser_act),
25
26
  db: AsyncSession = Depends(get_async_db),
26
27
  ) -> ProfileRead:
27
28
  """
@@ -31,11 +32,24 @@ async def get_single_profile(
31
32
  return profile
32
33
 
33
34
 
35
+ @router.get("/", response_model=list[ProfileRead], status_code=200)
36
+ async def get_profile_list(
37
+ superuser: UserOAuth = Depends(current_superuser_act),
38
+ db: AsyncSession = Depends(get_async_db),
39
+ ) -> ProfileRead:
40
+ """
41
+ Query single `Profile`.
42
+ """
43
+ res = await db.execute(select(Profile).order_by(Profile.id))
44
+ profiles = res.scalars().all()
45
+ return profiles
46
+
47
+
34
48
  @router.put("/{profile_id}/", response_model=ProfileRead, status_code=200)
35
49
  async def put_profile(
36
50
  profile_id: int,
37
51
  profile_update: ProfileCreate,
38
- superuser: UserOAuth = Depends(current_active_superuser),
52
+ superuser: UserOAuth = Depends(current_superuser_act),
39
53
  db: AsyncSession = Depends(get_async_db),
40
54
  ) -> ProfileRead:
41
55
  """
@@ -56,7 +70,7 @@ async def put_profile(
56
70
  @router.delete("/{profile_id}/", status_code=204)
57
71
  async def delete_profile(
58
72
  profile_id: int,
59
- superuser: UserOAuth = Depends(current_active_superuser),
73
+ superuser: UserOAuth = Depends(current_superuser_act),
60
74
  db: AsyncSession = Depends(get_async_db),
61
75
  ):
62
76
  """
@@ -6,7 +6,7 @@ from fractal_server.app.db import AsyncSession
6
6
  from fractal_server.app.db import get_async_db
7
7
  from fractal_server.app.models import UserOAuth
8
8
  from fractal_server.app.models.v2 import ProjectV2
9
- from fractal_server.app.routes.auth import current_active_superuser
9
+ from fractal_server.app.routes.auth import current_superuser_act
10
10
  from fractal_server.app.schemas.v2 import ProjectReadV2
11
11
 
12
12
  router = APIRouter()
@@ -16,7 +16,7 @@ router = APIRouter()
16
16
  async def view_project(
17
17
  id: int | None = None,
18
18
  user_id: int | None = None,
19
- user: UserOAuth = Depends(current_active_superuser),
19
+ user: UserOAuth = Depends(current_superuser_act),
20
20
  db: AsyncSession = Depends(get_async_db),
21
21
  ) -> list[ProjectReadV2]:
22
22
  """
@@ -16,7 +16,7 @@ from fractal_server.app.models.v2 import Profile
16
16
  from fractal_server.app.models.v2 import ProjectV2
17
17
  from fractal_server.app.models.v2 import Resource
18
18
  from fractal_server.app.models.v2 import TaskGroupV2
19
- from fractal_server.app.routes.auth import current_active_superuser
19
+ from fractal_server.app.routes.auth import current_superuser_act
20
20
  from fractal_server.app.schemas.v2 import ProfileCreate
21
21
  from fractal_server.app.schemas.v2 import ProfileRead
22
22
  from fractal_server.app.schemas.v2 import ResourceCreate
@@ -57,7 +57,7 @@ def _check_type_match_or_422(new_resource: ResourceCreate) -> None:
57
57
 
58
58
  @router.get("/", response_model=list[ResourceRead], status_code=200)
59
59
  async def get_resource_list(
60
- superuser: UserOAuth = Depends(current_active_superuser),
60
+ superuser: UserOAuth = Depends(current_superuser_act),
61
61
  db: AsyncSession = Depends(get_async_db),
62
62
  ) -> list[ResourceRead]:
63
63
  """
@@ -74,7 +74,7 @@ async def get_resource_list(
74
74
  @router.get("/{resource_id}/", response_model=ResourceRead, status_code=200)
75
75
  async def get_resource(
76
76
  resource_id: int,
77
- superuser: UserOAuth = Depends(current_active_superuser),
77
+ superuser: UserOAuth = Depends(current_superuser_act),
78
78
  db: AsyncSession = Depends(get_async_db),
79
79
  ) -> ResourceRead:
80
80
  """
@@ -88,7 +88,7 @@ async def get_resource(
88
88
  @router.post("/", response_model=ResourceRead, status_code=201)
89
89
  async def post_resource(
90
90
  resource_create: ResourceCreate,
91
- superuser: UserOAuth = Depends(current_active_superuser),
91
+ superuser: UserOAuth = Depends(current_superuser_act),
92
92
  db: AsyncSession = Depends(get_async_db),
93
93
  ) -> ResourceRead:
94
94
  """
@@ -116,7 +116,7 @@ async def post_resource(
116
116
  async def put_resource(
117
117
  resource_id: int,
118
118
  resource_update: ResourceCreate,
119
- superuser: UserOAuth = Depends(current_active_superuser),
119
+ superuser: UserOAuth = Depends(current_superuser_act),
120
120
  db: AsyncSession = Depends(get_async_db),
121
121
  ) -> ResourceRead:
122
122
  """
@@ -144,7 +144,7 @@ async def put_resource(
144
144
  @router.delete("/{resource_id}/", status_code=204)
145
145
  async def delete_resource(
146
146
  resource_id: int,
147
- superuser: UserOAuth = Depends(current_active_superuser),
147
+ superuser: UserOAuth = Depends(current_superuser_act),
148
148
  db: AsyncSession = Depends(get_async_db),
149
149
  ):
150
150
  """
@@ -214,7 +214,7 @@ async def delete_resource(
214
214
  )
215
215
  async def get_resource_profiles(
216
216
  resource_id: int,
217
- superuser: UserOAuth = Depends(current_active_superuser),
217
+ superuser: UserOAuth = Depends(current_superuser_act),
218
218
  db: AsyncSession = Depends(get_async_db),
219
219
  ) -> list[ProfileRead]:
220
220
  """
@@ -238,7 +238,7 @@ async def get_resource_profiles(
238
238
  async def post_profile(
239
239
  resource_id: int,
240
240
  profile_create: ProfileCreate,
241
- superuser: UserOAuth = Depends(current_active_superuser),
241
+ superuser: UserOAuth = Depends(current_superuser_act),
242
242
  db: AsyncSession = Depends(get_async_db),
243
243
  ) -> ProfileRead:
244
244
  """
@@ -10,11 +10,12 @@ from sqlmodel import select
10
10
 
11
11
  from fractal_server.app.db import AsyncSession
12
12
  from fractal_server.app.db import get_async_db
13
+ from fractal_server.app.models import TaskGroupV2
13
14
  from fractal_server.app.models import UserOAuth
14
15
  from fractal_server.app.models.v2 import TaskV2
15
16
  from fractal_server.app.models.v2 import WorkflowTaskV2
16
17
  from fractal_server.app.models.v2 import WorkflowV2
17
- from fractal_server.app.routes.auth import current_active_superuser
18
+ from fractal_server.app.routes.auth import current_superuser_act
18
19
 
19
20
  router = APIRouter()
20
21
 
@@ -58,7 +59,8 @@ async def query_tasks(
58
59
  category: str | None = None,
59
60
  modality: str | None = None,
60
61
  author: str | None = None,
61
- user: UserOAuth = Depends(current_active_superuser),
62
+ resource_id: int | None = None,
63
+ user: UserOAuth = Depends(current_superuser_act),
62
64
  db: AsyncSession = Depends(get_async_db),
63
65
  ) -> list[TaskV2Info]:
64
66
  """
@@ -75,6 +77,7 @@ async def query_tasks(
75
77
  category:
76
78
  modality:
77
79
  author:
80
+ resource_id:
78
81
  """
79
82
 
80
83
  stm = select(TaskV2)
@@ -93,6 +96,12 @@ async def query_tasks(
93
96
  stm = stm.where(func.lower(TaskV2.modality) == modality.lower())
94
97
  if author is not None:
95
98
  stm = stm.where(TaskV2.authors.icontains(author))
99
+ if resource_id is not None:
100
+ stm = (
101
+ stm.join(TaskGroupV2)
102
+ .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
103
+ .where(TaskGroupV2.resource_id == resource_id)
104
+ )
96
105
 
97
106
  res = await db.execute(stm)
98
107
  task_list = res.scalars().all()
@@ -12,18 +12,19 @@ from fractal_server.app.db import get_async_db
12
12
  from fractal_server.app.models import UserOAuth
13
13
  from fractal_server.app.models.v2 import TaskGroupActivityV2
14
14
  from fractal_server.app.models.v2 import TaskGroupV2
15
- from fractal_server.app.routes.auth import current_active_superuser
15
+ from fractal_server.app.routes.auth import current_superuser_act
16
16
  from fractal_server.app.routes.auth._aux_auth import (
17
17
  _verify_user_belongs_to_group,
18
18
  )
19
19
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
20
20
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
21
21
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
22
- from fractal_server.app.schemas.v2 import TaskGroupReadV2
22
+ from fractal_server.app.schemas.v2 import TaskGroupReadSuperuser
23
23
  from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
24
24
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
25
25
  from fractal_server.logger import set_logger
26
26
 
27
+
27
28
  router = APIRouter()
28
29
 
29
30
  logger = set_logger(__name__)
@@ -38,7 +39,7 @@ async def get_task_group_activity_list(
38
39
  status: TaskGroupActivityStatusV2 | None = None,
39
40
  action: TaskGroupActivityActionV2 | None = None,
40
41
  timestamp_started_min: AwareDatetime | None = None,
41
- superuser: UserOAuth = Depends(current_active_superuser),
42
+ superuser: UserOAuth = Depends(current_superuser_act),
42
43
  db: AsyncSession = Depends(get_async_db),
43
44
  ) -> list[TaskGroupActivityV2Read]:
44
45
  stm = select(TaskGroupActivityV2)
@@ -64,12 +65,12 @@ async def get_task_group_activity_list(
64
65
  return activities
65
66
 
66
67
 
67
- @router.get("/{task_group_id}/", response_model=TaskGroupReadV2)
68
+ @router.get("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
68
69
  async def query_task_group(
69
70
  task_group_id: int,
70
- user: UserOAuth = Depends(current_active_superuser),
71
+ user: UserOAuth = Depends(current_superuser_act),
71
72
  db: AsyncSession = Depends(get_async_db),
72
- ) -> TaskGroupReadV2:
73
+ ) -> TaskGroupReadSuperuser:
73
74
  task_group = await db.get(TaskGroupV2, task_group_id)
74
75
  if task_group is None:
75
76
  raise HTTPException(
@@ -79,7 +80,7 @@ async def query_task_group(
79
80
  return task_group
80
81
 
81
82
 
82
- @router.get("/", response_model=list[TaskGroupReadV2])
83
+ @router.get("/", response_model=list[TaskGroupReadSuperuser])
83
84
  async def query_task_group_list(
84
85
  user_id: int | None = None,
85
86
  user_group_id: int | None = None,
@@ -89,9 +90,10 @@ async def query_task_group_list(
89
90
  origin: TaskGroupV2OriginEnum | None = None,
90
91
  timestamp_last_used_min: AwareDatetime | None = None,
91
92
  timestamp_last_used_max: AwareDatetime | None = None,
92
- user: UserOAuth = Depends(current_active_superuser),
93
+ resource_id: int | None = None,
94
+ user: UserOAuth = Depends(current_superuser_act),
93
95
  db: AsyncSession = Depends(get_async_db),
94
- ) -> list[TaskGroupReadV2]:
96
+ ) -> list[TaskGroupReadSuperuser]:
95
97
  stm = select(TaskGroupV2)
96
98
 
97
99
  if user_group_id is not None and private is True:
@@ -128,19 +130,21 @@ async def query_task_group_list(
128
130
  stm = stm.where(
129
131
  TaskGroupV2.timestamp_last_used <= timestamp_last_used_max
130
132
  )
133
+ if resource_id is not None:
134
+ stm = stm.where(TaskGroupV2.resource_id == resource_id)
131
135
 
132
136
  res = await db.execute(stm)
133
137
  task_groups_list = res.scalars().all()
134
138
  return task_groups_list
135
139
 
136
140
 
137
- @router.patch("/{task_group_id}/", response_model=TaskGroupReadV2)
141
+ @router.patch("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
138
142
  async def patch_task_group(
139
143
  task_group_id: int,
140
144
  task_group_update: TaskGroupUpdateV2,
141
- user: UserOAuth = Depends(current_active_superuser),
145
+ user: UserOAuth = Depends(current_superuser_act),
142
146
  db: AsyncSession = Depends(get_async_db),
143
- ) -> list[TaskGroupReadV2]:
147
+ ) -> list[TaskGroupReadSuperuser]:
144
148
  task_group = await db.get(TaskGroupV2, task_group_id)
145
149
  if task_group is None:
146
150
  raise HTTPException(
@@ -21,7 +21,7 @@ from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
21
21
  from fractal_server.app.routes.api.v2._aux_functions_tasks import (
22
22
  _get_task_group_or_404,
23
23
  )
24
- from fractal_server.app.routes.auth import current_active_superuser
24
+ from fractal_server.app.routes.auth import current_superuser_act
25
25
  from fractal_server.app.routes.aux.validate_user_profile import (
26
26
  validate_user_profile,
27
27
  )
@@ -52,7 +52,7 @@ async def deactivate_task_group(
52
52
  task_group_id: int,
53
53
  background_tasks: BackgroundTasks,
54
54
  response: Response,
55
- superuser: UserOAuth = Depends(current_active_superuser),
55
+ superuser: UserOAuth = Depends(current_superuser_act),
56
56
  db: AsyncSession = Depends(get_async_db),
57
57
  ) -> TaskGroupActivityV2Read:
58
58
  """
@@ -146,7 +146,7 @@ async def reactivate_task_group(
146
146
  task_group_id: int,
147
147
  background_tasks: BackgroundTasks,
148
148
  response: Response,
149
- superuser: UserOAuth = Depends(current_active_superuser),
149
+ superuser: UserOAuth = Depends(current_superuser_act),
150
150
  db: AsyncSession = Depends(get_async_db),
151
151
  ) -> TaskGroupActivityV2Read:
152
152
  """
@@ -247,7 +247,7 @@ async def delete_task_group(
247
247
  task_group_id: int,
248
248
  background_tasks: BackgroundTasks,
249
249
  response: Response,
250
- superuser: UserOAuth = Depends(current_active_superuser),
250
+ superuser: UserOAuth = Depends(current_superuser_act),
251
251
  db: AsyncSession = Depends(get_async_db),
252
252
  ):
253
253
  task_group = await _get_task_group_or_404(