fractal-server 2.4.2__py3-none-any.whl → 2.5.0a0__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/app/db/__init__.py +4 -1
- fractal_server/app/models/v1/task.py +0 -5
- fractal_server/app/models/v2/workflowtask.py +2 -10
- fractal_server/app/routes/admin/v2.py +0 -30
- fractal_server/app/routes/api/v2/__init__.py +0 -4
- fractal_server/app/routes/api/v2/_aux_functions.py +11 -46
- fractal_server/app/routes/api/v2/workflow.py +23 -54
- fractal_server/app/routes/api/v2/workflowtask.py +9 -33
- fractal_server/app/runner/v2/__init__.py +1 -4
- fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +1 -4
- fractal_server/app/runner/v2/handle_failed_job.py +2 -9
- fractal_server/app/runner/v2/runner.py +42 -70
- fractal_server/app/runner/v2/runner_functions.py +0 -58
- fractal_server/app/runner/v2/runner_functions_low_level.py +7 -21
- fractal_server/app/schemas/v2/__init__.py +0 -1
- fractal_server/app/schemas/v2/dumps.py +2 -23
- fractal_server/app/schemas/v2/task.py +0 -5
- fractal_server/app/schemas/v2/workflowtask.py +4 -29
- fractal_server/migrations/env.py +4 -7
- fractal_server/migrations/naming_convention.py +7 -0
- fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +1 -1
- fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +97 -0
- {fractal_server-2.4.2.dist-info → fractal_server-2.5.0a0.dist-info}/METADATA +1 -1
- {fractal_server-2.4.2.dist-info → fractal_server-2.5.0a0.dist-info}/RECORD +28 -28
- fractal_server/app/routes/api/v2/task_legacy.py +0 -59
- fractal_server/app/runner/v2/v1_compat.py +0 -31
- {fractal_server-2.4.2.dist-info → fractal_server-2.5.0a0.dist-info}/LICENSE +0 -0
- {fractal_server-2.4.2.dist-info → fractal_server-2.5.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-2.4.2.dist-info → fractal_server-2.5.0a0.dist-info}/entry_points.txt +0 -0
@@ -16,8 +16,6 @@ from .merge_outputs import merge_outputs
|
|
16
16
|
from .runner_functions_low_level import run_single_task
|
17
17
|
from .task_interface import InitTaskOutput
|
18
18
|
from .task_interface import TaskOutput
|
19
|
-
from .v1_compat import convert_v2_args_into_v1
|
20
|
-
from fractal_server.app.models.v1 import Task as TaskV1
|
21
19
|
from fractal_server.app.models.v2 import TaskV2
|
22
20
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
23
21
|
from fractal_server.app.runner.components import _COMPONENT_KEY_
|
@@ -28,7 +26,6 @@ __all__ = [
|
|
28
26
|
"run_v2_task_non_parallel",
|
29
27
|
"run_v2_task_parallel",
|
30
28
|
"run_v2_task_compound",
|
31
|
-
"run_v1_task_parallel",
|
32
29
|
]
|
33
30
|
|
34
31
|
MAX_PARALLELIZATION_LIST_SIZE = 20_000
|
@@ -317,58 +314,3 @@ def run_v2_task_compound(
|
|
317
314
|
|
318
315
|
merged_output = merge_outputs(outputs)
|
319
316
|
return merged_output
|
320
|
-
|
321
|
-
|
322
|
-
def run_v1_task_parallel(
|
323
|
-
*,
|
324
|
-
images: list[dict[str, Any]],
|
325
|
-
task_legacy: TaskV1,
|
326
|
-
wftask: WorkflowTaskV2,
|
327
|
-
executor: Executor,
|
328
|
-
workflow_dir_local: Path,
|
329
|
-
workflow_dir_remote: Optional[Path] = None,
|
330
|
-
logger_name: Optional[str] = None,
|
331
|
-
submit_setup_call: Callable = no_op_submit_setup_call,
|
332
|
-
) -> TaskOutput:
|
333
|
-
|
334
|
-
_check_parallelization_list_size(images)
|
335
|
-
|
336
|
-
executor_options = _get_executor_options(
|
337
|
-
wftask=wftask,
|
338
|
-
workflow_dir_local=workflow_dir_local,
|
339
|
-
workflow_dir_remote=workflow_dir_remote,
|
340
|
-
submit_setup_call=submit_setup_call,
|
341
|
-
which_type="parallel",
|
342
|
-
)
|
343
|
-
|
344
|
-
list_function_kwargs = []
|
345
|
-
for ind, image in enumerate(images):
|
346
|
-
list_function_kwargs.append(
|
347
|
-
convert_v2_args_into_v1(
|
348
|
-
kwargs_v2=dict(
|
349
|
-
zarr_url=image["zarr_url"],
|
350
|
-
**(wftask.args_parallel or {}),
|
351
|
-
),
|
352
|
-
parallelization_level=task_legacy.parallelization_level,
|
353
|
-
),
|
354
|
-
)
|
355
|
-
list_function_kwargs[-1][_COMPONENT_KEY_] = _index_to_component(ind)
|
356
|
-
|
357
|
-
results_iterator = executor.map(
|
358
|
-
functools.partial(
|
359
|
-
run_single_task,
|
360
|
-
wftask=wftask,
|
361
|
-
command=task_legacy.command,
|
362
|
-
workflow_dir_local=workflow_dir_local,
|
363
|
-
workflow_dir_remote=workflow_dir_remote,
|
364
|
-
is_task_v1=True,
|
365
|
-
),
|
366
|
-
list_function_kwargs,
|
367
|
-
**executor_options,
|
368
|
-
)
|
369
|
-
# Explicitly iterate over the whole list, so that all futures are waited
|
370
|
-
list(results_iterator)
|
371
|
-
|
372
|
-
# Ignore any output metadata for V1 tasks, and return an empty object
|
373
|
-
out = TaskOutput()
|
374
|
-
return out
|
@@ -61,7 +61,6 @@ def run_single_task(
|
|
61
61
|
workflow_dir_local: Path,
|
62
62
|
workflow_dir_remote: Optional[Path] = None,
|
63
63
|
logger_name: Optional[str] = None,
|
64
|
-
is_task_v1: bool = False,
|
65
64
|
) -> dict[str, Any]:
|
66
65
|
"""
|
67
66
|
Runs within an executor.
|
@@ -73,10 +72,7 @@ def run_single_task(
|
|
73
72
|
if not workflow_dir_remote:
|
74
73
|
workflow_dir_remote = workflow_dir_local
|
75
74
|
|
76
|
-
|
77
|
-
task_name = wftask.task_legacy.name
|
78
|
-
else:
|
79
|
-
task_name = wftask.task.name
|
75
|
+
task_name = wftask.task.name
|
80
76
|
|
81
77
|
component = args.pop(_COMPONENT_KEY_, None)
|
82
78
|
task_files = get_task_file_paths(
|
@@ -92,18 +88,11 @@ def run_single_task(
|
|
92
88
|
json.dump(args, f, indent=2)
|
93
89
|
|
94
90
|
# Assemble full command
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
)
|
101
|
-
else:
|
102
|
-
full_command = (
|
103
|
-
f"{command} "
|
104
|
-
f"--args-json {task_files.args.as_posix()} "
|
105
|
-
f"--out-json {task_files.metadiff.as_posix()}"
|
106
|
-
)
|
91
|
+
full_command = (
|
92
|
+
f"{command} "
|
93
|
+
f"--args-json {task_files.args.as_posix()} "
|
94
|
+
f"--out-json {task_files.metadiff.as_posix()}"
|
95
|
+
)
|
107
96
|
|
108
97
|
try:
|
109
98
|
_call_command_wrapper(
|
@@ -113,10 +102,7 @@ def run_single_task(
|
|
113
102
|
except TaskExecutionError as e:
|
114
103
|
e.workflow_task_order = wftask.order
|
115
104
|
e.workflow_task_id = wftask.id
|
116
|
-
|
117
|
-
e.task_name = wftask.task_legacy.name
|
118
|
-
else:
|
119
|
-
e.task_name = wftask.task.name
|
105
|
+
e.task_name = wftask.task.name
|
120
106
|
raise e
|
121
107
|
|
122
108
|
try:
|
@@ -20,7 +20,6 @@ from .project import ProjectUpdateV2 # noqa F401
|
|
20
20
|
from .task import TaskCreateV2 # noqa F401
|
21
21
|
from .task import TaskExportV2 # noqa F401
|
22
22
|
from .task import TaskImportV2 # noqa F401
|
23
|
-
from .task import TaskLegacyReadV2 # noqa F401
|
24
23
|
from .task import TaskReadV2 # noqa F401
|
25
24
|
from .task import TaskUpdateV2 # noqa F401
|
26
25
|
from .task_collection import CollectionStateReadV2 # noqa F401
|
@@ -12,9 +12,7 @@ from typing import Optional
|
|
12
12
|
|
13
13
|
from pydantic import BaseModel
|
14
14
|
from pydantic import Extra
|
15
|
-
from pydantic import root_validator
|
16
15
|
|
17
|
-
from fractal_server.app.schemas.v1.dumps import TaskDumpV1
|
18
16
|
from fractal_server.images import Filters
|
19
17
|
|
20
18
|
|
@@ -45,29 +43,10 @@ class WorkflowTaskDumpV2(BaseModel):
|
|
45
43
|
workflow_id: int
|
46
44
|
order: Optional[int]
|
47
45
|
|
48
|
-
is_legacy_task: bool
|
49
|
-
|
50
46
|
input_filters: Filters
|
51
47
|
|
52
|
-
task_id:
|
53
|
-
task:
|
54
|
-
task_legacy_id: Optional[int]
|
55
|
-
task_legacy: Optional[TaskDumpV1]
|
56
|
-
|
57
|
-
# Validators
|
58
|
-
@root_validator
|
59
|
-
def task_v1_or_v2(cls, values):
|
60
|
-
v1 = values.get("task_legacy_id")
|
61
|
-
v2 = values.get("task_id")
|
62
|
-
if ((v1 is not None) and (v2 is not None)) or (
|
63
|
-
(v1 is None) and (v2 is None)
|
64
|
-
):
|
65
|
-
message = "both" if (v1 and v2) else "none"
|
66
|
-
raise ValueError(
|
67
|
-
"One and only one must be provided between "
|
68
|
-
f"'task_legacy_id' and 'task_id' (you provided {message})"
|
69
|
-
)
|
70
|
-
return values
|
48
|
+
task_id: int
|
49
|
+
task: TaskDumpV2
|
71
50
|
|
72
51
|
|
73
52
|
class WorkflowDumpV2(BaseModel, extra=Extra.forbid):
|
@@ -11,7 +11,6 @@ from pydantic import validator
|
|
11
11
|
|
12
12
|
from .._validators import valdictkeys
|
13
13
|
from .._validators import valstr
|
14
|
-
from ..v1.task import TaskReadV1
|
15
14
|
|
16
15
|
|
17
16
|
class TaskCreateV2(BaseModel, extra=Extra.forbid):
|
@@ -101,10 +100,6 @@ class TaskReadV2(BaseModel):
|
|
101
100
|
output_types: dict[str, bool]
|
102
101
|
|
103
102
|
|
104
|
-
class TaskLegacyReadV2(TaskReadV1):
|
105
|
-
is_v2_compatible: bool
|
106
|
-
|
107
|
-
|
108
103
|
class TaskUpdateV2(BaseModel):
|
109
104
|
|
110
105
|
name: Optional[str]
|
@@ -5,16 +5,12 @@ from typing import Optional
|
|
5
5
|
from pydantic import BaseModel
|
6
6
|
from pydantic import Extra
|
7
7
|
from pydantic import Field
|
8
|
-
from pydantic import root_validator
|
9
8
|
from pydantic import validator
|
10
9
|
|
11
10
|
from .._validators import valdictkeys
|
12
11
|
from .._validators import valint
|
13
|
-
from ..v1.task import TaskExportV1
|
14
|
-
from ..v1.task import TaskImportV1
|
15
12
|
from .task import TaskExportV2
|
16
13
|
from .task import TaskImportV2
|
17
|
-
from .task import TaskLegacyReadV2
|
18
14
|
from .task import TaskReadV2
|
19
15
|
from fractal_server.images import Filters
|
20
16
|
|
@@ -49,8 +45,6 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
49
45
|
order: Optional[int]
|
50
46
|
input_filters: Filters = Field(default_factory=Filters)
|
51
47
|
|
52
|
-
is_legacy_task: bool = False
|
53
|
-
|
54
48
|
# Validators
|
55
49
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
56
50
|
valdictkeys("meta_non_parallel")
|
@@ -88,18 +82,6 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
88
82
|
)
|
89
83
|
return value
|
90
84
|
|
91
|
-
@root_validator
|
92
|
-
def validate_legacy_task(cls, values):
|
93
|
-
if values["is_legacy_task"] and (
|
94
|
-
values.get("meta_non_parallel") is not None
|
95
|
-
or values.get("args_non_parallel") is not None
|
96
|
-
):
|
97
|
-
raise ValueError(
|
98
|
-
"If Task is legacy, 'args_non_parallel' and 'meta_non_parallel"
|
99
|
-
"must be None"
|
100
|
-
)
|
101
|
-
return values
|
102
|
-
|
103
85
|
|
104
86
|
class WorkflowTaskReadV2(BaseModel):
|
105
87
|
|
@@ -115,12 +97,9 @@ class WorkflowTaskReadV2(BaseModel):
|
|
115
97
|
|
116
98
|
input_filters: Filters
|
117
99
|
|
118
|
-
is_legacy_task: bool
|
119
100
|
task_type: str
|
120
|
-
task_id:
|
121
|
-
task:
|
122
|
-
task_legacy_id: Optional[int]
|
123
|
-
task_legacy: Optional[TaskLegacyReadV2]
|
101
|
+
task_id: int
|
102
|
+
task: TaskReadV2
|
124
103
|
|
125
104
|
|
126
105
|
class WorkflowTaskUpdateV2(BaseModel):
|
@@ -177,9 +156,7 @@ class WorkflowTaskImportV2(BaseModel):
|
|
177
156
|
|
178
157
|
input_filters: Optional[Filters] = None
|
179
158
|
|
180
|
-
|
181
|
-
task: Optional[TaskImportV2] = None
|
182
|
-
task_legacy: Optional[TaskImportV1] = None
|
159
|
+
task: TaskImportV2
|
183
160
|
|
184
161
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
185
162
|
valdictkeys("meta_non_parallel")
|
@@ -203,6 +180,4 @@ class WorkflowTaskExportV2(BaseModel):
|
|
203
180
|
args_parallel: Optional[dict[str, Any]] = None
|
204
181
|
input_filters: Filters = Field(default_factory=Filters)
|
205
182
|
|
206
|
-
|
207
|
-
task: Optional[TaskExportV2]
|
208
|
-
task_legacy: Optional[TaskExportV1]
|
183
|
+
task: TaskExportV2
|
fractal_server/migrations/env.py
CHANGED
@@ -7,6 +7,7 @@ from sqlmodel import SQLModel
|
|
7
7
|
|
8
8
|
from fractal_server.app import models # noqa
|
9
9
|
from fractal_server.config import get_settings
|
10
|
+
from fractal_server.migrations.naming_convention import NAMING_CONVENTION
|
10
11
|
from fractal_server.syringe import Inject
|
11
12
|
|
12
13
|
# this is the Alembic Config object, which provides
|
@@ -25,13 +26,7 @@ if config.config_file_name is not None:
|
|
25
26
|
# from myapp import mymodel
|
26
27
|
# target_metadata = mymodel.Base.metadata
|
27
28
|
target_metadata = SQLModel.metadata
|
28
|
-
target_metadata.naming_convention =
|
29
|
-
"ix": "ix_%(column_0_label)s",
|
30
|
-
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
31
|
-
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
32
|
-
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
33
|
-
"pk": "pk_%(table_name)s",
|
34
|
-
}
|
29
|
+
target_metadata.naming_convention = NAMING_CONVENTION
|
35
30
|
|
36
31
|
# other values from the config, defined by the needs of env.py,
|
37
32
|
# can be acquired:
|
@@ -58,6 +53,7 @@ def run_migrations_offline() -> None:
|
|
58
53
|
target_metadata=target_metadata,
|
59
54
|
literal_binds=True,
|
60
55
|
dialect_opts={"paramstyle": "named"},
|
56
|
+
render_as_batch=True,
|
61
57
|
)
|
62
58
|
|
63
59
|
with context.begin_transaction():
|
@@ -68,6 +64,7 @@ def do_run_migrations(connection: Connection) -> None:
|
|
68
64
|
context.configure(
|
69
65
|
connection=connection,
|
70
66
|
target_metadata=target_metadata,
|
67
|
+
render_as_batch=True,
|
71
68
|
)
|
72
69
|
|
73
70
|
with context.begin_transaction():
|
@@ -0,0 +1,97 @@
|
|
1
|
+
"""Remove link between v1 and v2 tasks/workflowtasks tables
|
2
|
+
|
3
|
+
Revision ID: d9a140db5d42
|
4
|
+
Revises: 5bf02391cfef
|
5
|
+
Create Date: 2024-09-09 14:15:34.415926
|
6
|
+
|
7
|
+
"""
|
8
|
+
import sqlalchemy as sa
|
9
|
+
from alembic import op
|
10
|
+
|
11
|
+
from fractal_server.migrations.naming_convention import NAMING_CONVENTION
|
12
|
+
|
13
|
+
# revision identifiers, used by Alembic.
|
14
|
+
revision = "d9a140db5d42"
|
15
|
+
down_revision = "091b01f51f88"
|
16
|
+
branch_labels = None
|
17
|
+
depends_on = None
|
18
|
+
|
19
|
+
|
20
|
+
def upgrade() -> None:
|
21
|
+
|
22
|
+
with op.batch_alter_table("workflowtaskv2") as batch_op:
|
23
|
+
batch_op.alter_column(
|
24
|
+
"task_id", existing_type=sa.INTEGER(), nullable=False
|
25
|
+
)
|
26
|
+
|
27
|
+
# NOTE: in sqlite, this `drop_constraint` only works if
|
28
|
+
# `batch_alter_table` has a `naming_convention` set. Ref
|
29
|
+
# https://alembic.sqlalchemy.org/en/latest/batch.html#dropping-unnamed-or-named-foreign-key-constraints
|
30
|
+
with op.batch_alter_table(
|
31
|
+
"workflowtaskv2", naming_convention=NAMING_CONVENTION
|
32
|
+
) as batch_op:
|
33
|
+
batch_op.drop_constraint(
|
34
|
+
"fk_workflowtaskv2_task_legacy_id_task", type_="foreignkey"
|
35
|
+
)
|
36
|
+
|
37
|
+
# NOTE: in sqlite, the `drop_index` command fails if the existing table
|
38
|
+
# has zero rows, while it succeeds if there are already some rows
|
39
|
+
if op.get_bind().dialect.name == "sqlite":
|
40
|
+
import sqlite3
|
41
|
+
import logging
|
42
|
+
|
43
|
+
logger = logging.getLogger("alembic.runtime.migration")
|
44
|
+
logger.warning(
|
45
|
+
f"Using sqlite, with {sqlite3.version=} and "
|
46
|
+
f"{sqlite3.sqlite_version=}"
|
47
|
+
)
|
48
|
+
logger.warning("Now drop index 'idx_workflowtaskv2_task_legacy_id'")
|
49
|
+
try:
|
50
|
+
with op.batch_alter_table("workflowtaskv2") as batch_op:
|
51
|
+
batch_op.drop_index("idx_workflowtaskv2_task_legacy_id")
|
52
|
+
except sa.exc.OperationalError:
|
53
|
+
logger.warning(
|
54
|
+
"Could not drop index; "
|
55
|
+
"this is expected, when the database is empty."
|
56
|
+
)
|
57
|
+
logger.warning("Continue.")
|
58
|
+
|
59
|
+
with op.batch_alter_table(
|
60
|
+
"workflowtaskv2", schema=None, naming_convention=NAMING_CONVENTION
|
61
|
+
) as batch_op:
|
62
|
+
batch_op.drop_column("is_legacy_task")
|
63
|
+
batch_op.drop_column("task_legacy_id")
|
64
|
+
|
65
|
+
with op.batch_alter_table("task") as batch_op:
|
66
|
+
batch_op.drop_column("is_v2_compatible")
|
67
|
+
|
68
|
+
|
69
|
+
def downgrade() -> None:
|
70
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
71
|
+
with op.batch_alter_table("task", schema=None) as batch_op:
|
72
|
+
batch_op.add_column(
|
73
|
+
sa.Column(
|
74
|
+
"is_v2_compatible",
|
75
|
+
sa.BOOLEAN(),
|
76
|
+
server_default=sa.text("(false)"),
|
77
|
+
nullable=False,
|
78
|
+
)
|
79
|
+
)
|
80
|
+
with op.batch_alter_table("workflowtaskv2", schema=None) as batch_op:
|
81
|
+
batch_op.add_column(
|
82
|
+
sa.Column("task_legacy_id", sa.INTEGER(), nullable=True)
|
83
|
+
)
|
84
|
+
batch_op.add_column(
|
85
|
+
sa.Column("is_legacy_task", sa.BOOLEAN(), nullable=False)
|
86
|
+
)
|
87
|
+
batch_op.create_foreign_key(
|
88
|
+
"fk_workflowtaskv2_task_legacy_id_task",
|
89
|
+
"task",
|
90
|
+
["task_legacy_id"],
|
91
|
+
["id"],
|
92
|
+
)
|
93
|
+
batch_op.alter_column(
|
94
|
+
"task_id", existing_type=sa.INTEGER(), nullable=True
|
95
|
+
)
|
96
|
+
|
97
|
+
# ### end Alembic commands ###
|
@@ -1,8 +1,8 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=_rI4eT5b6qxaE6SGCTem_2zE6Wjz5WRJOKhvLkZ4pMs,24
|
2
2
|
fractal_server/__main__.py,sha256=upYBkGYrkBnkS1rp4D_nb_1LS37QT4j-wxGX1ZMvR4A,5704
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
fractal_server/app/db/__init__.py,sha256=
|
5
|
+
fractal_server/app/db/__init__.py,sha256=81rK9w1__Z6PJ5cEcChPVc-wI9YOK4fN--_5Opry0MQ,4119
|
6
6
|
fractal_server/app/models/__init__.py,sha256=zt0Tiv91DWLg0daT8bkENz-LusihOJJX1h09UfHlAns,452
|
7
7
|
fractal_server/app/models/linkusergroup.py,sha256=ufthlbLFAWMU_dJmsVZzVlQa_D9C9SmgydxypQ2Xq1U,309
|
8
8
|
fractal_server/app/models/linkuserproject.py,sha256=eQaourbGRshvlMVlKzLYJKHEjfsW1CbWws9yW4eHXhA,567
|
@@ -12,7 +12,7 @@ fractal_server/app/models/v1/dataset.py,sha256=99GDgt7njx8yYQApkImqp_7bHA5HH3Elv
|
|
12
12
|
fractal_server/app/models/v1/job.py,sha256=QLGXcWdVRHaUHQNDapYYlLpEfw4K7QyD8TmcwhrWw2o,3304
|
13
13
|
fractal_server/app/models/v1/project.py,sha256=JG7b5J9CzVNxua4MaMYpfB57xt2qjbXr5SnR7_oKQ70,819
|
14
14
|
fractal_server/app/models/v1/state.py,sha256=m9gMZqqnm3oDpJNJp-Lht4kM7oO7pcEI7sL1g7LFvWU,1043
|
15
|
-
fractal_server/app/models/v1/task.py,sha256=
|
15
|
+
fractal_server/app/models/v1/task.py,sha256=uFXam7eu3Ye1Yt7_g7llCzY8BetmDRilsq5hR2C1Zbg,2640
|
16
16
|
fractal_server/app/models/v1/workflow.py,sha256=dnY5eMaOe3oZv8arn00RNX9qVkBtTLG-vYdWXcQuyo4,3950
|
17
17
|
fractal_server/app/models/v2/__init__.py,sha256=uLzdInqATSwi0bS_V4vKB-TqFrOFaXuxCAbU73c0f24,473
|
18
18
|
fractal_server/app/models/v2/collection_state.py,sha256=nxb042i8tt8rCpmgbFJoBCYWU-34m0HdUfO9YurTp8k,588
|
@@ -21,11 +21,11 @@ fractal_server/app/models/v2/job.py,sha256=ypJmN-qspkKBGhBG7Mt-HypSQqcQ2EmB4Bzzb
|
|
21
21
|
fractal_server/app/models/v2/project.py,sha256=rAHoh5KfYwIaW7rTX0_O0jvWmxEvfo1BafvmcXuSSRk,786
|
22
22
|
fractal_server/app/models/v2/task.py,sha256=Esf2j9c-0pGYjdbb__Ptpdx7NCAKVxqbQMoza524miU,1286
|
23
23
|
fractal_server/app/models/v2/workflow.py,sha256=YBgFGCziUgU0aJ5EM3Svu9W2c46AewZO9VBlFCHiSps,1069
|
24
|
-
fractal_server/app/models/v2/workflowtask.py,sha256=
|
24
|
+
fractal_server/app/models/v2/workflowtask.py,sha256=iDuJYk8kp4PNqGmbKRtGI7y-QsbjkNd_gDsbMzL4i-g,1274
|
25
25
|
fractal_server/app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
fractal_server/app/routes/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
27
|
fractal_server/app/routes/admin/v1.py,sha256=GIpZlwAwwwLGDWkBqywhtmp9TGsKLhGmZAdj1TDKJvE,13976
|
28
|
-
fractal_server/app/routes/admin/v2.py,sha256=
|
28
|
+
fractal_server/app/routes/admin/v2.py,sha256=hoEbH_sRJ-MaKMwhFp8c7SNObP0-Kmtgw1zbs_Z3nLY,12971
|
29
29
|
fractal_server/app/routes/api/__init__.py,sha256=2IDheFi0OFdsUg7nbUiyahqybvpgXqeHUXIL2QtWrQQ,641
|
30
30
|
fractal_server/app/routes/api/v1/__init__.py,sha256=Y2HQdG197J0a7DyQEE2jn53IfxD0EHGhzK1I2JZuEck,958
|
31
31
|
fractal_server/app/routes/api/v1/_aux_functions.py,sha256=1YZdLch-Q1c44hQ_1ZEiijgWhLW6H3QEL5y5SHw5Ijk,13023
|
@@ -36,8 +36,8 @@ fractal_server/app/routes/api/v1/task.py,sha256=OLASM6M4yfHvQgHQOuR5810jR2s0_W1H
|
|
36
36
|
fractal_server/app/routes/api/v1/task_collection.py,sha256=VYxhtd_idBppgJM7-FCHikI2OKMAIz05fhV_TsJpWI8,9060
|
37
37
|
fractal_server/app/routes/api/v1/workflow.py,sha256=2T93DuEnSshaDCue-JPmjuvGCtbk6lt9pFMuPt783t8,11217
|
38
38
|
fractal_server/app/routes/api/v1/workflowtask.py,sha256=OYYConwJbmNULDw5I3T-UbSJKrbbBiAHbbBeVcpoFKQ,5785
|
39
|
-
fractal_server/app/routes/api/v2/__init__.py,sha256=
|
40
|
-
fractal_server/app/routes/api/v2/_aux_functions.py,sha256=
|
39
|
+
fractal_server/app/routes/api/v2/__init__.py,sha256=301enf_GsL27_CnG6lSbMIeoz9-rrb3R2iDSs5pk4q8,1650
|
40
|
+
fractal_server/app/routes/api/v2/_aux_functions.py,sha256=OLDwaZpkCr6q2heebmceGY5YvuKQy0-mVkkfcCp6kEc,13550
|
41
41
|
fractal_server/app/routes/api/v2/dataset.py,sha256=Eilf_BAGjicIhqUiVwI86jlW45ineA5sVzxXW4b2GoQ,8329
|
42
42
|
fractal_server/app/routes/api/v2/images.py,sha256=JR1rR6qEs81nacjriOXAOBQjAbCXF4Ew7M7mkWdxBU0,7920
|
43
43
|
fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhKRjEH2_ayEE,5157
|
@@ -47,9 +47,8 @@ fractal_server/app/routes/api/v2/submit.py,sha256=iTGCYbxiZNszHQa8r3gmAR4QcF6QhV
|
|
47
47
|
fractal_server/app/routes/api/v2/task.py,sha256=XgRnGBvSoI9VNJHtWZQ2Ide99f6elo7a2FN3GQkf0dU,8376
|
48
48
|
fractal_server/app/routes/api/v2/task_collection.py,sha256=wEwP8VfsxhKPZ6K3v1Bnput_Zw0Cjhlaal0-e50feIQ,12337
|
49
49
|
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=6MW-l7xTCTbWKDSYDw8e_hnm5jFCJpgFL3UdvrHAaBk,6029
|
50
|
-
fractal_server/app/routes/api/v2/
|
51
|
-
fractal_server/app/routes/api/v2/
|
52
|
-
fractal_server/app/routes/api/v2/workflowtask.py,sha256=HoHFnVRDa0Cw1oqTea1Of6A5ZhjGJeSTDTdI-SKP7Co,8063
|
50
|
+
fractal_server/app/routes/api/v2/workflow.py,sha256=rMCcclz9aJAMSVLncUdSDGrgkKbn4KOCZTqZtqs2HDY,10428
|
51
|
+
fractal_server/app/routes/api/v2/workflowtask.py,sha256=-3-c8DDnxGjMwWbX_h5V5OLaC_iCLXYzwWKBUaL-5wE,7060
|
53
52
|
fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
|
54
53
|
fractal_server/app/routes/auth/_aux_auth.py,sha256=Kpgiw5q1eiCYLFkfhTT7XJGBu1d08YM71CEHhNtfJ5g,3126
|
55
54
|
fractal_server/app/routes/auth/current_user.py,sha256=LK0Z13NgaXYQ3FaQ3MNec0p2RRiKxKN31XIt2g9mcGk,2003
|
@@ -100,7 +99,7 @@ fractal_server/app/runner/v1/_slurm/_submit_setup.py,sha256=KO9c694d318adoPQh9UG
|
|
100
99
|
fractal_server/app/runner/v1/_slurm/get_slurm_config.py,sha256=6pQNNx997bLIfLp0guF09t_O0ZYRXnbEGLktSAcKnic,5999
|
101
100
|
fractal_server/app/runner/v1/common.py,sha256=_L-vjLnWato80VdlB_BFN4G8P4jSM07u-5cnl1T3S34,3294
|
102
101
|
fractal_server/app/runner/v1/handle_failed_job.py,sha256=bHzScC_aIlU3q-bQxGW6rfWV4xbZ2tho_sktjsAs1no,4684
|
103
|
-
fractal_server/app/runner/v2/__init__.py,sha256=
|
102
|
+
fractal_server/app/runner/v2/__init__.py,sha256=L9fiDphG3qa5w_zpBAaOeUqTezwSt8VRb6XXtkKrD58,17042
|
104
103
|
fractal_server/app/runner/v2/_local/__init__.py,sha256=KTj14K6jH8fXGUi5P7u5_RqEE1zF4aXtgPxCKzw46iw,5971
|
105
104
|
fractal_server/app/runner/v2/_local/_local_config.py,sha256=9oi209Dlp35ANfxb_DISqmMKKc6DPaMsmYVWbZLseME,3630
|
106
105
|
fractal_server/app/runner/v2/_local/_submit_setup.py,sha256=MucNOo8Er0F5ZIwH7CnTeXgnFMc6d3pKPkv563QNVi0,1630
|
@@ -110,19 +109,18 @@ fractal_server/app/runner/v2/_local_experimental/_local_config.py,sha256=QiS5ODe
|
|
110
109
|
fractal_server/app/runner/v2/_local_experimental/_submit_setup.py,sha256=we7r-sQf0CJ9gxbfbgHcYdC6pKjx8eXweljIjthxkv8,1212
|
111
110
|
fractal_server/app/runner/v2/_local_experimental/executor.py,sha256=vcBKjireIIyF5WgIQLatD6ojlWEydbTwyIG0bcpIjys,5438
|
112
111
|
fractal_server/app/runner/v2/_slurm_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
113
|
-
fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py,sha256=
|
112
|
+
fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py,sha256=UdkoFF0HF_TdKbay-d9bjkxT2ltcOE5i8H_FoOu64HU,6202
|
114
113
|
fractal_server/app/runner/v2/_slurm_ssh/__init__.py,sha256=D0Dnbhnzw0BXwQmjqLmxqpE9oreAtasA-9aOzxC4l_I,4530
|
115
114
|
fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py,sha256=a5_FDPH_yxYmrjAjMRLgh_Y4DSG3mRslCLQodGM3-t4,2838
|
116
115
|
fractal_server/app/runner/v2/_slurm_sudo/__init__.py,sha256=q2fwiKqtNpXtfs5wUFQjwJxdYqKPPTbCy1ieBhhi-Bw,4316
|
117
116
|
fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py,sha256=a5_FDPH_yxYmrjAjMRLgh_Y4DSG3mRslCLQodGM3-t4,2838
|
118
117
|
fractal_server/app/runner/v2/deduplicate_list.py,sha256=-imwO7OB7ATADEnqVbTElUwoY0YIJCTf_SbWJNN9OZg,639
|
119
|
-
fractal_server/app/runner/v2/handle_failed_job.py,sha256=
|
118
|
+
fractal_server/app/runner/v2/handle_failed_job.py,sha256=fipRJT5Y8UY0US4bXUX-4ORTAQ1AetZcCAOVCjDO3_c,5202
|
120
119
|
fractal_server/app/runner/v2/merge_outputs.py,sha256=IHuHqbKmk97K35BFvTrKVBs60z3e_--OzXTnsvmA02c,1281
|
121
|
-
fractal_server/app/runner/v2/runner.py,sha256=
|
122
|
-
fractal_server/app/runner/v2/runner_functions.py,sha256=
|
123
|
-
fractal_server/app/runner/v2/runner_functions_low_level.py,sha256=
|
120
|
+
fractal_server/app/runner/v2/runner.py,sha256=nw9oYt3cFItHWVoevJyMI63K0kWHCTAriAQ_KINo_F8,13039
|
121
|
+
fractal_server/app/runner/v2/runner_functions.py,sha256=BLREIcQaE6FSc2AEJyZuiYk6rGazEz_9gprUqUZDljs,9488
|
122
|
+
fractal_server/app/runner/v2/runner_functions_low_level.py,sha256=hXRjaPjBVWQ2HK7s2yhvqKC5Uc_K41MvW9kUm4KajTA,3453
|
124
123
|
fractal_server/app/runner/v2/task_interface.py,sha256=myS-kT0DsJ8xIJZBVEzgD8g54VbiwL6i7Im3e1zcVHQ,1866
|
125
|
-
fractal_server/app/runner/v2/v1_compat.py,sha256=t0ficzAHUFaaeI56nqTb4YEKxfARF7L9Y6ijtJCwjP8,912
|
126
124
|
fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
|
127
125
|
fractal_server/app/schemas/__init__.py,sha256=jiIf54owztXupv3PO6Ilh0qcrkh2RUzKq4bcEFqEfc4,40
|
128
126
|
fractal_server/app/schemas/_validators.py,sha256=1dTOYr1IZykrxuQSV2-zuEMZbKe_nGwrfS7iUrsh-sE,3461
|
@@ -138,17 +136,17 @@ fractal_server/app/schemas/v1/state.py,sha256=GYeOE_1PtDOgu5W4t_3gw3DBHXH2aCGzIN
|
|
138
136
|
fractal_server/app/schemas/v1/task.py,sha256=7BxOZ_qoRQ8n3YbQpDvB7VMcxB5fSYQmR5RLIWhuJ5U,3704
|
139
137
|
fractal_server/app/schemas/v1/task_collection.py,sha256=uvq9bcMaGD_qHsh7YtcpoSAkVAbw12eY4DocIO3MKOg,3057
|
140
138
|
fractal_server/app/schemas/v1/workflow.py,sha256=tuOs5E5Q_ozA8if7YPZ07cQjzqB_QMkBS4u92qo4Ro0,4618
|
141
|
-
fractal_server/app/schemas/v2/__init__.py,sha256=
|
139
|
+
fractal_server/app/schemas/v2/__init__.py,sha256=kmM4NfSGIL0I4xVtnmMST20kfVo3nBBG-Ssk8vJAvLs,1979
|
142
140
|
fractal_server/app/schemas/v2/dataset.py,sha256=dLT52tV4dSf2HrFNak4vdQEn8PT_04IUrGnd2z-AXIU,2599
|
143
|
-
fractal_server/app/schemas/v2/dumps.py,sha256=
|
141
|
+
fractal_server/app/schemas/v2/dumps.py,sha256=_iVgV98jb3gaKcOxX_halQFureKE81KTRIqbJsefw1s,1332
|
144
142
|
fractal_server/app/schemas/v2/job.py,sha256=zfF9K3v4jWUJ7M482ta2CkqUJ4tVT4XfVt60p9IRhP0,3250
|
145
143
|
fractal_server/app/schemas/v2/manifest.py,sha256=N37IWohcfO3_y2l8rVM0h_1nZq7m4Izxk9iL1vtwBJw,6243
|
146
144
|
fractal_server/app/schemas/v2/project.py,sha256=u7S4B-bote1oGjzAGiZ-DuQIyeRAGqJsI71Tc1EtYE0,736
|
147
145
|
fractal_server/app/schemas/v2/status.py,sha256=SQaUpQkjFq5c5k5J4rOjNhuQaDOEg8lksPhkKmPU5VU,332
|
148
|
-
fractal_server/app/schemas/v2/task.py,sha256=
|
146
|
+
fractal_server/app/schemas/v2/task.py,sha256=xQfQxL2h-Vw0YL3yEiYvVIXTybE1lyRE0pPUu59nZes,4574
|
149
147
|
fractal_server/app/schemas/v2/task_collection.py,sha256=8PG1bOqkfQqORMN0brWf6mHDmijt0bBW-mZsF7cSxUs,6129
|
150
148
|
fractal_server/app/schemas/v2/workflow.py,sha256=Zzx3e-qgkH8le0FUmAx9UrV5PWd7bj14PPXUh_zgZXM,1827
|
151
|
-
fractal_server/app/schemas/v2/workflowtask.py,sha256=
|
149
|
+
fractal_server/app/schemas/v2/workflowtask.py,sha256=TN-mdkuE_EWet9Wk-xFrUwIt_tXYcw88WOKMnUcchKk,5665
|
152
150
|
fractal_server/app/security/__init__.py,sha256=vuAE0sg2er0I2PAf7xCUHcxbMgxV1d8y6oJjO98n86o,11858
|
153
151
|
fractal_server/config.py,sha256=R0VezSe2PEDjQjHEX2V29A1jMdoomdyECBjWNY15v_0,25049
|
154
152
|
fractal_server/data_migrations/2_4_0.py,sha256=T1HRRWp9ZuXeVfBY6NRGxQ8aNIHVSftOMnB-CMrfvi8,2117
|
@@ -160,11 +158,13 @@ fractal_server/images/tools.py,sha256=gxeniYy4Z-cp_ToK2LHPJUTVVUUrdpogYdcBUvBuLi
|
|
160
158
|
fractal_server/logger.py,sha256=56wfka6fHaa3Rx5qO009nEs_y8gx5wZ2NUNZZ1I-uvc,5130
|
161
159
|
fractal_server/main.py,sha256=NUvMd8C8kosulAcQ8pCFLnOGdLw7j-6RzcHxoNvSB7k,5003
|
162
160
|
fractal_server/migrations/README,sha256=4rQvyDfqodGhpJw74VYijRmgFP49ji5chyEemWGHsuw,59
|
163
|
-
fractal_server/migrations/env.py,sha256=
|
161
|
+
fractal_server/migrations/env.py,sha256=mEiX0TRa_8KAYBrUGJTx1cFJ5YAq_oNHHsFCp1raegk,2543
|
162
|
+
fractal_server/migrations/naming_convention.py,sha256=htbKrVdetx3pklowb_9Cdo5RqeF0fJ740DNecY5de_M,265
|
164
163
|
fractal_server/migrations/script.py.mako,sha256=oMXw9LC3zRbinWWPPDgeZ4z9FJrV2zhRWiYdS5YgNbI,526
|
165
|
-
fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py,sha256
|
164
|
+
fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py,sha256=-BSS9AFTPcu3gYC-sYbawSy4MWQQx8TfMb5BW5EBKmQ,1450
|
166
165
|
fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py,sha256=-wHe-fOffmYeAm0JXVl_lxZ7hhDkaEVqxgxpHkb_uL8,954
|
167
166
|
fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py,sha256=Mob8McGYAcmgvrseyyYOa54E6Gsgr-4SiGdC-r9O4_A,1157
|
167
|
+
fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py,sha256=5ROUgcoZOdjf8kMt6cxuvPhzHmV6xaCxvZEbhUEyZM4,3271
|
168
168
|
fractal_server/migrations/versions/50a13d6138fd_initial_schema.py,sha256=zwXegXs9J40eyCWi3w0c_iIBVJjXNn4VdVnQaT3KxDg,8770
|
169
169
|
fractal_server/migrations/versions/5bf02391cfef_v2.py,sha256=axhNkr_H6R4rRbY7oGYazNbFvPXeSyBDWFVbKNmiqs8,8433
|
170
170
|
fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py,sha256=Q-DsMzG3IcUV2Ol1dhJWosDvKERamBE6QvA2zzS5zpQ,1632
|
@@ -207,8 +207,8 @@ fractal_server/tasks/v2/utils.py,sha256=JOyCacb6MNvrwfLNTyLwcz8y79J29YuJeJ2MK5kq
|
|
207
207
|
fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
|
208
208
|
fractal_server/utils.py,sha256=b7WwFdcFZ8unyT65mloFToYuEDXpQoHRcmRNqrhd_dQ,2115
|
209
209
|
fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
|
210
|
-
fractal_server-2.
|
211
|
-
fractal_server-2.
|
212
|
-
fractal_server-2.
|
213
|
-
fractal_server-2.
|
214
|
-
fractal_server-2.
|
210
|
+
fractal_server-2.5.0a0.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
211
|
+
fractal_server-2.5.0a0.dist-info/METADATA,sha256=K899Jp3wlYZMo1qI3MhmqiVJlcZQQxHQfa8reJVx9k0,4630
|
212
|
+
fractal_server-2.5.0a0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
213
|
+
fractal_server-2.5.0a0.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
214
|
+
fractal_server-2.5.0a0.dist-info/RECORD,,
|
@@ -1,59 +0,0 @@
|
|
1
|
-
from fastapi import APIRouter
|
2
|
-
from fastapi import Depends
|
3
|
-
from fastapi import HTTPException
|
4
|
-
from fastapi import status
|
5
|
-
from sqlmodel import select
|
6
|
-
|
7
|
-
from fractal_server.app.db import AsyncSession
|
8
|
-
from fractal_server.app.db import get_async_db
|
9
|
-
from fractal_server.app.models import UserOAuth
|
10
|
-
from fractal_server.app.models.v1 import Task as TaskV1
|
11
|
-
from fractal_server.app.routes.auth import current_active_user
|
12
|
-
from fractal_server.app.schemas.v2 import TaskLegacyReadV2
|
13
|
-
from fractal_server.logger import set_logger
|
14
|
-
|
15
|
-
router = APIRouter()
|
16
|
-
|
17
|
-
logger = set_logger(__name__)
|
18
|
-
|
19
|
-
|
20
|
-
@router.get("/", response_model=list[TaskLegacyReadV2])
|
21
|
-
async def get_list_task_legacy(
|
22
|
-
args_schema: bool = True,
|
23
|
-
only_v2_compatible: bool = False,
|
24
|
-
user: UserOAuth = Depends(current_active_user),
|
25
|
-
db: AsyncSession = Depends(get_async_db),
|
26
|
-
) -> list[TaskLegacyReadV2]:
|
27
|
-
"""
|
28
|
-
Get list of available legacy tasks
|
29
|
-
"""
|
30
|
-
stm = select(TaskV1)
|
31
|
-
if only_v2_compatible:
|
32
|
-
stm = stm.where(TaskV1.is_v2_compatible)
|
33
|
-
res = await db.execute(stm)
|
34
|
-
task_list = res.scalars().all()
|
35
|
-
await db.close()
|
36
|
-
if args_schema is False:
|
37
|
-
for task in task_list:
|
38
|
-
setattr(task, "args_schema", None)
|
39
|
-
|
40
|
-
return task_list
|
41
|
-
|
42
|
-
|
43
|
-
@router.get("/{task_id}/", response_model=TaskLegacyReadV2)
|
44
|
-
async def get_task_legacy(
|
45
|
-
task_id: int,
|
46
|
-
user: UserOAuth = Depends(current_active_user),
|
47
|
-
db: AsyncSession = Depends(get_async_db),
|
48
|
-
) -> TaskLegacyReadV2:
|
49
|
-
"""
|
50
|
-
Get info on a specific legacy task
|
51
|
-
"""
|
52
|
-
task = await db.get(TaskV1, task_id)
|
53
|
-
await db.close()
|
54
|
-
if not task:
|
55
|
-
raise HTTPException(
|
56
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
57
|
-
detail=f"TaskV1[{task_id}] not found",
|
58
|
-
)
|
59
|
-
return task
|