fractal-server 2.16.6__py3-none-any.whl → 2.17.0__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.
- fractal_server/__init__.py +1 -1
- fractal_server/__main__.py +178 -52
- fractal_server/app/db/__init__.py +9 -11
- fractal_server/app/models/security.py +30 -22
- fractal_server/app/models/user_settings.py +5 -4
- fractal_server/app/models/v2/__init__.py +4 -0
- fractal_server/app/models/v2/profile.py +16 -0
- fractal_server/app/models/v2/project.py +5 -0
- fractal_server/app/models/v2/resource.py +130 -0
- fractal_server/app/models/v2/task_group.py +4 -0
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
- fractal_server/app/routes/admin/v2/accounting.py +3 -3
- fractal_server/app/routes/admin/v2/impersonate.py +2 -2
- fractal_server/app/routes/admin/v2/job.py +51 -15
- fractal_server/app/routes/admin/v2/profile.py +100 -0
- fractal_server/app/routes/admin/v2/project.py +2 -2
- fractal_server/app/routes/admin/v2/resource.py +222 -0
- fractal_server/app/routes/admin/v2/task.py +59 -32
- fractal_server/app/routes/admin/v2/task_group.py +17 -12
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +52 -86
- fractal_server/app/routes/api/__init__.py +45 -8
- fractal_server/app/routes/api/v2/_aux_functions.py +17 -1
- fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +55 -19
- fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +21 -17
- fractal_server/app/routes/api/v2/dataset.py +10 -19
- fractal_server/app/routes/api/v2/history.py +8 -8
- fractal_server/app/routes/api/v2/images.py +5 -5
- fractal_server/app/routes/api/v2/job.py +8 -8
- fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
- fractal_server/app/routes/api/v2/project.py +15 -7
- fractal_server/app/routes/api/v2/status_legacy.py +2 -2
- fractal_server/app/routes/api/v2/submit.py +49 -42
- fractal_server/app/routes/api/v2/task.py +26 -8
- fractal_server/app/routes/api/v2/task_collection.py +39 -50
- fractal_server/app/routes/api/v2/task_collection_custom.py +10 -6
- fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -42
- fractal_server/app/routes/api/v2/task_group.py +19 -9
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +43 -86
- fractal_server/app/routes/api/v2/task_version_update.py +3 -3
- fractal_server/app/routes/api/v2/workflow.py +9 -9
- fractal_server/app/routes/api/v2/workflow_import.py +25 -13
- fractal_server/app/routes/api/v2/workflowtask.py +5 -5
- fractal_server/app/routes/auth/__init__.py +34 -5
- fractal_server/app/routes/auth/_aux_auth.py +39 -20
- fractal_server/app/routes/auth/current_user.py +56 -67
- fractal_server/app/routes/auth/group.py +29 -46
- fractal_server/app/routes/auth/oauth.py +55 -38
- fractal_server/app/routes/auth/register.py +2 -2
- fractal_server/app/routes/auth/router.py +4 -2
- fractal_server/app/routes/auth/users.py +29 -53
- fractal_server/app/routes/aux/_runner.py +2 -1
- fractal_server/app/routes/aux/validate_user_profile.py +62 -0
- fractal_server/app/schemas/__init__.py +0 -1
- fractal_server/app/schemas/user.py +43 -13
- fractal_server/app/schemas/user_group.py +2 -1
- fractal_server/app/schemas/v2/__init__.py +12 -0
- fractal_server/app/schemas/v2/profile.py +78 -0
- fractal_server/app/schemas/v2/resource.py +137 -0
- fractal_server/app/schemas/v2/task_collection.py +11 -3
- fractal_server/app/schemas/v2/task_group.py +5 -0
- fractal_server/app/security/__init__.py +174 -75
- fractal_server/app/security/signup_email.py +52 -34
- fractal_server/config/__init__.py +27 -0
- fractal_server/config/_data.py +68 -0
- fractal_server/config/_database.py +59 -0
- fractal_server/config/_email.py +133 -0
- fractal_server/config/_main.py +78 -0
- fractal_server/config/_oauth.py +69 -0
- fractal_server/config/_settings_config.py +7 -0
- fractal_server/data_migrations/2_17_0.py +339 -0
- fractal_server/images/tools.py +3 -3
- fractal_server/logger.py +3 -3
- fractal_server/main.py +17 -23
- fractal_server/migrations/naming_convention.py +1 -1
- fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +195 -0
- fractal_server/runner/config/__init__.py +2 -0
- fractal_server/runner/config/_local.py +21 -0
- fractal_server/runner/config/_slurm.py +129 -0
- fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
- fractal_server/runner/exceptions.py +4 -0
- fractal_server/runner/executors/base_runner.py +17 -7
- fractal_server/runner/executors/local/get_local_config.py +21 -86
- fractal_server/runner/executors/local/runner.py +48 -5
- fractal_server/runner/executors/slurm_common/_batching.py +2 -2
- fractal_server/runner/executors/slurm_common/base_slurm_runner.py +60 -26
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +39 -55
- fractal_server/runner/executors/slurm_common/remote.py +1 -1
- fractal_server/runner/executors/slurm_common/slurm_config.py +214 -0
- fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
- fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
- fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
- fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
- fractal_server/runner/v2/_local.py +36 -21
- fractal_server/runner/v2/_slurm_ssh.py +41 -4
- fractal_server/runner/v2/_slurm_sudo.py +42 -12
- fractal_server/runner/v2/db_tools.py +1 -1
- fractal_server/runner/v2/runner.py +3 -11
- fractal_server/runner/v2/runner_functions.py +42 -28
- fractal_server/runner/v2/submit_workflow.py +88 -109
- fractal_server/runner/versions.py +8 -3
- fractal_server/ssh/_fabric.py +6 -6
- fractal_server/tasks/config/__init__.py +3 -0
- fractal_server/tasks/config/_pixi.py +127 -0
- fractal_server/tasks/config/_python.py +51 -0
- fractal_server/tasks/v2/local/_utils.py +7 -7
- fractal_server/tasks/v2/local/collect.py +13 -5
- fractal_server/tasks/v2/local/collect_pixi.py +26 -10
- fractal_server/tasks/v2/local/deactivate.py +7 -1
- fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
- fractal_server/tasks/v2/local/delete.py +5 -1
- fractal_server/tasks/v2/local/reactivate.py +13 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
- fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
- fractal_server/tasks/v2/ssh/_utils.py +6 -7
- fractal_server/tasks/v2/ssh/collect.py +19 -12
- fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
- fractal_server/tasks/v2/ssh/deactivate.py +12 -8
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
- fractal_server/tasks/v2/ssh/delete.py +12 -9
- fractal_server/tasks/v2/ssh/reactivate.py +18 -12
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
- fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
- fractal_server/tasks/v2/utils_database.py +2 -2
- fractal_server/tasks/v2/utils_pixi.py +3 -0
- fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
- fractal_server/tasks/v2/utils_templates.py +7 -10
- fractal_server/utils.py +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/METADATA +4 -6
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/RECORD +136 -117
- fractal_server/app/routes/aux/validate_user_settings.py +0 -73
- fractal_server/app/schemas/user_settings.py +0 -67
- fractal_server/app/user_settings.py +0 -42
- fractal_server/config.py +0 -906
- fractal_server/data_migrations/2_14_10.py +0 -48
- fractal_server/runner/executors/slurm_common/_slurm_config.py +0 -471
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,11 +12,17 @@ from fractal_server.app.db import AsyncSession
|
|
|
12
12
|
from fractal_server.app.models import LinkUserGroup
|
|
13
13
|
from fractal_server.app.models import UserGroup
|
|
14
14
|
from fractal_server.app.models import UserOAuth
|
|
15
|
+
from fractal_server.app.models.v2 import Profile
|
|
15
16
|
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
|
16
17
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
17
18
|
from fractal_server.app.models.v2 import TaskV2
|
|
18
19
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
|
19
|
-
from fractal_server.app.routes.
|
|
20
|
+
from fractal_server.app.routes.api.v2._aux_functions import (
|
|
21
|
+
_get_user_resource_id,
|
|
22
|
+
)
|
|
23
|
+
from fractal_server.app.routes.auth._aux_auth import (
|
|
24
|
+
_get_default_usergroup_id_or_none,
|
|
25
|
+
)
|
|
20
26
|
from fractal_server.app.routes.auth._aux_auth import (
|
|
21
27
|
_verify_user_belongs_to_group,
|
|
22
28
|
)
|
|
@@ -33,7 +39,7 @@ async def _get_task_group_or_404(
|
|
|
33
39
|
"""
|
|
34
40
|
Get an existing task group or raise a 404.
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
Args:
|
|
37
43
|
task_group_id: The TaskGroupV2 id
|
|
38
44
|
db: An asynchronous db session
|
|
39
45
|
"""
|
|
@@ -55,7 +61,7 @@ async def _get_task_group_read_access(
|
|
|
55
61
|
"""
|
|
56
62
|
Get a task group or raise a 403 if user has no read access.
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
Args:
|
|
59
65
|
task_group_id: ID of the required task group.
|
|
60
66
|
user_id: ID of the current user.
|
|
61
67
|
db: An asynchronous db session.
|
|
@@ -69,7 +75,7 @@ async def _get_task_group_read_access(
|
|
|
69
75
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
70
76
|
detail=(
|
|
71
77
|
"Current user has no read access to TaskGroupV2 "
|
|
72
|
-
f"{task_group_id}."
|
|
78
|
+
f"{task_group_id}."
|
|
73
79
|
),
|
|
74
80
|
)
|
|
75
81
|
|
|
@@ -80,11 +86,16 @@ async def _get_task_group_read_access(
|
|
|
80
86
|
else:
|
|
81
87
|
stm = (
|
|
82
88
|
select(LinkUserGroup)
|
|
89
|
+
.join(UserOAuth)
|
|
90
|
+
.join(Profile)
|
|
83
91
|
.where(LinkUserGroup.group_id == task_group.user_group_id)
|
|
84
92
|
.where(LinkUserGroup.user_id == user_id)
|
|
93
|
+
.where(UserOAuth.id == user_id)
|
|
94
|
+
.where(Profile.id == UserOAuth.profile_id)
|
|
95
|
+
.where(task_group.resource_id == Profile.resource_id)
|
|
85
96
|
)
|
|
86
97
|
res = await db.execute(stm)
|
|
87
|
-
link = res.
|
|
98
|
+
link = res.unique().scalars().one_or_none()
|
|
88
99
|
if link is None:
|
|
89
100
|
raise forbidden_exception
|
|
90
101
|
else:
|
|
@@ -100,7 +111,7 @@ async def _get_task_group_full_access(
|
|
|
100
111
|
"""
|
|
101
112
|
Get a task group or raise a 403 if user has no full access.
|
|
102
113
|
|
|
103
|
-
|
|
114
|
+
Args:
|
|
104
115
|
task_group_id: ID of the required task group.
|
|
105
116
|
user_id: ID of the current user.
|
|
106
117
|
db: An asynchronous db session
|
|
@@ -125,7 +136,7 @@ async def _get_task_or_404(*, task_id: int, db: AsyncSession) -> TaskV2:
|
|
|
125
136
|
"""
|
|
126
137
|
Get an existing task or raise a 404.
|
|
127
138
|
|
|
128
|
-
|
|
139
|
+
Args:
|
|
129
140
|
task_id: ID of the required task.
|
|
130
141
|
db: An asynchronous db session
|
|
131
142
|
"""
|
|
@@ -147,15 +158,23 @@ async def _get_task_full_access(
|
|
|
147
158
|
"""
|
|
148
159
|
Get an existing task or raise a 404.
|
|
149
160
|
|
|
150
|
-
|
|
161
|
+
Args:
|
|
151
162
|
task_id: ID of the required task.
|
|
152
163
|
user_id: ID of the current user.
|
|
153
164
|
db: An asynchronous db session.
|
|
154
165
|
"""
|
|
155
166
|
task = await _get_task_or_404(task_id=task_id, db=db)
|
|
156
|
-
await _get_task_group_full_access(
|
|
167
|
+
task_group = await _get_task_group_full_access(
|
|
157
168
|
task_group_id=task.taskgroupv2_id, user_id=user_id, db=db
|
|
158
169
|
)
|
|
170
|
+
|
|
171
|
+
resource_id = await _get_user_resource_id(user_id=user_id, db=db)
|
|
172
|
+
if resource_id is None or resource_id != task_group.resource_id:
|
|
173
|
+
raise HTTPException(
|
|
174
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
175
|
+
detail=f"User {user_id} has no access to TaskGroup's Resource.",
|
|
176
|
+
)
|
|
177
|
+
|
|
159
178
|
return task
|
|
160
179
|
|
|
161
180
|
|
|
@@ -169,7 +188,7 @@ async def _get_task_read_access(
|
|
|
169
188
|
"""
|
|
170
189
|
Get an existing task or raise a 404.
|
|
171
190
|
|
|
172
|
-
|
|
191
|
+
Args:
|
|
173
192
|
task_id: ID of the required task.
|
|
174
193
|
user_id: ID of the current user.
|
|
175
194
|
db: An asynchronous db session.
|
|
@@ -179,12 +198,20 @@ async def _get_task_read_access(
|
|
|
179
198
|
task_group = await _get_task_group_read_access(
|
|
180
199
|
task_group_id=task.taskgroupv2_id, user_id=user_id, db=db
|
|
181
200
|
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
201
|
+
|
|
202
|
+
resource_id = await _get_user_resource_id(user_id=user_id, db=db)
|
|
203
|
+
if resource_id is None or resource_id != task_group.resource_id:
|
|
204
|
+
raise HTTPException(
|
|
205
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
206
|
+
detail=f"User {user_id} has no access to TaskGroup's Resource.",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if require_active and not task_group.active:
|
|
210
|
+
raise HTTPException(
|
|
211
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
212
|
+
detail=f"Error: task {task_id} ({task.name}) is not active.",
|
|
213
|
+
)
|
|
214
|
+
|
|
188
215
|
return task
|
|
189
216
|
|
|
190
217
|
|
|
@@ -198,7 +225,7 @@ async def _get_valid_user_group_id(
|
|
|
198
225
|
"""
|
|
199
226
|
Validate query parameters for endpoints that create some task(s).
|
|
200
227
|
|
|
201
|
-
|
|
228
|
+
Args:
|
|
202
229
|
user_group_id:
|
|
203
230
|
private:
|
|
204
231
|
user_id: ID of the current user
|
|
@@ -212,7 +239,7 @@ async def _get_valid_user_group_id(
|
|
|
212
239
|
elif private is True:
|
|
213
240
|
user_group_id = None
|
|
214
241
|
elif user_group_id is None:
|
|
215
|
-
user_group_id = await
|
|
242
|
+
user_group_id = await _get_default_usergroup_id_or_none(db=db)
|
|
216
243
|
else:
|
|
217
244
|
await _verify_user_belongs_to_group(
|
|
218
245
|
user_id=user_id, user_group_id=user_group_id, db=db
|
|
@@ -255,16 +282,19 @@ async def _get_collection_task_group_activity_status_message(
|
|
|
255
282
|
|
|
256
283
|
|
|
257
284
|
async def _verify_non_duplication_user_constraint(
|
|
285
|
+
*,
|
|
258
286
|
db: AsyncSession,
|
|
259
287
|
user_id: int,
|
|
260
288
|
pkg_name: str,
|
|
261
289
|
version: str | None,
|
|
290
|
+
user_resource_id: int,
|
|
262
291
|
):
|
|
263
292
|
stm = (
|
|
264
293
|
select(TaskGroupV2)
|
|
265
294
|
.where(TaskGroupV2.user_id == user_id)
|
|
266
295
|
.where(TaskGroupV2.pkg_name == pkg_name)
|
|
267
296
|
.where(TaskGroupV2.version == version)
|
|
297
|
+
.where(TaskGroupV2.resource_id == user_resource_id)
|
|
268
298
|
)
|
|
269
299
|
res = await db.execute(stm)
|
|
270
300
|
duplicate = res.scalars().all()
|
|
@@ -342,7 +372,9 @@ async def _verify_non_duplication_group_constraint(
|
|
|
342
372
|
|
|
343
373
|
|
|
344
374
|
async def _verify_non_duplication_group_path(
|
|
375
|
+
*,
|
|
345
376
|
path: str | None,
|
|
377
|
+
resource_id: int,
|
|
346
378
|
db: AsyncSession,
|
|
347
379
|
) -> None:
|
|
348
380
|
"""
|
|
@@ -350,7 +382,11 @@ async def _verify_non_duplication_group_path(
|
|
|
350
382
|
"""
|
|
351
383
|
if path is None:
|
|
352
384
|
return
|
|
353
|
-
stm =
|
|
385
|
+
stm = (
|
|
386
|
+
select(TaskGroupV2.id)
|
|
387
|
+
.where(TaskGroupV2.path == path)
|
|
388
|
+
.where(TaskGroupV2.resource_id == resource_id)
|
|
389
|
+
)
|
|
354
390
|
res = await db.execute(stm)
|
|
355
391
|
duplicate_ids = res.scalars().all()
|
|
356
392
|
if duplicate_ids:
|
|
@@ -5,8 +5,10 @@ from sqlmodel import select
|
|
|
5
5
|
from fractal_server.app.db import AsyncSession
|
|
6
6
|
from fractal_server.app.models import LinkUserGroup
|
|
7
7
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
8
|
+
from fractal_server.config import get_settings
|
|
8
9
|
from fractal_server.exceptions import UnreachableBranchError
|
|
9
10
|
from fractal_server.logger import set_logger
|
|
11
|
+
from fractal_server.syringe import Inject
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
logger = set_logger(__name__)
|
|
@@ -16,7 +18,7 @@ async def _disambiguate_task_groups(
|
|
|
16
18
|
*,
|
|
17
19
|
matching_task_groups: list[TaskGroupV2],
|
|
18
20
|
user_id: int,
|
|
19
|
-
default_group_id: int,
|
|
21
|
+
default_group_id: int | None,
|
|
20
22
|
db: AsyncSession,
|
|
21
23
|
) -> TaskGroupV2 | None:
|
|
22
24
|
"""
|
|
@@ -49,21 +51,23 @@ async def _disambiguate_task_groups(
|
|
|
49
51
|
)
|
|
50
52
|
|
|
51
53
|
# Medium priority: task groups owned by default user group
|
|
54
|
+
settings = Inject(get_settings)
|
|
52
55
|
list_user_group_ids = [tg.user_group_id for tg in matching_task_groups]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
if settings.FRACTAL_DEFAULT_GROUP_NAME is not None:
|
|
57
|
+
try:
|
|
58
|
+
ind_user_group_id = list_user_group_ids.index(default_group_id)
|
|
59
|
+
task_group = matching_task_groups[ind_user_group_id]
|
|
60
|
+
logger.debug(
|
|
61
|
+
"[_disambiguate_task_groups] "
|
|
62
|
+
f"Found task group {task_group.id} with {user_id=}, return."
|
|
63
|
+
)
|
|
64
|
+
return task_group
|
|
65
|
+
except ValueError:
|
|
66
|
+
logger.debug(
|
|
67
|
+
"[_disambiguate_task_groups] "
|
|
68
|
+
"No task group with user_group_id="
|
|
69
|
+
f"{default_group_id}, continue."
|
|
70
|
+
)
|
|
67
71
|
|
|
68
72
|
# Lowest priority: task groups owned by other groups, sorted
|
|
69
73
|
# according to age of the user/usergroup link
|
|
@@ -97,7 +101,7 @@ async def _disambiguate_task_groups_not_none(
|
|
|
97
101
|
*,
|
|
98
102
|
matching_task_groups: list[TaskGroupV2],
|
|
99
103
|
user_id: int,
|
|
100
|
-
default_group_id: int,
|
|
104
|
+
default_group_id: int | None,
|
|
101
105
|
db: AsyncSession,
|
|
102
106
|
) -> TaskGroupV2:
|
|
103
107
|
"""
|
|
@@ -133,7 +137,7 @@ async def remove_duplicate_task_groups(
|
|
|
133
137
|
*,
|
|
134
138
|
task_groups: list[TaskGroupV2],
|
|
135
139
|
user_id: int,
|
|
136
|
-
default_group_id: int,
|
|
140
|
+
default_group_id: int | None,
|
|
137
141
|
db: AsyncSession,
|
|
138
142
|
) -> list[TaskGroupV2]:
|
|
139
143
|
"""
|
|
@@ -19,7 +19,7 @@ from ._aux_functions import _get_dataset_check_owner
|
|
|
19
19
|
from ._aux_functions import _get_project_check_owner
|
|
20
20
|
from ._aux_functions import _get_submitted_jobs_statement
|
|
21
21
|
from fractal_server.app.models import UserOAuth
|
|
22
|
-
from fractal_server.app.routes.auth import
|
|
22
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
23
23
|
from fractal_server.string_tools import sanitize_string
|
|
24
24
|
from fractal_server.urls import normalize_url
|
|
25
25
|
|
|
@@ -34,7 +34,7 @@ router = APIRouter()
|
|
|
34
34
|
async def create_dataset(
|
|
35
35
|
project_id: int,
|
|
36
36
|
dataset: DatasetCreateV2,
|
|
37
|
-
user: UserOAuth = Depends(
|
|
37
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
38
38
|
db: AsyncSession = Depends(get_async_db),
|
|
39
39
|
) -> DatasetReadV2 | None:
|
|
40
40
|
"""
|
|
@@ -45,15 +45,6 @@ async def create_dataset(
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
if dataset.zarr_dir is None:
|
|
48
|
-
if user.settings.project_dir is None:
|
|
49
|
-
raise HTTPException(
|
|
50
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
51
|
-
detail=(
|
|
52
|
-
"Both 'dataset.zarr_dir' and 'user.settings.project_dir' "
|
|
53
|
-
"are null"
|
|
54
|
-
),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
48
|
db_dataset = DatasetV2(
|
|
58
49
|
project_id=project_id,
|
|
59
50
|
zarr_dir="__PLACEHOLDER__",
|
|
@@ -63,7 +54,7 @@ async def create_dataset(
|
|
|
63
54
|
await db.commit()
|
|
64
55
|
await db.refresh(db_dataset)
|
|
65
56
|
path = (
|
|
66
|
-
f"{user.
|
|
57
|
+
f"{user.project_dir}/fractal/"
|
|
67
58
|
f"{project_id}_{sanitize_string(project.name)}/"
|
|
68
59
|
f"{db_dataset.id}_{sanitize_string(db_dataset.name)}"
|
|
69
60
|
)
|
|
@@ -88,7 +79,7 @@ async def create_dataset(
|
|
|
88
79
|
)
|
|
89
80
|
async def read_dataset_list(
|
|
90
81
|
project_id: int,
|
|
91
|
-
user: UserOAuth = Depends(
|
|
82
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
92
83
|
db: AsyncSession = Depends(get_async_db),
|
|
93
84
|
) -> list[DatasetReadV2] | None:
|
|
94
85
|
"""
|
|
@@ -116,7 +107,7 @@ async def read_dataset_list(
|
|
|
116
107
|
async def read_dataset(
|
|
117
108
|
project_id: int,
|
|
118
109
|
dataset_id: int,
|
|
119
|
-
user: UserOAuth = Depends(
|
|
110
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
120
111
|
db: AsyncSession = Depends(get_async_db),
|
|
121
112
|
) -> DatasetReadV2 | None:
|
|
122
113
|
"""
|
|
@@ -141,7 +132,7 @@ async def update_dataset(
|
|
|
141
132
|
project_id: int,
|
|
142
133
|
dataset_id: int,
|
|
143
134
|
dataset_update: DatasetUpdateV2,
|
|
144
|
-
user: UserOAuth = Depends(
|
|
135
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
145
136
|
db: AsyncSession = Depends(get_async_db),
|
|
146
137
|
) -> DatasetReadV2 | None:
|
|
147
138
|
"""
|
|
@@ -181,7 +172,7 @@ async def update_dataset(
|
|
|
181
172
|
async def delete_dataset(
|
|
182
173
|
project_id: int,
|
|
183
174
|
dataset_id: int,
|
|
184
|
-
user: UserOAuth = Depends(
|
|
175
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
185
176
|
db: AsyncSession = Depends(get_async_db),
|
|
186
177
|
) -> Response:
|
|
187
178
|
"""
|
|
@@ -219,7 +210,7 @@ async def delete_dataset(
|
|
|
219
210
|
|
|
220
211
|
@router.get("/dataset/", response_model=list[DatasetReadV2])
|
|
221
212
|
async def get_user_datasets(
|
|
222
|
-
user: UserOAuth = Depends(
|
|
213
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
223
214
|
db: AsyncSession = Depends(get_async_db),
|
|
224
215
|
) -> list[DatasetReadV2]:
|
|
225
216
|
"""
|
|
@@ -243,7 +234,7 @@ async def get_user_datasets(
|
|
|
243
234
|
async def export_dataset(
|
|
244
235
|
project_id: int,
|
|
245
236
|
dataset_id: int,
|
|
246
|
-
user: UserOAuth = Depends(
|
|
237
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
247
238
|
db: AsyncSession = Depends(get_async_db),
|
|
248
239
|
) -> DatasetExportV2 | None:
|
|
249
240
|
"""
|
|
@@ -270,7 +261,7 @@ async def export_dataset(
|
|
|
270
261
|
async def import_dataset(
|
|
271
262
|
project_id: int,
|
|
272
263
|
dataset: DatasetImportV2,
|
|
273
|
-
user: UserOAuth = Depends(
|
|
264
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
274
265
|
db: AsyncSession = Depends(get_async_db),
|
|
275
266
|
) -> DatasetReadV2 | None:
|
|
276
267
|
"""
|
|
@@ -23,7 +23,7 @@ from fractal_server.app.models.v2 import HistoryImageCache
|
|
|
23
23
|
from fractal_server.app.models.v2 import HistoryRun
|
|
24
24
|
from fractal_server.app.models.v2 import HistoryUnit
|
|
25
25
|
from fractal_server.app.models.v2 import TaskV2
|
|
26
|
-
from fractal_server.app.routes.auth import
|
|
26
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
27
27
|
from fractal_server.app.routes.pagination import get_pagination_params
|
|
28
28
|
from fractal_server.app.routes.pagination import PaginationRequest
|
|
29
29
|
from fractal_server.app.routes.pagination import PaginationResponse
|
|
@@ -68,7 +68,7 @@ async def get_workflow_tasks_statuses(
|
|
|
68
68
|
project_id: int,
|
|
69
69
|
dataset_id: int,
|
|
70
70
|
workflow_id: int,
|
|
71
|
-
user: UserOAuth = Depends(
|
|
71
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
72
72
|
db: AsyncSession = Depends(get_async_db),
|
|
73
73
|
) -> JSONResponse:
|
|
74
74
|
# Access control
|
|
@@ -179,7 +179,7 @@ async def get_history_run_list(
|
|
|
179
179
|
project_id: int,
|
|
180
180
|
dataset_id: int,
|
|
181
181
|
workflowtask_id: int,
|
|
182
|
-
user: UserOAuth = Depends(
|
|
182
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
183
183
|
db: AsyncSession = Depends(get_async_db),
|
|
184
184
|
) -> list[HistoryRunReadAggregated]:
|
|
185
185
|
# Access control
|
|
@@ -271,7 +271,7 @@ async def get_history_run_units(
|
|
|
271
271
|
workflowtask_id: int,
|
|
272
272
|
history_run_id: int,
|
|
273
273
|
unit_status: HistoryUnitStatus | None = None,
|
|
274
|
-
user: UserOAuth = Depends(
|
|
274
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
275
275
|
db: AsyncSession = Depends(get_async_db),
|
|
276
276
|
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
277
277
|
) -> PaginationResponse[HistoryUnitRead]:
|
|
@@ -330,7 +330,7 @@ async def get_history_images(
|
|
|
330
330
|
dataset_id: int,
|
|
331
331
|
workflowtask_id: int,
|
|
332
332
|
request_body: ImageQuery,
|
|
333
|
-
user: UserOAuth = Depends(
|
|
333
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
334
334
|
db: AsyncSession = Depends(get_async_db),
|
|
335
335
|
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
336
336
|
) -> ImagePage:
|
|
@@ -412,7 +412,7 @@ async def get_history_images(
|
|
|
412
412
|
async def get_image_log(
|
|
413
413
|
project_id: int,
|
|
414
414
|
request_data: ImageLogsRequest,
|
|
415
|
-
user: UserOAuth = Depends(
|
|
415
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
416
416
|
db: AsyncSession = Depends(get_async_db),
|
|
417
417
|
) -> JSONResponse:
|
|
418
418
|
# Access control
|
|
@@ -460,7 +460,7 @@ async def get_history_unit_log(
|
|
|
460
460
|
history_unit_id: int,
|
|
461
461
|
workflowtask_id: int,
|
|
462
462
|
dataset_id: int,
|
|
463
|
-
user: UserOAuth = Depends(
|
|
463
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
464
464
|
db: AsyncSession = Depends(get_async_db),
|
|
465
465
|
) -> JSONResponse:
|
|
466
466
|
# Access control
|
|
@@ -508,7 +508,7 @@ async def get_history_unit_log(
|
|
|
508
508
|
async def get_dataset_history(
|
|
509
509
|
project_id: int,
|
|
510
510
|
dataset_id: int,
|
|
511
|
-
user: UserOAuth = Depends(
|
|
511
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
512
512
|
db: AsyncSession = Depends(get_async_db),
|
|
513
513
|
) -> list[HistoryRunRead]:
|
|
514
514
|
"""
|
|
@@ -13,7 +13,7 @@ from fractal_server.app.db import AsyncSession
|
|
|
13
13
|
from fractal_server.app.db import get_async_db
|
|
14
14
|
from fractal_server.app.models import HistoryImageCache
|
|
15
15
|
from fractal_server.app.models import UserOAuth
|
|
16
|
-
from fractal_server.app.routes.auth import
|
|
16
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
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
|
|
@@ -60,7 +60,7 @@ async def post_new_image(
|
|
|
60
60
|
project_id: int,
|
|
61
61
|
dataset_id: int,
|
|
62
62
|
new_image: SingleImage,
|
|
63
|
-
user: UserOAuth = Depends(
|
|
63
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
64
64
|
db: AsyncSession = Depends(get_async_db),
|
|
65
65
|
) -> Response:
|
|
66
66
|
output = await _get_dataset_check_owner(
|
|
@@ -112,7 +112,7 @@ async def query_dataset_images(
|
|
|
112
112
|
dataset_id: int,
|
|
113
113
|
query: ImageQueryWithZarrUrl | None = None,
|
|
114
114
|
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
115
|
-
user: UserOAuth = Depends(
|
|
115
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
116
116
|
db: AsyncSession = Depends(get_async_db),
|
|
117
117
|
) -> ImagePage:
|
|
118
118
|
page = pagination.page
|
|
@@ -183,7 +183,7 @@ async def delete_dataset_images(
|
|
|
183
183
|
project_id: int,
|
|
184
184
|
dataset_id: int,
|
|
185
185
|
zarr_url: str,
|
|
186
|
-
user: UserOAuth = Depends(
|
|
186
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
187
187
|
db: AsyncSession = Depends(get_async_db),
|
|
188
188
|
) -> Response:
|
|
189
189
|
output = await _get_dataset_check_owner(
|
|
@@ -227,7 +227,7 @@ async def patch_dataset_image(
|
|
|
227
227
|
project_id: int,
|
|
228
228
|
dataset_id: int,
|
|
229
229
|
image_update: SingleImageUpdate,
|
|
230
|
-
user: UserOAuth = Depends(
|
|
230
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
231
231
|
db: AsyncSession = Depends(get_async_db),
|
|
232
232
|
):
|
|
233
233
|
output = await _get_dataset_check_owner(
|
|
@@ -23,7 +23,7 @@ from ._aux_functions import _get_job_check_owner
|
|
|
23
23
|
from ._aux_functions import _get_project_check_owner
|
|
24
24
|
from ._aux_functions import _get_workflow_check_owner
|
|
25
25
|
from fractal_server.app.models import UserOAuth
|
|
26
|
-
from fractal_server.app.routes.auth import
|
|
26
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
27
27
|
from fractal_server.runner.filenames import WORKFLOW_LOG_FILENAME
|
|
28
28
|
|
|
29
29
|
|
|
@@ -39,7 +39,7 @@ router = APIRouter()
|
|
|
39
39
|
|
|
40
40
|
@router.get("/job/", response_model=list[JobReadV2])
|
|
41
41
|
async def get_user_jobs(
|
|
42
|
-
user: UserOAuth = Depends(
|
|
42
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
43
43
|
log: bool = True,
|
|
44
44
|
db: AsyncSession = Depends(get_async_db),
|
|
45
45
|
) -> list[JobReadV2]:
|
|
@@ -68,7 +68,7 @@ async def get_user_jobs(
|
|
|
68
68
|
async def get_workflow_jobs(
|
|
69
69
|
project_id: int,
|
|
70
70
|
workflow_id: int,
|
|
71
|
-
user: UserOAuth = Depends(
|
|
71
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
72
72
|
db: AsyncSession = Depends(get_async_db),
|
|
73
73
|
) -> list[JobReadV2] | None:
|
|
74
74
|
"""
|
|
@@ -88,7 +88,7 @@ async def get_latest_job(
|
|
|
88
88
|
project_id: int,
|
|
89
89
|
workflow_id: int,
|
|
90
90
|
dataset_id: int,
|
|
91
|
-
user: UserOAuth = Depends(
|
|
91
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
92
92
|
db: AsyncSession = Depends(get_async_db),
|
|
93
93
|
) -> JobReadV2:
|
|
94
94
|
await _get_workflow_check_owner(
|
|
@@ -120,7 +120,7 @@ async def read_job(
|
|
|
120
120
|
project_id: int,
|
|
121
121
|
job_id: int,
|
|
122
122
|
show_tmp_logs: bool = False,
|
|
123
|
-
user: UserOAuth = Depends(
|
|
123
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
124
124
|
db: AsyncSession = Depends(get_async_db),
|
|
125
125
|
) -> JobReadV2 | None:
|
|
126
126
|
"""
|
|
@@ -153,7 +153,7 @@ async def read_job(
|
|
|
153
153
|
async def download_job_logs(
|
|
154
154
|
project_id: int,
|
|
155
155
|
job_id: int,
|
|
156
|
-
user: UserOAuth = Depends(
|
|
156
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
157
157
|
db: AsyncSession = Depends(get_async_db),
|
|
158
158
|
) -> StreamingResponse:
|
|
159
159
|
"""
|
|
@@ -183,7 +183,7 @@ async def download_job_logs(
|
|
|
183
183
|
)
|
|
184
184
|
async def get_job_list(
|
|
185
185
|
project_id: int,
|
|
186
|
-
user: UserOAuth = Depends(
|
|
186
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
187
187
|
log: bool = True,
|
|
188
188
|
db: AsyncSession = Depends(get_async_db),
|
|
189
189
|
) -> list[JobReadV2] | None:
|
|
@@ -212,7 +212,7 @@ async def get_job_list(
|
|
|
212
212
|
async def stop_job(
|
|
213
213
|
project_id: int,
|
|
214
214
|
job_id: int,
|
|
215
|
-
user: UserOAuth = Depends(
|
|
215
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
216
216
|
db: AsyncSession = Depends(get_async_db),
|
|
217
217
|
) -> Response:
|
|
218
218
|
"""
|
|
@@ -11,7 +11,7 @@ from .images import ImageQuery
|
|
|
11
11
|
from fractal_server.app.db import AsyncSession
|
|
12
12
|
from fractal_server.app.db import get_async_db
|
|
13
13
|
from fractal_server.app.models import UserOAuth
|
|
14
|
-
from fractal_server.app.routes.auth import
|
|
14
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
15
15
|
from fractal_server.app.schemas.v2 import HistoryUnitStatus
|
|
16
16
|
from fractal_server.app.schemas.v2 import TaskType
|
|
17
17
|
from fractal_server.images.status_tools import enrich_images_unsorted_async
|
|
@@ -32,7 +32,7 @@ async def verify_unique_types(
|
|
|
32
32
|
dataset_id: int,
|
|
33
33
|
workflowtask_id: int,
|
|
34
34
|
query: ImageQuery | None = None,
|
|
35
|
-
user: UserOAuth = Depends(
|
|
35
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
36
36
|
db: AsyncSession = Depends(get_async_db),
|
|
37
37
|
) -> list[str]:
|
|
38
38
|
# Get dataset
|
|
@@ -93,7 +93,7 @@ async def check_non_processed_images(
|
|
|
93
93
|
workflow_id: int,
|
|
94
94
|
workflowtask_id: int,
|
|
95
95
|
filters: NonProcessedImagesPayload,
|
|
96
|
-
user: UserOAuth = Depends(
|
|
96
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
97
97
|
db: AsyncSession = Depends(get_async_db),
|
|
98
98
|
) -> JSONResponse:
|
|
99
99
|
db_workflow_task, db_workflow = await _get_workflow_task_check_owner(
|
|
@@ -15,18 +15,19 @@ from ....models.v2 import ProjectV2
|
|
|
15
15
|
from ....schemas.v2 import ProjectCreateV2
|
|
16
16
|
from ....schemas.v2 import ProjectReadV2
|
|
17
17
|
from ....schemas.v2 import ProjectUpdateV2
|
|
18
|
+
from ...aux.validate_user_profile import validate_user_profile
|
|
18
19
|
from ._aux_functions import _check_project_exists
|
|
19
20
|
from ._aux_functions import _get_project_check_owner
|
|
20
21
|
from ._aux_functions import _get_submitted_jobs_statement
|
|
21
22
|
from fractal_server.app.models import UserOAuth
|
|
22
|
-
from fractal_server.app.routes.auth import
|
|
23
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
23
24
|
|
|
24
25
|
router = APIRouter()
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@router.get("/project/", response_model=list[ProjectReadV2])
|
|
28
29
|
async def get_list_project(
|
|
29
|
-
user: UserOAuth = Depends(
|
|
30
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
30
31
|
db: AsyncSession = Depends(get_async_db),
|
|
31
32
|
) -> list[ProjectV2]:
|
|
32
33
|
"""
|
|
@@ -46,19 +47,26 @@ async def get_list_project(
|
|
|
46
47
|
@router.post("/project/", response_model=ProjectReadV2, status_code=201)
|
|
47
48
|
async def create_project(
|
|
48
49
|
project: ProjectCreateV2,
|
|
49
|
-
user: UserOAuth = Depends(
|
|
50
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
50
51
|
db: AsyncSession = Depends(get_async_db),
|
|
51
52
|
) -> ProjectReadV2 | None:
|
|
52
53
|
"""
|
|
53
54
|
Create new project
|
|
54
55
|
"""
|
|
55
56
|
|
|
57
|
+
# Get validated resource and profile
|
|
58
|
+
resource, profile = await validate_user_profile(
|
|
59
|
+
user=user,
|
|
60
|
+
db=db,
|
|
61
|
+
)
|
|
62
|
+
resource_id = resource.id
|
|
63
|
+
|
|
56
64
|
# Check that there is no project with the same user and name
|
|
57
65
|
await _check_project_exists(
|
|
58
66
|
project_name=project.name, user_id=user.id, db=db
|
|
59
67
|
)
|
|
60
68
|
|
|
61
|
-
db_project = ProjectV2(**project.model_dump())
|
|
69
|
+
db_project = ProjectV2(**project.model_dump(), resource_id=resource_id)
|
|
62
70
|
db_project.user_list.append(user)
|
|
63
71
|
|
|
64
72
|
db.add(db_project)
|
|
@@ -72,7 +80,7 @@ async def create_project(
|
|
|
72
80
|
@router.get("/project/{project_id}/", response_model=ProjectReadV2)
|
|
73
81
|
async def read_project(
|
|
74
82
|
project_id: int,
|
|
75
|
-
user: UserOAuth = Depends(
|
|
83
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
76
84
|
db: AsyncSession = Depends(get_async_db),
|
|
77
85
|
) -> ProjectReadV2 | None:
|
|
78
86
|
"""
|
|
@@ -89,7 +97,7 @@ async def read_project(
|
|
|
89
97
|
async def update_project(
|
|
90
98
|
project_id: int,
|
|
91
99
|
project_update: ProjectUpdateV2,
|
|
92
|
-
user: UserOAuth = Depends(
|
|
100
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
93
101
|
db: AsyncSession = Depends(get_async_db),
|
|
94
102
|
):
|
|
95
103
|
project = await _get_project_check_owner(
|
|
@@ -114,7 +122,7 @@ async def update_project(
|
|
|
114
122
|
@router.delete("/project/{project_id}/", status_code=204)
|
|
115
123
|
async def delete_project(
|
|
116
124
|
project_id: int,
|
|
117
|
-
user: UserOAuth = Depends(
|
|
125
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
118
126
|
db: AsyncSession = Depends(get_async_db),
|
|
119
127
|
) -> Response:
|
|
120
128
|
"""
|
|
@@ -13,7 +13,7 @@ from ._aux_functions import _get_dataset_check_owner
|
|
|
13
13
|
from ._aux_functions import _get_submitted_jobs_statement
|
|
14
14
|
from ._aux_functions import _get_workflow_check_owner
|
|
15
15
|
from fractal_server.app.models import UserOAuth
|
|
16
|
-
from fractal_server.app.routes.auth import
|
|
16
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
17
17
|
|
|
18
18
|
router = APIRouter()
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ async def get_workflowtask_status(
|
|
|
28
28
|
project_id: int,
|
|
29
29
|
dataset_id: int,
|
|
30
30
|
workflow_id: int,
|
|
31
|
-
user: UserOAuth = Depends(
|
|
31
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
32
32
|
db: AsyncSession = Depends(get_async_db),
|
|
33
33
|
) -> LegacyStatusReadV2 | None:
|
|
34
34
|
"""
|