fractal-server 2.14.0a3__py3-none-any.whl → 2.14.0a4__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 +3 -1
- fractal_server/app/history/__init__.py +4 -4
- fractal_server/app/history/image_updates.py +124 -143
- fractal_server/app/history/status_enum.py +2 -2
- fractal_server/app/models/v2/__init__.py +6 -4
- fractal_server/app/models/v2/history.py +44 -20
- fractal_server/app/routes/api/__init__.py +1 -1
- fractal_server/app/routes/api/v2/__init__.py +4 -0
- fractal_server/app/routes/api/v2/_aux_functions_history.py +49 -0
- fractal_server/app/routes/api/v2/dataset.py +0 -12
- fractal_server/app/routes/api/v2/history.py +301 -186
- fractal_server/app/routes/api/v2/project.py +0 -25
- fractal_server/app/routes/api/v2/status_legacy.py +168 -0
- fractal_server/app/routes/api/v2/workflow.py +2 -17
- fractal_server/app/routes/api/v2/workflowtask.py +41 -71
- fractal_server/app/routes/auth/oauth.py +5 -3
- fractal_server/app/runner/executors/local/runner.py +10 -55
- fractal_server/app/runner/executors/slurm_sudo/runner.py +171 -108
- fractal_server/app/runner/v2/__init__.py +0 -20
- fractal_server/app/runner/v2/runner.py +45 -58
- fractal_server/app/runner/v2/runner_functions.py +164 -22
- fractal_server/app/schemas/_validators.py +13 -24
- fractal_server/app/schemas/user.py +10 -7
- fractal_server/app/schemas/user_settings.py +9 -21
- fractal_server/app/schemas/v2/dataset.py +8 -6
- fractal_server/app/schemas/v2/job.py +9 -5
- fractal_server/app/schemas/v2/manifest.py +2 -6
- fractal_server/app/schemas/v2/project.py +9 -7
- fractal_server/app/schemas/v2/task.py +41 -77
- fractal_server/app/schemas/v2/task_collection.py +14 -32
- fractal_server/app/schemas/v2/task_group.py +10 -9
- fractal_server/app/schemas/v2/workflow.py +10 -11
- fractal_server/app/security/signup_email.py +2 -2
- fractal_server/config.py +31 -32
- fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +120 -0
- fractal_server/tasks/v2/templates/2_pip_install.sh +1 -1
- fractal_server/tasks/v2/utils_templates.py +6 -0
- {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a4.dist-info}/METADATA +1 -1
- {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a4.dist-info}/RECORD +43 -44
- fractal_server/app/runner/executors/slurm_sudo/_executor_wait_thread.py +0 -130
- fractal_server/app/schemas/v2/history.py +0 -23
- fractal_server/migrations/versions/87cd72a537a2_add_historyitem_table.py +0 -68
- fractal_server/migrations/versions/954ddc64425a_image_status.py +0 -63
- {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a4.dist-info}/LICENSE +0 -0
- {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a4.dist-info}/WHEEL +0 -0
- {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a4.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.14.
|
1
|
+
__VERSION__ = "2.14.0a4"
|
fractal_server/__main__.py
CHANGED
@@ -123,7 +123,9 @@ def set_db(skip_init_data: bool = False):
|
|
123
123
|
asyncio.run(
|
124
124
|
_create_first_user(
|
125
125
|
email=settings.FRACTAL_DEFAULT_ADMIN_EMAIL,
|
126
|
-
password=
|
126
|
+
password=(
|
127
|
+
settings.FRACTAL_DEFAULT_ADMIN_PASSWORD.get_secret_value()
|
128
|
+
),
|
127
129
|
username=settings.FRACTAL_DEFAULT_ADMIN_USERNAME,
|
128
130
|
is_superuser=True,
|
129
131
|
is_verified=True,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from .image_updates import update_all_images # noqa: F401
|
2
|
-
from .image_updates import update_single_image # noqa
|
3
|
-
from .image_updates import update_single_image_logfile # noqa
|
4
|
-
from .status_enum import
|
1
|
+
# from .image_updates import update_all_images # noqa: F401
|
2
|
+
# from .image_updates import update_single_image # noqa
|
3
|
+
# from .image_updates import update_single_image_logfile # noqa
|
4
|
+
# from .status_enum import XXXStatus # noqa: F401
|
@@ -1,143 +1,124 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
|
-
from sqlalchemy.orm import
|
4
|
-
from
|
5
|
-
from
|
6
|
-
|
7
|
-
from fractal_server.app.
|
8
|
-
from fractal_server.app.
|
9
|
-
from fractal_server.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
new_images = {
|
126
|
-
zarr_url: status for zarr_url in history_item.images.keys()
|
127
|
-
}
|
128
|
-
history_item.images = new_images
|
129
|
-
flag_modified(history_item, "images")
|
130
|
-
db.commit()
|
131
|
-
|
132
|
-
# FIXME: Make this a bulk edit, if possible
|
133
|
-
for ind, zarr_url in enumerate(history_item.images.keys()):
|
134
|
-
_update_single_image_status(
|
135
|
-
zarr_url=zarr_url,
|
136
|
-
dataset_id=history_item.dataset_id,
|
137
|
-
workflowtask_id=history_item.workflowtask_id,
|
138
|
-
commit=False,
|
139
|
-
status=status,
|
140
|
-
logfile=logfile,
|
141
|
-
db=db,
|
142
|
-
)
|
143
|
-
db.commit()
|
1
|
+
# from typing import Optional
|
2
|
+
# from sqlalchemy.orm import Session
|
3
|
+
# from sqlalchemy.orm.attributes import flag_modified
|
4
|
+
# from sqlmodel import select
|
5
|
+
# from fractal_server.app.db import get_sync_db
|
6
|
+
# from fractal_server.app.history.status_enum import HistoryItemImageStatus
|
7
|
+
# from fractal_server.app.models.v2 import HistoryItemV2
|
8
|
+
# from fractal_server.app.models.v2 import ImageStatus
|
9
|
+
# from fractal_server.logger import set_logger
|
10
|
+
# logger = set_logger(__name__)
|
11
|
+
# def _update_single_image_status(
|
12
|
+
# *,
|
13
|
+
# zarr_url: str,
|
14
|
+
# workflowtask_id: int,
|
15
|
+
# dataset_id: int,
|
16
|
+
# status: HistoryItemImageStatus,
|
17
|
+
# db: Session,
|
18
|
+
# commit: bool = True,
|
19
|
+
# logfile: Optional[str] = None,
|
20
|
+
# ) -> None:
|
21
|
+
# image_status = db.get(
|
22
|
+
# ImageStatus,
|
23
|
+
# (
|
24
|
+
# zarr_url,
|
25
|
+
# workflowtask_id,
|
26
|
+
# dataset_id,
|
27
|
+
# ),
|
28
|
+
# )
|
29
|
+
# if image_status is None:
|
30
|
+
# raise RuntimeError("This should have not happened")
|
31
|
+
# image_status.status = status
|
32
|
+
# if logfile is not None:
|
33
|
+
# image_status.logfile = logfile
|
34
|
+
# db.add(image_status)
|
35
|
+
# if commit:
|
36
|
+
# db.commit()
|
37
|
+
# def update_single_image(
|
38
|
+
# *,
|
39
|
+
# history_item_id: int,
|
40
|
+
# zarr_url: str,
|
41
|
+
# status: HistoryItemImageStatus,
|
42
|
+
# ) -> None:
|
43
|
+
# logger.debug(
|
44
|
+
# f"[update_single_image] {history_item_id=}, {status=}, {zarr_url=}"
|
45
|
+
# )
|
46
|
+
# # Note: thanks to `with_for_update`, a lock is acquired and kept
|
47
|
+
# # until `db.commit()`
|
48
|
+
# with next(get_sync_db()) as db:
|
49
|
+
# stm = (
|
50
|
+
# select(HistoryItemV2)
|
51
|
+
# .where(HistoryItemV2.id == history_item_id)
|
52
|
+
# .with_for_update(nowait=False)
|
53
|
+
# )
|
54
|
+
# history_item = db.execute(stm).scalar_one()
|
55
|
+
# history_item.images[zarr_url] = status
|
56
|
+
# flag_modified(history_item, "images")
|
57
|
+
# db.commit()
|
58
|
+
# _update_single_image_status(
|
59
|
+
# zarr_url=zarr_url,
|
60
|
+
# dataset_id=history_item.dataset_id,
|
61
|
+
# workflowtask_id=history_item.workflowtask_id,
|
62
|
+
# commit=True,
|
63
|
+
# status=status,
|
64
|
+
# db=db,
|
65
|
+
# )
|
66
|
+
# def update_single_image_logfile(
|
67
|
+
# *,
|
68
|
+
# history_item_id: int,
|
69
|
+
# zarr_url: str,
|
70
|
+
# logfile: str,
|
71
|
+
# ) -> None:
|
72
|
+
# logger.debug(
|
73
|
+
# "[update_single_image_logfile] "
|
74
|
+
# f"{history_item_id=}, {logfile=}, {zarr_url=}"
|
75
|
+
# )
|
76
|
+
# with next(get_sync_db()) as db:
|
77
|
+
# history_item = db.get(HistoryItemV2, history_item_id)
|
78
|
+
# image_status = db.get(
|
79
|
+
# ImageStatus,
|
80
|
+
# (
|
81
|
+
# zarr_url,
|
82
|
+
# history_item.workflowtask_id,
|
83
|
+
# history_item.dataset_id,
|
84
|
+
# ),
|
85
|
+
# )
|
86
|
+
# if image_status is None:
|
87
|
+
# raise RuntimeError("This should have not happened")
|
88
|
+
# image_status.logfile = logfile
|
89
|
+
# db.merge(image_status)
|
90
|
+
# db.commit()
|
91
|
+
# def update_all_images(
|
92
|
+
# *,
|
93
|
+
# history_item_id: int,
|
94
|
+
# status: HistoryItemImageStatus,
|
95
|
+
# logfile: Optional[str] = None,
|
96
|
+
# ) -> None:
|
97
|
+
# logger.debug(f"[update_all_images] {history_item_id=}, {status=}")
|
98
|
+
# # Note: thanks to `with_for_update`, a lock is acquired and kept
|
99
|
+
# # until `db.commit()`
|
100
|
+
# stm = (
|
101
|
+
# select(HistoryItemV2)
|
102
|
+
# .where(HistoryItemV2.id == history_item_id)
|
103
|
+
# .with_for_update(nowait=False)
|
104
|
+
# )
|
105
|
+
# with next(get_sync_db()) as db:
|
106
|
+
# history_item = db.execute(stm).scalar_one()
|
107
|
+
# new_images = {
|
108
|
+
# zarr_url: status for zarr_url in history_item.images.keys()
|
109
|
+
# }
|
110
|
+
# history_item.images = new_images
|
111
|
+
# flag_modified(history_item, "images")
|
112
|
+
# db.commit()
|
113
|
+
# # FIXME: Make this a bulk edit, if possible
|
114
|
+
# for ind, zarr_url in enumerate(history_item.images.keys()):
|
115
|
+
# _update_single_image_status(
|
116
|
+
# zarr_url=zarr_url,
|
117
|
+
# dataset_id=history_item.dataset_id,
|
118
|
+
# workflowtask_id=history_item.workflowtask_id,
|
119
|
+
# commit=False,
|
120
|
+
# status=status,
|
121
|
+
# logfile=logfile,
|
122
|
+
# db=db,
|
123
|
+
# )
|
124
|
+
# db.commit()
|
@@ -5,8 +5,9 @@ from ..linkuserproject import LinkUserProjectV2
|
|
5
5
|
from .accounting import AccountingRecord
|
6
6
|
from .accounting import AccountingRecordSlurm
|
7
7
|
from .dataset import DatasetV2
|
8
|
-
from .history import
|
9
|
-
from .history import
|
8
|
+
from .history import HistoryImageCache
|
9
|
+
from .history import HistoryRun
|
10
|
+
from .history import HistoryUnit
|
10
11
|
from .job import JobV2
|
11
12
|
from .project import ProjectV2
|
12
13
|
from .task import TaskV2
|
@@ -27,6 +28,7 @@ __all__ = [
|
|
27
28
|
"TaskV2",
|
28
29
|
"WorkflowV2",
|
29
30
|
"WorkflowTaskV2",
|
30
|
-
"
|
31
|
-
"
|
31
|
+
"HistoryRun",
|
32
|
+
"HistoryUnit",
|
33
|
+
"HistoryImageCache",
|
32
34
|
]
|
@@ -4,6 +4,8 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
from pydantic import ConfigDict
|
6
6
|
from sqlalchemy import Column
|
7
|
+
from sqlalchemy import String
|
8
|
+
from sqlalchemy.dialects.postgresql import ARRAY
|
7
9
|
from sqlalchemy.dialects.postgresql import JSONB
|
8
10
|
from sqlalchemy.types import DateTime
|
9
11
|
from sqlmodel import Field
|
@@ -12,42 +14,64 @@ from sqlmodel import SQLModel
|
|
12
14
|
from ....utils import get_timestamp
|
13
15
|
|
14
16
|
|
15
|
-
class
|
17
|
+
class HistoryRun(SQLModel, table=True):
|
16
18
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
17
19
|
|
18
20
|
id: Optional[int] = Field(default=None, primary_key=True)
|
19
|
-
dataset_id: int = Field(
|
21
|
+
dataset_id: int = Field(
|
22
|
+
foreign_key="datasetv2.id",
|
23
|
+
ondelete="CASCADE",
|
24
|
+
)
|
20
25
|
workflowtask_id: Optional[int] = Field(
|
21
26
|
foreign_key="workflowtaskv2.id",
|
22
27
|
default=None,
|
28
|
+
ondelete="SET NULL",
|
23
29
|
)
|
24
|
-
|
25
|
-
default_factory=get_timestamp,
|
26
|
-
sa_column=Column(
|
27
|
-
DateTime(timezone=True),
|
28
|
-
nullable=False,
|
29
|
-
),
|
30
|
-
)
|
30
|
+
|
31
31
|
workflowtask_dump: dict[str, Any] = Field(
|
32
|
-
sa_column=Column(JSONB, nullable=False)
|
32
|
+
sa_column=Column(JSONB, nullable=False),
|
33
33
|
)
|
34
34
|
task_group_dump: dict[str, Any] = Field(
|
35
|
-
sa_column=Column(JSONB, nullable=False)
|
35
|
+
sa_column=Column(JSONB, nullable=False),
|
36
36
|
)
|
37
|
-
|
37
|
+
|
38
|
+
timestamp_started: datetime = Field(
|
39
|
+
sa_column=Column(DateTime(timezone=True), nullable=False),
|
40
|
+
default_factory=get_timestamp,
|
41
|
+
)
|
42
|
+
status: str
|
38
43
|
num_available_images: int
|
39
|
-
num_current_images: int
|
40
|
-
images: dict[str, str] = Field(sa_column=Column(JSONB, nullable=False))
|
41
44
|
|
42
45
|
|
43
|
-
class
|
46
|
+
class HistoryUnit(SQLModel, table=True):
|
47
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
48
|
+
history_run_id: int = Field(
|
49
|
+
foreign_key="historyrun.id",
|
50
|
+
ondelete="CASCADE",
|
51
|
+
)
|
52
|
+
|
53
|
+
logfile: Optional[str]
|
54
|
+
status: str
|
55
|
+
zarr_urls: list[str] = Field(
|
56
|
+
sa_column=Column(ARRAY(String)),
|
57
|
+
default_factory=list,
|
58
|
+
)
|
59
|
+
|
44
60
|
|
61
|
+
class HistoryImageCache(SQLModel, table=True):
|
45
62
|
zarr_url: str = Field(primary_key=True)
|
63
|
+
dataset_id: int = Field(
|
64
|
+
primary_key=True,
|
65
|
+
foreign_key="datasetv2.id",
|
66
|
+
ondelete="CASCADE",
|
67
|
+
)
|
46
68
|
workflowtask_id: int = Field(
|
47
|
-
primary_key=True,
|
69
|
+
primary_key=True,
|
70
|
+
foreign_key="workflowtaskv2.id",
|
71
|
+
ondelete="CASCADE",
|
48
72
|
)
|
49
|
-
dataset_id: int = Field(primary_key=True, foreign_key="datasetv2.id")
|
50
73
|
|
51
|
-
|
52
|
-
|
53
|
-
|
74
|
+
latest_history_unit_id: int = Field(
|
75
|
+
foreign_key="historyunit.id",
|
76
|
+
ondelete="CASCADE",
|
77
|
+
)
|
@@ -8,6 +8,7 @@ from .history import router as history_router_v2
|
|
8
8
|
from .images import router as images_routes_v2
|
9
9
|
from .job import router as job_router_v2
|
10
10
|
from .project import router as project_router_v2
|
11
|
+
from .status_legacy import router as status_legacy_router_v2
|
11
12
|
from .submit import router as submit_job_router_v2
|
12
13
|
from .task import router as task_router_v2
|
13
14
|
from .task_collection import router as task_collection_router_v2
|
@@ -29,6 +30,9 @@ router_api_v2.include_router(images_routes_v2, tags=["V2 Images"])
|
|
29
30
|
router_api_v2.include_router(project_router_v2, tags=["V2 Project"])
|
30
31
|
router_api_v2.include_router(submit_job_router_v2, tags=["V2 Job"])
|
31
32
|
router_api_v2.include_router(history_router_v2, tags=["V2 History"])
|
33
|
+
router_api_v2.include_router(
|
34
|
+
status_legacy_router_v2, tags=["V2 Status Legacy"]
|
35
|
+
)
|
32
36
|
|
33
37
|
|
34
38
|
settings = Inject(get_settings)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from fastapi import HTTPException
|
4
|
+
from fastapi import status
|
5
|
+
|
6
|
+
from fractal_server.app.db import AsyncSession
|
7
|
+
from fractal_server.app.models import WorkflowTaskV2
|
8
|
+
from fractal_server.app.models.v2 import HistoryUnit
|
9
|
+
|
10
|
+
|
11
|
+
async def get_history_unit_or_404(
|
12
|
+
*, history_unit_id: int, db: AsyncSession
|
13
|
+
) -> HistoryUnit:
|
14
|
+
"""
|
15
|
+
Get an existing HistoryUnit or raise a 404.
|
16
|
+
|
17
|
+
Arguments:
|
18
|
+
history_unit_id: The `HistoryUnit` id
|
19
|
+
db: An asynchronous db session
|
20
|
+
"""
|
21
|
+
history_unit = await db.get(HistoryUnit, history_unit_id)
|
22
|
+
if history_unit is None:
|
23
|
+
raise HTTPException(
|
24
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
25
|
+
detail=f"HistoryUnit {history_unit_id} not found",
|
26
|
+
)
|
27
|
+
return history_unit
|
28
|
+
|
29
|
+
|
30
|
+
def read_log_file(
|
31
|
+
*,
|
32
|
+
logfile: str | None,
|
33
|
+
wftask: WorkflowTaskV2,
|
34
|
+
dataset_id: int,
|
35
|
+
):
|
36
|
+
if logfile is None or not Path(logfile).exists():
|
37
|
+
return (
|
38
|
+
f"Logs for task '{wftask.task.name}' in dataset "
|
39
|
+
f"{dataset_id} are not available."
|
40
|
+
)
|
41
|
+
|
42
|
+
try:
|
43
|
+
with open(logfile, "r") as f:
|
44
|
+
return f.read()
|
45
|
+
except Exception as e:
|
46
|
+
return (
|
47
|
+
f"Error while retrieving logs for task '{wftask.task.name}' "
|
48
|
+
f"in dataset {dataset_id}. Original error: {str(e)}."
|
49
|
+
)
|
@@ -5,14 +5,11 @@ from fastapi import Depends
|
|
5
5
|
from fastapi import HTTPException
|
6
6
|
from fastapi import Response
|
7
7
|
from fastapi import status
|
8
|
-
from sqlmodel import delete
|
9
8
|
from sqlmodel import select
|
10
9
|
|
11
10
|
from ....db import AsyncSession
|
12
11
|
from ....db import get_async_db
|
13
12
|
from ....models.v2 import DatasetV2
|
14
|
-
from ....models.v2 import HistoryItemV2
|
15
|
-
from ....models.v2 import ImageStatus
|
16
13
|
from ....models.v2 import JobV2
|
17
14
|
from ....models.v2 import ProjectV2
|
18
15
|
from ....schemas.v2 import DatasetCreateV2
|
@@ -223,15 +220,6 @@ async def delete_dataset(
|
|
223
220
|
for job in jobs:
|
224
221
|
job.dataset_id = None
|
225
222
|
|
226
|
-
# Cascade operations: delete history items and image status which are in
|
227
|
-
# relationship with the current dataset
|
228
|
-
|
229
|
-
stm = delete(HistoryItemV2).where(HistoryItemV2.dataset_id == dataset_id)
|
230
|
-
await db.execute(stm)
|
231
|
-
|
232
|
-
stm = delete(ImageStatus).where(ImageStatus.dataset_id == dataset_id)
|
233
|
-
await db.execute(stm)
|
234
|
-
|
235
223
|
# Delete dataset
|
236
224
|
await db.delete(dataset)
|
237
225
|
await db.commit()
|