fractal-server 2.17.2__py3-none-any.whl → 2.18.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 +2 -1
- fractal_server/app/models/linkuserproject.py +40 -0
- fractal_server/app/models/security.py +7 -5
- fractal_server/app/models/v2/job.py +13 -2
- fractal_server/app/models/v2/resource.py +13 -0
- fractal_server/app/routes/admin/v2/__init__.py +11 -11
- fractal_server/app/routes/admin/v2/accounting.py +2 -2
- fractal_server/app/routes/admin/v2/job.py +34 -23
- fractal_server/app/routes/admin/v2/sharing.py +103 -0
- fractal_server/app/routes/admin/v2/task.py +9 -8
- fractal_server/app/routes/admin/v2/task_group.py +94 -16
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +20 -20
- fractal_server/app/routes/api/__init__.py +0 -9
- fractal_server/app/routes/api/v2/__init__.py +47 -47
- fractal_server/app/routes/api/v2/_aux_functions.py +65 -64
- fractal_server/app/routes/api/v2/_aux_functions_history.py +8 -3
- fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +4 -4
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +2 -2
- fractal_server/app/routes/api/v2/dataset.py +89 -77
- fractal_server/app/routes/api/v2/history.py +28 -16
- fractal_server/app/routes/api/v2/images.py +22 -8
- fractal_server/app/routes/api/v2/job.py +40 -24
- fractal_server/app/routes/api/v2/pre_submission_checks.py +13 -6
- fractal_server/app/routes/api/v2/project.py +48 -25
- fractal_server/app/routes/api/v2/sharing.py +311 -0
- fractal_server/app/routes/api/v2/status_legacy.py +22 -33
- fractal_server/app/routes/api/v2/submit.py +76 -71
- fractal_server/app/routes/api/v2/task.py +15 -17
- fractal_server/app/routes/api/v2/task_collection.py +18 -18
- fractal_server/app/routes/api/v2/task_collection_custom.py +11 -13
- fractal_server/app/routes/api/v2/task_collection_pixi.py +9 -9
- fractal_server/app/routes/api/v2/task_group.py +18 -18
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +26 -26
- fractal_server/app/routes/api/v2/task_version_update.py +12 -9
- fractal_server/app/routes/api/v2/workflow.py +41 -29
- fractal_server/app/routes/api/v2/workflow_import.py +25 -23
- fractal_server/app/routes/api/v2/workflowtask.py +25 -17
- fractal_server/app/routes/auth/_aux_auth.py +100 -0
- fractal_server/app/routes/auth/current_user.py +0 -63
- fractal_server/app/routes/auth/group.py +1 -30
- fractal_server/app/routes/auth/router.py +2 -0
- fractal_server/app/routes/auth/users.py +9 -0
- fractal_server/app/routes/auth/viewer_paths.py +43 -0
- fractal_server/app/schemas/user.py +29 -12
- fractal_server/app/schemas/user_group.py +0 -15
- fractal_server/app/schemas/v2/__init__.py +55 -48
- fractal_server/app/schemas/v2/dataset.py +35 -13
- fractal_server/app/schemas/v2/dumps.py +9 -9
- fractal_server/app/schemas/v2/job.py +11 -11
- fractal_server/app/schemas/v2/project.py +3 -3
- fractal_server/app/schemas/v2/resource.py +13 -4
- fractal_server/app/schemas/v2/sharing.py +99 -0
- fractal_server/app/schemas/v2/status_legacy.py +3 -3
- fractal_server/app/schemas/v2/task.py +6 -6
- fractal_server/app/schemas/v2/task_collection.py +4 -4
- fractal_server/app/schemas/v2/task_group.py +16 -16
- fractal_server/app/schemas/v2/workflow.py +16 -16
- fractal_server/app/schemas/v2/workflowtask.py +14 -14
- fractal_server/app/security/__init__.py +1 -1
- fractal_server/app/shutdown.py +6 -6
- fractal_server/config/__init__.py +0 -6
- fractal_server/config/_data.py +0 -79
- fractal_server/config/_main.py +6 -1
- fractal_server/data_migrations/2_18_0.py +30 -0
- fractal_server/images/models.py +1 -2
- fractal_server/main.py +72 -11
- fractal_server/migrations/versions/7910eed4cf97_user_project_dirs_and_usergroup_viewer_.py +60 -0
- fractal_server/migrations/versions/88270f589c9b_add_prevent_new_submissions.py +39 -0
- fractal_server/migrations/versions/bc0e8b3327a7_project_sharing.py +72 -0
- fractal_server/migrations/versions/f0702066b007_one_submitted_job_per_dataset.py +40 -0
- fractal_server/runner/config/_slurm.py +2 -0
- fractal_server/runner/executors/slurm_common/_batching.py +4 -10
- fractal_server/runner/executors/slurm_common/slurm_config.py +1 -0
- fractal_server/runner/executors/slurm_ssh/runner.py +1 -1
- fractal_server/runner/executors/slurm_sudo/runner.py +1 -1
- fractal_server/runner/v2/_local.py +4 -3
- fractal_server/runner/v2/_slurm_ssh.py +4 -3
- fractal_server/runner/v2/_slurm_sudo.py +4 -3
- fractal_server/runner/v2/runner.py +36 -17
- fractal_server/runner/v2/runner_functions.py +11 -14
- fractal_server/runner/v2/submit_workflow.py +22 -9
- fractal_server/tasks/v2/local/_utils.py +2 -2
- fractal_server/tasks/v2/local/collect.py +5 -6
- fractal_server/tasks/v2/local/collect_pixi.py +5 -6
- fractal_server/tasks/v2/local/deactivate.py +7 -7
- fractal_server/tasks/v2/local/deactivate_pixi.py +3 -3
- fractal_server/tasks/v2/local/delete.py +5 -5
- fractal_server/tasks/v2/local/reactivate.py +5 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +5 -5
- fractal_server/tasks/v2/ssh/collect.py +5 -5
- fractal_server/tasks/v2/ssh/collect_pixi.py +5 -5
- fractal_server/tasks/v2/ssh/deactivate.py +7 -7
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +2 -2
- fractal_server/tasks/v2/ssh/delete.py +5 -5
- fractal_server/tasks/v2/ssh/reactivate.py +5 -5
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +5 -5
- fractal_server/tasks/v2/utils_background.py +7 -7
- fractal_server/tasks/v2/utils_database.py +5 -5
- fractal_server/types/__init__.py +22 -0
- fractal_server/types/validators/__init__.py +3 -0
- fractal_server/types/validators/_common_validators.py +32 -0
- {fractal_server-2.17.2.dist-info → fractal_server-2.18.0.dist-info}/METADATA +3 -2
- {fractal_server-2.17.2.dist-info → fractal_server-2.18.0.dist-info}/RECORD +108 -98
- {fractal_server-2.17.2.dist-info → fractal_server-2.18.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.17.2.dist-info → fractal_server-2.18.0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.17.2.dist-info → fractal_server-2.18.0.dist-info}/licenses/LICENSE +0 -0
fractal_server/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__VERSION__ = "2.
|
|
1
|
+
__VERSION__ = "2.18.0"
|
fractal_server/__main__.py
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
from sqlmodel import BOOLEAN
|
|
2
|
+
from sqlmodel import CheckConstraint
|
|
3
|
+
from sqlmodel import Column
|
|
1
4
|
from sqlmodel import Field
|
|
5
|
+
from sqlmodel import Index
|
|
2
6
|
from sqlmodel import SQLModel
|
|
7
|
+
from sqlmodel import String
|
|
8
|
+
from sqlmodel import column
|
|
3
9
|
|
|
4
10
|
|
|
5
11
|
class LinkUserProjectV2(SQLModel, table=True):
|
|
@@ -11,3 +17,37 @@ class LinkUserProjectV2(SQLModel, table=True):
|
|
|
11
17
|
foreign_key="projectv2.id", primary_key=True, ondelete="CASCADE"
|
|
12
18
|
)
|
|
13
19
|
user_id: int = Field(foreign_key="user_oauth.id", primary_key=True)
|
|
20
|
+
|
|
21
|
+
# TODO-2.18.1 drop server_default
|
|
22
|
+
is_owner: bool = Field(
|
|
23
|
+
sa_column=Column(BOOLEAN, server_default="true", nullable=False)
|
|
24
|
+
)
|
|
25
|
+
# TODO-2.18.1 drop server_default
|
|
26
|
+
is_verified: bool = Field(
|
|
27
|
+
sa_column=Column(BOOLEAN, server_default="true", nullable=False)
|
|
28
|
+
)
|
|
29
|
+
# TODO-2.18.1 drop server_default
|
|
30
|
+
permissions: str = Field(
|
|
31
|
+
sa_column=Column(String, server_default="'rwx'", nullable=False)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__table_args__ = (
|
|
35
|
+
Index(
|
|
36
|
+
"ix_linkuserprojectv2_one_owner_per_project",
|
|
37
|
+
"project_id",
|
|
38
|
+
unique=True,
|
|
39
|
+
postgresql_where=column("is_owner").is_(True),
|
|
40
|
+
),
|
|
41
|
+
CheckConstraint(
|
|
42
|
+
"NOT (is_owner AND NOT is_verified)",
|
|
43
|
+
name="owner_is_verified",
|
|
44
|
+
),
|
|
45
|
+
CheckConstraint(
|
|
46
|
+
"NOT (is_owner AND permissions <> 'rwx')",
|
|
47
|
+
name="owner_full_permissions",
|
|
48
|
+
),
|
|
49
|
+
CheckConstraint(
|
|
50
|
+
"permissions IN ('r', 'rw', 'rwx')",
|
|
51
|
+
name="valid_permissions",
|
|
52
|
+
),
|
|
53
|
+
)
|
|
@@ -6,7 +6,6 @@ from pydantic import EmailStr
|
|
|
6
6
|
from sqlalchemy import Column
|
|
7
7
|
from sqlalchemy import String
|
|
8
8
|
from sqlalchemy.dialects.postgresql import ARRAY
|
|
9
|
-
from sqlalchemy.dialects.postgresql import JSONB
|
|
10
9
|
from sqlalchemy.types import DateTime
|
|
11
10
|
from sqlmodel import Field
|
|
12
11
|
from sqlmodel import Relationship
|
|
@@ -113,7 +112,13 @@ class UserOAuth(SQLModel, table=True):
|
|
|
113
112
|
ondelete="RESTRICT",
|
|
114
113
|
)
|
|
115
114
|
|
|
116
|
-
|
|
115
|
+
# TODO-2.18.1: drop `project_dir`
|
|
116
|
+
project_dir: str | None = Field(default=None, nullable=True)
|
|
117
|
+
# TODO-2.18.1: `project_dirs: list[str] = Field(min_length=1)`
|
|
118
|
+
project_dirs: list[str] = Field(
|
|
119
|
+
sa_column=Column(ARRAY(String), nullable=False, server_default="{}"),
|
|
120
|
+
)
|
|
121
|
+
|
|
117
122
|
slurm_accounts: list[str] = Field(
|
|
118
123
|
sa_column=Column(ARRAY(String), server_default="{}"),
|
|
119
124
|
)
|
|
@@ -135,6 +140,3 @@ class UserGroup(SQLModel, table=True):
|
|
|
135
140
|
default_factory=get_timestamp,
|
|
136
141
|
sa_column=Column(DateTime(timezone=True), nullable=False),
|
|
137
142
|
)
|
|
138
|
-
viewer_paths: list[str] = Field(
|
|
139
|
-
sa_column=Column(JSONB, server_default="[]", nullable=False)
|
|
140
|
-
)
|
|
@@ -6,9 +6,11 @@ from sqlalchemy import Column
|
|
|
6
6
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
7
7
|
from sqlalchemy.types import DateTime
|
|
8
8
|
from sqlmodel import Field
|
|
9
|
+
from sqlmodel import Index
|
|
9
10
|
from sqlmodel import SQLModel
|
|
11
|
+
from sqlmodel import text
|
|
10
12
|
|
|
11
|
-
from fractal_server.app.schemas.v2 import
|
|
13
|
+
from fractal_server.app.schemas.v2 import JobStatusType
|
|
12
14
|
from fractal_server.utils import get_timestamp
|
|
13
15
|
|
|
14
16
|
|
|
@@ -56,7 +58,7 @@ class JobV2(SQLModel, table=True):
|
|
|
56
58
|
end_timestamp: datetime | None = Field(
|
|
57
59
|
default=None, sa_column=Column(DateTime(timezone=True))
|
|
58
60
|
)
|
|
59
|
-
status: str =
|
|
61
|
+
status: str = JobStatusType.SUBMITTED
|
|
60
62
|
log: str | None = None
|
|
61
63
|
executor_error_log: str | None = None
|
|
62
64
|
|
|
@@ -66,3 +68,12 @@ class JobV2(SQLModel, table=True):
|
|
|
66
68
|
type_filters: dict[str, bool] = Field(
|
|
67
69
|
sa_column=Column(JSONB, nullable=False, server_default="{}")
|
|
68
70
|
)
|
|
71
|
+
|
|
72
|
+
__table_args__ = (
|
|
73
|
+
Index(
|
|
74
|
+
"ix_jobv2_one_submitted_job_per_dataset",
|
|
75
|
+
"dataset_id",
|
|
76
|
+
unique=True,
|
|
77
|
+
postgresql_where=text(f"status = '{JobStatusType.SUBMITTED}'"),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
@@ -5,6 +5,7 @@ from typing import Self
|
|
|
5
5
|
from sqlalchemy import Column
|
|
6
6
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
7
7
|
from sqlalchemy.types import DateTime
|
|
8
|
+
from sqlmodel import BOOLEAN
|
|
8
9
|
from sqlmodel import CheckConstraint
|
|
9
10
|
from sqlmodel import Field
|
|
10
11
|
from sqlmodel import SQLModel
|
|
@@ -43,6 +44,18 @@ class Resource(SQLModel, table=True):
|
|
|
43
44
|
Address for ssh connections, when `type="slurm_ssh"`.
|
|
44
45
|
"""
|
|
45
46
|
|
|
47
|
+
prevent_new_submissions: bool = Field(
|
|
48
|
+
sa_column=Column(
|
|
49
|
+
BOOLEAN,
|
|
50
|
+
server_default="false",
|
|
51
|
+
nullable=False,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
"""
|
|
55
|
+
When set to true: Prevent new job submissions and stop execution of
|
|
56
|
+
ongoing jobs as soon as the current task is complete.
|
|
57
|
+
"""
|
|
58
|
+
|
|
46
59
|
jobs_local_dir: str
|
|
47
60
|
"""
|
|
48
61
|
Base local folder for job subfolders (containing artifacts and logs).
|
|
@@ -9,19 +9,19 @@ from .impersonate import router as impersonate_router
|
|
|
9
9
|
from .job import router as job_router
|
|
10
10
|
from .profile import router as profile_router
|
|
11
11
|
from .resource import router as resource_router
|
|
12
|
+
from .sharing import router as sharing_router
|
|
12
13
|
from .task import router as task_router
|
|
13
14
|
from .task_group import router as task_group_router
|
|
14
15
|
from .task_group_lifecycle import router as task_group_lifecycle_router
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
router_admin = APIRouter()
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
router_admin_v2.include_router(profile_router, prefix="/profile")
|
|
19
|
+
router_admin.include_router(accounting_router, prefix="/accounting")
|
|
20
|
+
router_admin.include_router(job_router, prefix="/job")
|
|
21
|
+
router_admin.include_router(task_router, prefix="/task")
|
|
22
|
+
router_admin.include_router(task_group_router, prefix="/task-group")
|
|
23
|
+
router_admin.include_router(task_group_lifecycle_router, prefix="/task-group")
|
|
24
|
+
router_admin.include_router(impersonate_router, prefix="/impersonate")
|
|
25
|
+
router_admin.include_router(resource_router, prefix="/resource")
|
|
26
|
+
router_admin.include_router(profile_router, prefix="/profile")
|
|
27
|
+
router_admin.include_router(sharing_router, prefix="/linkuserproject")
|
|
@@ -67,11 +67,11 @@ async def query_accounting(
|
|
|
67
67
|
res = await db.execute(stm)
|
|
68
68
|
records = res.scalars().all()
|
|
69
69
|
|
|
70
|
-
return
|
|
70
|
+
return dict(
|
|
71
71
|
total_count=total_count,
|
|
72
72
|
page_size=page_size,
|
|
73
73
|
current_page=page,
|
|
74
|
-
items=
|
|
74
|
+
items=records,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
|
|
@@ -24,9 +24,9 @@ from fractal_server.app.routes.pagination import PaginationRequest
|
|
|
24
24
|
from fractal_server.app.routes.pagination import PaginationResponse
|
|
25
25
|
from fractal_server.app.routes.pagination import get_pagination_params
|
|
26
26
|
from fractal_server.app.schemas.v2 import HistoryUnitStatus
|
|
27
|
-
from fractal_server.app.schemas.v2 import
|
|
28
|
-
from fractal_server.app.schemas.v2 import
|
|
29
|
-
from fractal_server.app.schemas.v2 import
|
|
27
|
+
from fractal_server.app.schemas.v2 import JobRead
|
|
28
|
+
from fractal_server.app.schemas.v2 import JobStatusType
|
|
29
|
+
from fractal_server.app.schemas.v2 import JobUpdate
|
|
30
30
|
from fractal_server.runner.filenames import WORKFLOW_LOG_FILENAME
|
|
31
31
|
from fractal_server.utils import get_timestamp
|
|
32
32
|
from fractal_server.zip_tools import _zip_folder_to_byte_stream_iterator
|
|
@@ -34,14 +34,14 @@ from fractal_server.zip_tools import _zip_folder_to_byte_stream_iterator
|
|
|
34
34
|
router = APIRouter()
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
@router.get("/", response_model=PaginationResponse[
|
|
37
|
+
@router.get("/", response_model=PaginationResponse[JobRead])
|
|
38
38
|
async def view_job(
|
|
39
39
|
id: int | None = None,
|
|
40
40
|
user_id: int | None = None,
|
|
41
41
|
project_id: int | None = None,
|
|
42
42
|
dataset_id: int | None = None,
|
|
43
43
|
workflow_id: int | None = None,
|
|
44
|
-
status:
|
|
44
|
+
status: JobStatusType | None = None,
|
|
45
45
|
start_timestamp_min: AwareDatetime | None = None,
|
|
46
46
|
start_timestamp_max: AwareDatetime | None = None,
|
|
47
47
|
end_timestamp_min: AwareDatetime | None = None,
|
|
@@ -50,12 +50,13 @@ async def view_job(
|
|
|
50
50
|
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
51
51
|
user: UserOAuth = Depends(current_superuser_act),
|
|
52
52
|
db: AsyncSession = Depends(get_async_db),
|
|
53
|
-
) -> PaginationResponse[
|
|
53
|
+
) -> PaginationResponse[JobRead]:
|
|
54
54
|
"""
|
|
55
55
|
Query `JobV2` table.
|
|
56
56
|
|
|
57
57
|
Args:
|
|
58
58
|
id: If not `None`, select a given `applyworkflow.id`.
|
|
59
|
+
user_id:
|
|
59
60
|
project_id: If not `None`, select a given `applyworkflow.project_id`.
|
|
60
61
|
dataset_id: If not `None`, select a given
|
|
61
62
|
`applyworkflow.input_dataset_id`.
|
|
@@ -84,12 +85,22 @@ async def view_job(
|
|
|
84
85
|
stm = stm.where(JobV2.id == id)
|
|
85
86
|
stm_count = stm_count.where(JobV2.id == id)
|
|
86
87
|
if user_id is not None:
|
|
87
|
-
stm =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
stm = (
|
|
89
|
+
stm.join(
|
|
90
|
+
LinkUserProjectV2,
|
|
91
|
+
LinkUserProjectV2.project_id == JobV2.project_id,
|
|
92
|
+
)
|
|
93
|
+
.where(LinkUserProjectV2.user_id == user_id)
|
|
94
|
+
.where(LinkUserProjectV2.is_owner.is_(True))
|
|
95
|
+
)
|
|
96
|
+
stm_count = (
|
|
97
|
+
stm_count.join(
|
|
98
|
+
LinkUserProjectV2,
|
|
99
|
+
LinkUserProjectV2.project_id == JobV2.project_id,
|
|
100
|
+
)
|
|
101
|
+
.where(LinkUserProjectV2.user_id == user_id)
|
|
102
|
+
.where(LinkUserProjectV2.is_owner.is_(True))
|
|
103
|
+
)
|
|
93
104
|
if project_id is not None:
|
|
94
105
|
stm = stm.where(JobV2.project_id == project_id)
|
|
95
106
|
stm_count = stm_count.where(JobV2.project_id == project_id)
|
|
@@ -135,21 +146,21 @@ async def view_job(
|
|
|
135
146
|
for job in job_list:
|
|
136
147
|
setattr(job, "log", None)
|
|
137
148
|
|
|
138
|
-
return
|
|
149
|
+
return dict(
|
|
139
150
|
total_count=total_count,
|
|
140
151
|
page_size=page_size,
|
|
141
152
|
current_page=page,
|
|
142
|
-
items=
|
|
153
|
+
items=job_list,
|
|
143
154
|
)
|
|
144
155
|
|
|
145
156
|
|
|
146
|
-
@router.get("/{job_id}/", response_model=
|
|
157
|
+
@router.get("/{job_id}/", response_model=JobRead)
|
|
147
158
|
async def view_single_job(
|
|
148
159
|
job_id: int,
|
|
149
160
|
show_tmp_logs: bool = False,
|
|
150
161
|
user: UserOAuth = Depends(current_superuser_act),
|
|
151
162
|
db: AsyncSession = Depends(get_async_db),
|
|
152
|
-
) ->
|
|
163
|
+
) -> JobRead:
|
|
153
164
|
job = await db.get(JobV2, job_id)
|
|
154
165
|
if not job:
|
|
155
166
|
raise HTTPException(
|
|
@@ -158,7 +169,7 @@ async def view_single_job(
|
|
|
158
169
|
)
|
|
159
170
|
await db.close()
|
|
160
171
|
|
|
161
|
-
if show_tmp_logs and (job.status ==
|
|
172
|
+
if show_tmp_logs and (job.status == JobStatusType.SUBMITTED):
|
|
162
173
|
try:
|
|
163
174
|
with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}") as f:
|
|
164
175
|
job.log = f.read()
|
|
@@ -168,13 +179,13 @@ async def view_single_job(
|
|
|
168
179
|
return job
|
|
169
180
|
|
|
170
181
|
|
|
171
|
-
@router.patch("/{job_id}/", response_model=
|
|
182
|
+
@router.patch("/{job_id}/", response_model=JobRead)
|
|
172
183
|
async def update_job(
|
|
173
|
-
job_update:
|
|
184
|
+
job_update: JobUpdate,
|
|
174
185
|
job_id: int,
|
|
175
186
|
user: UserOAuth = Depends(current_superuser_act),
|
|
176
187
|
db: AsyncSession = Depends(get_async_db),
|
|
177
|
-
) ->
|
|
188
|
+
) -> JobRead | None:
|
|
178
189
|
"""
|
|
179
190
|
Change the status of an existing job.
|
|
180
191
|
|
|
@@ -187,13 +198,13 @@ async def update_job(
|
|
|
187
198
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
188
199
|
detail=f"Job {job_id} not found",
|
|
189
200
|
)
|
|
190
|
-
if job.status !=
|
|
201
|
+
if job.status != JobStatusType.SUBMITTED:
|
|
191
202
|
raise HTTPException(
|
|
192
203
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
193
204
|
detail=f"Job {job_id} has status {job.status=} != 'submitted'.",
|
|
194
205
|
)
|
|
195
206
|
|
|
196
|
-
if job_update.status !=
|
|
207
|
+
if job_update.status != JobStatusType.FAILED:
|
|
197
208
|
raise HTTPException(
|
|
198
209
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
199
210
|
detail=f"Cannot set job status to {job_update.status}",
|
|
@@ -206,7 +217,7 @@ async def update_job(
|
|
|
206
217
|
job,
|
|
207
218
|
"log",
|
|
208
219
|
f"{job.log or ''}\nThis job was manually marked as "
|
|
209
|
-
f"'{
|
|
220
|
+
f"'{JobStatusType.FAILED}' by an admin ({timestamp.isoformat()}).",
|
|
210
221
|
)
|
|
211
222
|
|
|
212
223
|
res = await db.execute(
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from fastapi import Depends
|
|
3
|
+
from sqlalchemy import func
|
|
4
|
+
from sqlmodel import select
|
|
5
|
+
|
|
6
|
+
from fractal_server.app.db import AsyncSession
|
|
7
|
+
from fractal_server.app.db import get_async_db
|
|
8
|
+
from fractal_server.app.models import LinkUserProjectV2
|
|
9
|
+
from fractal_server.app.models import UserOAuth
|
|
10
|
+
from fractal_server.app.models.v2 import ProjectV2
|
|
11
|
+
from fractal_server.app.routes.auth import current_superuser_act
|
|
12
|
+
from fractal_server.app.routes.pagination import PaginationRequest
|
|
13
|
+
from fractal_server.app.routes.pagination import PaginationResponse
|
|
14
|
+
from fractal_server.app.routes.pagination import get_pagination_params
|
|
15
|
+
from fractal_server.app.schemas.v2 import LinkUserProjectRead
|
|
16
|
+
|
|
17
|
+
router = APIRouter()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/", response_model=PaginationResponse[LinkUserProjectRead])
|
|
21
|
+
async def view_link_user_project(
|
|
22
|
+
# User info
|
|
23
|
+
user_id: int | None = None,
|
|
24
|
+
# Project info
|
|
25
|
+
project_id: int | None = None,
|
|
26
|
+
project_name: str | None = None,
|
|
27
|
+
# Permissions
|
|
28
|
+
is_owner: bool | None = None,
|
|
29
|
+
is_verified: bool | None = None,
|
|
30
|
+
# -----
|
|
31
|
+
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
32
|
+
superuser: UserOAuth = Depends(current_superuser_act),
|
|
33
|
+
db: AsyncSession = Depends(get_async_db),
|
|
34
|
+
) -> PaginationResponse[LinkUserProjectRead]:
|
|
35
|
+
page = pagination.page
|
|
36
|
+
page_size = pagination.page_size
|
|
37
|
+
|
|
38
|
+
stm = (
|
|
39
|
+
select(
|
|
40
|
+
LinkUserProjectV2,
|
|
41
|
+
UserOAuth.email,
|
|
42
|
+
ProjectV2.name,
|
|
43
|
+
)
|
|
44
|
+
.join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
|
|
45
|
+
.join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
|
|
46
|
+
.order_by(UserOAuth.email, ProjectV2.name)
|
|
47
|
+
)
|
|
48
|
+
stm_count = (
|
|
49
|
+
select(func.count())
|
|
50
|
+
.select_from(LinkUserProjectV2)
|
|
51
|
+
.join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
|
|
52
|
+
.join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if project_id is not None:
|
|
56
|
+
stm = stm.where(LinkUserProjectV2.project_id == project_id)
|
|
57
|
+
stm_count = stm_count.where(LinkUserProjectV2.project_id == project_id)
|
|
58
|
+
if project_name is not None:
|
|
59
|
+
stm = stm.where(ProjectV2.name.icontains(project_name))
|
|
60
|
+
stm_count = stm_count.where(ProjectV2.name.icontains(project_name))
|
|
61
|
+
if user_id is not None:
|
|
62
|
+
stm = stm.where(LinkUserProjectV2.user_id == user_id)
|
|
63
|
+
stm_count = stm_count.where(LinkUserProjectV2.user_id == user_id)
|
|
64
|
+
if is_owner is not None:
|
|
65
|
+
stm = stm.where(LinkUserProjectV2.is_owner == is_owner)
|
|
66
|
+
stm_count = stm_count.where(LinkUserProjectV2.is_owner == is_owner)
|
|
67
|
+
if is_verified is not None:
|
|
68
|
+
stm = stm.where(LinkUserProjectV2.is_verified == is_verified)
|
|
69
|
+
stm_count = stm_count.where(
|
|
70
|
+
LinkUserProjectV2.is_verified == is_verified
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
res_total_count = await db.execute(stm_count)
|
|
74
|
+
|
|
75
|
+
total_count = res_total_count.scalar()
|
|
76
|
+
if page_size is None:
|
|
77
|
+
page_size = total_count
|
|
78
|
+
else:
|
|
79
|
+
stm = stm.offset((page - 1) * page_size).limit(page_size)
|
|
80
|
+
|
|
81
|
+
res = await db.execute(stm)
|
|
82
|
+
items = res.all()
|
|
83
|
+
|
|
84
|
+
return PaginationResponse[LinkUserProjectRead](
|
|
85
|
+
total_count=total_count,
|
|
86
|
+
page_size=page_size,
|
|
87
|
+
current_page=page,
|
|
88
|
+
items=[
|
|
89
|
+
dict(
|
|
90
|
+
# User info
|
|
91
|
+
user_id=linkuserproject.user_id,
|
|
92
|
+
user_email=user_email,
|
|
93
|
+
# Project info
|
|
94
|
+
project_id=linkuserproject.project_id,
|
|
95
|
+
project_name=project_name,
|
|
96
|
+
# Permissions
|
|
97
|
+
is_verified=linkuserproject.is_verified,
|
|
98
|
+
is_owner=linkuserproject.is_owner,
|
|
99
|
+
permissions=linkuserproject.permissions,
|
|
100
|
+
)
|
|
101
|
+
for linkuserproject, user_email, project_name in items
|
|
102
|
+
],
|
|
103
|
+
)
|
|
@@ -23,7 +23,7 @@ from fractal_server.app.schemas.v2.task import TaskType
|
|
|
23
23
|
router = APIRouter()
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class
|
|
26
|
+
class TaskMinimal(BaseModel):
|
|
27
27
|
id: int
|
|
28
28
|
name: str
|
|
29
29
|
type: str
|
|
@@ -39,7 +39,7 @@ class ProjectUser(BaseModel):
|
|
|
39
39
|
email: EmailStr
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
class
|
|
42
|
+
class TaskRelationship(BaseModel):
|
|
43
43
|
workflow_id: int
|
|
44
44
|
workflow_name: str
|
|
45
45
|
project_id: int
|
|
@@ -47,12 +47,12 @@ class TaskV2Relationship(BaseModel):
|
|
|
47
47
|
project_users: list[ProjectUser] = Field(default_factory=list)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class
|
|
51
|
-
task:
|
|
52
|
-
relationships: list[
|
|
50
|
+
class TaskInfo(BaseModel):
|
|
51
|
+
task: TaskMinimal
|
|
52
|
+
relationships: list[TaskRelationship]
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
@router.get("/", response_model=PaginationResponse[
|
|
55
|
+
@router.get("/", response_model=PaginationResponse[TaskInfo])
|
|
56
56
|
async def query_tasks(
|
|
57
57
|
id: int | None = None,
|
|
58
58
|
source: str | None = None,
|
|
@@ -66,7 +66,7 @@ async def query_tasks(
|
|
|
66
66
|
pagination: PaginationRequest = Depends(get_pagination_params),
|
|
67
67
|
user: UserOAuth = Depends(current_superuser_act),
|
|
68
68
|
db: AsyncSession = Depends(get_async_db),
|
|
69
|
-
) -> PaginationResponse[
|
|
69
|
+
) -> PaginationResponse[TaskInfo]:
|
|
70
70
|
"""
|
|
71
71
|
Query `TaskV2` and get information about related workflows and projects.
|
|
72
72
|
"""
|
|
@@ -148,6 +148,7 @@ async def query_tasks(
|
|
|
148
148
|
LinkUserProjectV2.user_id == UserOAuth.id,
|
|
149
149
|
)
|
|
150
150
|
.where(LinkUserProjectV2.project_id == project_id)
|
|
151
|
+
.where(LinkUserProjectV2.is_owner.is_(True))
|
|
151
152
|
)
|
|
152
153
|
project_users[project_id] = [
|
|
153
154
|
ProjectUser(id=p_user[0], email=p_user[1])
|
|
@@ -169,7 +170,7 @@ async def query_tasks(
|
|
|
169
170
|
],
|
|
170
171
|
)
|
|
171
172
|
)
|
|
172
|
-
return
|
|
173
|
+
return dict(
|
|
173
174
|
total_count=total_count,
|
|
174
175
|
page_size=page_size,
|
|
175
176
|
current_page=page,
|