fractal-server 1.4.10__py3-none-any.whl → 2.0.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.
Files changed (132) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/__init__.py +4 -7
  3. fractal_server/app/models/linkuserproject.py +9 -0
  4. fractal_server/app/models/security.py +6 -0
  5. fractal_server/app/models/state.py +1 -1
  6. fractal_server/app/models/v1/__init__.py +10 -0
  7. fractal_server/app/models/{dataset.py → v1/dataset.py} +5 -5
  8. fractal_server/app/models/{job.py → v1/job.py} +5 -5
  9. fractal_server/app/models/{project.py → v1/project.py} +5 -5
  10. fractal_server/app/models/{task.py → v1/task.py} +7 -2
  11. fractal_server/app/models/{workflow.py → v1/workflow.py} +5 -5
  12. fractal_server/app/models/v2/__init__.py +20 -0
  13. fractal_server/app/models/v2/dataset.py +55 -0
  14. fractal_server/app/models/v2/job.py +51 -0
  15. fractal_server/app/models/v2/project.py +31 -0
  16. fractal_server/app/models/v2/task.py +93 -0
  17. fractal_server/app/models/v2/workflow.py +43 -0
  18. fractal_server/app/models/v2/workflowtask.py +90 -0
  19. fractal_server/app/routes/{admin.py → admin/v1.py} +42 -42
  20. fractal_server/app/routes/admin/v2.py +275 -0
  21. fractal_server/app/routes/api/v1/__init__.py +7 -7
  22. fractal_server/app/routes/api/v1/_aux_functions.py +2 -2
  23. fractal_server/app/routes/api/v1/dataset.py +37 -37
  24. fractal_server/app/routes/api/v1/job.py +12 -12
  25. fractal_server/app/routes/api/v1/project.py +23 -21
  26. fractal_server/app/routes/api/v1/task.py +24 -14
  27. fractal_server/app/routes/api/v1/task_collection.py +16 -14
  28. fractal_server/app/routes/api/v1/workflow.py +24 -24
  29. fractal_server/app/routes/api/v1/workflowtask.py +10 -10
  30. fractal_server/app/routes/api/v2/__init__.py +28 -0
  31. fractal_server/app/routes/api/v2/_aux_functions.py +497 -0
  32. fractal_server/app/routes/api/v2/apply.py +220 -0
  33. fractal_server/app/routes/api/v2/dataset.py +310 -0
  34. fractal_server/app/routes/api/v2/images.py +212 -0
  35. fractal_server/app/routes/api/v2/job.py +200 -0
  36. fractal_server/app/routes/api/v2/project.py +205 -0
  37. fractal_server/app/routes/api/v2/task.py +222 -0
  38. fractal_server/app/routes/api/v2/task_collection.py +229 -0
  39. fractal_server/app/routes/api/v2/workflow.py +398 -0
  40. fractal_server/app/routes/api/v2/workflowtask.py +269 -0
  41. fractal_server/app/routes/aux/_job.py +1 -1
  42. fractal_server/app/runner/async_wrap.py +27 -0
  43. fractal_server/app/runner/exceptions.py +129 -0
  44. fractal_server/app/runner/executors/local/__init__.py +3 -0
  45. fractal_server/app/runner/{_local → executors/local}/executor.py +2 -2
  46. fractal_server/app/runner/executors/slurm/__init__.py +3 -0
  47. fractal_server/app/runner/{_slurm → executors/slurm}/_batching.py +1 -1
  48. fractal_server/app/runner/{_slurm → executors/slurm}/_check_jobs_status.py +1 -1
  49. fractal_server/app/runner/{_slurm → executors/slurm}/_executor_wait_thread.py +1 -1
  50. fractal_server/app/runner/{_slurm → executors/slurm}/_slurm_config.py +3 -152
  51. fractal_server/app/runner/{_slurm → executors/slurm}/_subprocess_run_as_user.py +1 -1
  52. fractal_server/app/runner/{_slurm → executors/slurm}/executor.py +9 -9
  53. fractal_server/app/runner/filenames.py +6 -0
  54. fractal_server/app/runner/set_start_and_last_task_index.py +39 -0
  55. fractal_server/app/runner/task_files.py +105 -0
  56. fractal_server/app/runner/{__init__.py → v1/__init__.py} +24 -22
  57. fractal_server/app/runner/{_common.py → v1/_common.py} +13 -120
  58. fractal_server/app/runner/{_local → v1/_local}/__init__.py +6 -6
  59. fractal_server/app/runner/{_local → v1/_local}/_local_config.py +6 -7
  60. fractal_server/app/runner/{_local → v1/_local}/_submit_setup.py +1 -5
  61. fractal_server/app/runner/v1/_slurm/__init__.py +310 -0
  62. fractal_server/app/runner/{_slurm → v1/_slurm}/_submit_setup.py +3 -9
  63. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +163 -0
  64. fractal_server/app/runner/v1/common.py +117 -0
  65. fractal_server/app/runner/{handle_failed_job.py → v1/handle_failed_job.py} +8 -8
  66. fractal_server/app/runner/v2/__init__.py +337 -0
  67. fractal_server/app/runner/v2/_local/__init__.py +169 -0
  68. fractal_server/app/runner/v2/_local/_local_config.py +118 -0
  69. fractal_server/app/runner/v2/_local/_submit_setup.py +52 -0
  70. fractal_server/app/runner/v2/_slurm/__init__.py +157 -0
  71. fractal_server/app/runner/v2/_slurm/_submit_setup.py +83 -0
  72. fractal_server/app/runner/v2/_slurm/get_slurm_config.py +179 -0
  73. fractal_server/app/runner/v2/components.py +5 -0
  74. fractal_server/app/runner/v2/deduplicate_list.py +24 -0
  75. fractal_server/app/runner/v2/handle_failed_job.py +156 -0
  76. fractal_server/app/runner/v2/merge_outputs.py +41 -0
  77. fractal_server/app/runner/v2/runner.py +264 -0
  78. fractal_server/app/runner/v2/runner_functions.py +339 -0
  79. fractal_server/app/runner/v2/runner_functions_low_level.py +134 -0
  80. fractal_server/app/runner/v2/task_interface.py +43 -0
  81. fractal_server/app/runner/v2/v1_compat.py +21 -0
  82. fractal_server/app/schemas/__init__.py +4 -42
  83. fractal_server/app/schemas/v1/__init__.py +42 -0
  84. fractal_server/app/schemas/{applyworkflow.py → v1/applyworkflow.py} +18 -18
  85. fractal_server/app/schemas/{dataset.py → v1/dataset.py} +30 -30
  86. fractal_server/app/schemas/{dumps.py → v1/dumps.py} +8 -8
  87. fractal_server/app/schemas/{manifest.py → v1/manifest.py} +5 -5
  88. fractal_server/app/schemas/{project.py → v1/project.py} +9 -9
  89. fractal_server/app/schemas/{task.py → v1/task.py} +12 -12
  90. fractal_server/app/schemas/{task_collection.py → v1/task_collection.py} +7 -7
  91. fractal_server/app/schemas/{workflow.py → v1/workflow.py} +38 -38
  92. fractal_server/app/schemas/v2/__init__.py +34 -0
  93. fractal_server/app/schemas/v2/dataset.py +88 -0
  94. fractal_server/app/schemas/v2/dumps.py +87 -0
  95. fractal_server/app/schemas/v2/job.py +113 -0
  96. fractal_server/app/schemas/v2/manifest.py +109 -0
  97. fractal_server/app/schemas/v2/project.py +36 -0
  98. fractal_server/app/schemas/v2/task.py +121 -0
  99. fractal_server/app/schemas/v2/task_collection.py +105 -0
  100. fractal_server/app/schemas/v2/workflow.py +78 -0
  101. fractal_server/app/schemas/v2/workflowtask.py +118 -0
  102. fractal_server/config.py +5 -4
  103. fractal_server/images/__init__.py +50 -0
  104. fractal_server/images/tools.py +86 -0
  105. fractal_server/main.py +11 -3
  106. fractal_server/migrations/versions/4b35c5cefbe3_tmp_is_v2_compatible.py +39 -0
  107. fractal_server/migrations/versions/56af171b0159_v2.py +217 -0
  108. fractal_server/migrations/versions/876f28db9d4e_tmp_split_task_and_wftask_meta.py +68 -0
  109. fractal_server/migrations/versions/974c802f0dd0_tmp_workflowtaskv2_type_in_db.py +37 -0
  110. fractal_server/migrations/versions/9cd305cd6023_tmp_workflowtaskv2.py +40 -0
  111. fractal_server/migrations/versions/a6231ed6273c_tmp_args_schemas_in_taskv2.py +42 -0
  112. fractal_server/migrations/versions/b9e9eed9d442_tmp_taskv2_type.py +37 -0
  113. fractal_server/migrations/versions/e3e639454d4b_tmp_make_task_meta_non_optional.py +50 -0
  114. fractal_server/tasks/__init__.py +0 -5
  115. fractal_server/tasks/endpoint_operations.py +13 -19
  116. fractal_server/tasks/utils.py +35 -0
  117. fractal_server/tasks/{_TaskCollectPip.py → v1/_TaskCollectPip.py} +3 -3
  118. fractal_server/tasks/{background_operations.py → v1/background_operations.py} +18 -50
  119. fractal_server/tasks/v1/get_collection_data.py +14 -0
  120. fractal_server/tasks/v2/_TaskCollectPip.py +103 -0
  121. fractal_server/tasks/v2/background_operations.py +382 -0
  122. fractal_server/tasks/v2/get_collection_data.py +14 -0
  123. {fractal_server-1.4.10.dist-info → fractal_server-2.0.0a0.dist-info}/METADATA +1 -1
  124. fractal_server-2.0.0a0.dist-info/RECORD +166 -0
  125. fractal_server/app/runner/_slurm/.gitignore +0 -2
  126. fractal_server/app/runner/_slurm/__init__.py +0 -150
  127. fractal_server/app/runner/common.py +0 -311
  128. fractal_server-1.4.10.dist-info/RECORD +0 -98
  129. /fractal_server/app/runner/{_slurm → executors/slurm}/remote.py +0 -0
  130. {fractal_server-1.4.10.dist-info → fractal_server-2.0.0a0.dist-info}/LICENSE +0 -0
  131. {fractal_server-1.4.10.dist-info → fractal_server-2.0.0a0.dist-info}/WHEEL +0 -0
  132. {fractal_server-1.4.10.dist-info → fractal_server-2.0.0a0.dist-info}/entry_points.txt +0 -0
@@ -1 +1 @@
1
- __VERSION__ = "1.4.10"
1
+ __VERSION__ = "2.0.0a0"
@@ -1,11 +1,8 @@
1
1
  """
2
2
  `models` module
3
3
  """
4
- from ..schemas import * # noqa F401
5
- from .dataset import * # noqa: F403, F401
6
- from .job import * # noqa: F403, F401
7
- from .project import * # noqa: F403, F401
8
- from .security import * # noqa: F403, F401
4
+ from ..schemas.v1 import * # noqa F401, F403 # FIXME: remove this
5
+ from .security import * # noqa: F401, F403
9
6
  from .state import State # noqa: F401
10
- from .task import * # noqa: F403, F401
11
- from .workflow import * # noqa: F401, F403
7
+ from .v1 import * # noqa: F401, F403
8
+ from .v2 import * # noqa: F401, F403
@@ -9,3 +9,12 @@ class LinkUserProject(SQLModel, table=True):
9
9
 
10
10
  project_id: int = Field(foreign_key="project.id", primary_key=True)
11
11
  user_id: int = Field(foreign_key="user_oauth.id", primary_key=True)
12
+
13
+
14
+ class LinkUserProjectV2(SQLModel, table=True):
15
+ """
16
+ Crossing table between User and ProjectV2
17
+ """
18
+
19
+ project_id: int = Field(foreign_key="projectv2.id", primary_key=True)
20
+ user_id: int = Field(foreign_key="user_oauth.id", primary_key=True)
@@ -19,6 +19,7 @@ from sqlmodel import Relationship
19
19
  from sqlmodel import SQLModel
20
20
 
21
21
  from .linkuserproject import LinkUserProject
22
+ from .linkuserproject import LinkUserProjectV2
22
23
 
23
24
 
24
25
  class OAuthAccount(SQLModel, table=True):
@@ -107,6 +108,11 @@ class UserOAuth(SQLModel, table=True):
107
108
  link_model=LinkUserProject,
108
109
  sa_relationship_kwargs={"lazy": "selectin"},
109
110
  )
111
+ project_list_v2: list["ProjectV2"] = Relationship( # noqa
112
+ back_populates="user_list",
113
+ link_model=LinkUserProjectV2,
114
+ sa_relationship_kwargs={"lazy": "selectin"},
115
+ )
110
116
 
111
117
  class Config:
112
118
  orm_mode = True
@@ -9,7 +9,7 @@ from sqlmodel import Field
9
9
  from sqlmodel import SQLModel
10
10
 
11
11
  from ...utils import get_timestamp
12
- from ..schemas import _StateBase
12
+ from ..schemas.v1 import _StateBase
13
13
 
14
14
 
15
15
  class State(_StateBase, SQLModel, table=True):
@@ -0,0 +1,10 @@
1
+ """
2
+ `models` module
3
+ """
4
+ from .dataset import Dataset # noqa: F401
5
+ from .dataset import Resource # noqa: F401
6
+ from .job import ApplyWorkflow # noqa: F403, F401
7
+ from .project import Project # noqa: F403, F401
8
+ from .task import Task # noqa: F403, F401
9
+ from .workflow import Workflow # noqa: F401, F403
10
+ from .workflow import WorkflowTask # noqa: F401, F403
@@ -10,17 +10,17 @@ from sqlmodel import Field
10
10
  from sqlmodel import Relationship
11
11
  from sqlmodel import SQLModel
12
12
 
13
- from ...utils import get_timestamp
14
- from ..schemas.dataset import _DatasetBase
15
- from ..schemas.dataset import _ResourceBase
13
+ from ....utils import get_timestamp
14
+ from ...schemas.v1.dataset import _DatasetBaseV1
15
+ from ...schemas.v1.dataset import _ResourceBaseV1
16
16
 
17
17
 
18
- class Resource(_ResourceBase, SQLModel, table=True):
18
+ class Resource(_ResourceBaseV1, SQLModel, table=True):
19
19
  id: Optional[int] = Field(default=None, primary_key=True)
20
20
  dataset_id: int = Field(foreign_key="dataset.id")
21
21
 
22
22
 
23
- class Dataset(_DatasetBase, SQLModel, table=True):
23
+ class Dataset(_DatasetBaseV1, SQLModel, table=True):
24
24
  """
25
25
  Represent a dataset
26
26
 
@@ -8,12 +8,12 @@ from sqlalchemy.types import JSON
8
8
  from sqlmodel import Field
9
9
  from sqlmodel import SQLModel
10
10
 
11
- from ...utils import get_timestamp
12
- from ..schemas import JobStatusType
13
- from ..schemas.applyworkflow import _ApplyWorkflowBase
11
+ from ....utils import get_timestamp
12
+ from ...schemas.v1 import JobStatusTypeV1
13
+ from ...schemas.v1.applyworkflow import _ApplyWorkflowBaseV1
14
14
 
15
15
 
16
- class ApplyWorkflow(_ApplyWorkflowBase, SQLModel, table=True):
16
+ class ApplyWorkflow(_ApplyWorkflowBaseV1, SQLModel, table=True):
17
17
  """
18
18
  Represent a workflow run
19
19
 
@@ -97,5 +97,5 @@ class ApplyWorkflow(_ApplyWorkflowBase, SQLModel, table=True):
97
97
  end_timestamp: Optional[datetime] = Field(
98
98
  default=None, sa_column=Column(DateTime(timezone=True))
99
99
  )
100
- status: str = JobStatusType.SUBMITTED
100
+ status: str = JobStatusTypeV1.SUBMITTED
101
101
  log: Optional[str] = None
@@ -7,13 +7,13 @@ from sqlmodel import Field
7
7
  from sqlmodel import Relationship
8
8
  from sqlmodel import SQLModel
9
9
 
10
- from ...utils import get_timestamp
11
- from ..schemas.project import _ProjectBase
12
- from .linkuserproject import LinkUserProject
13
- from .security import UserOAuth
10
+ from ....utils import get_timestamp
11
+ from ...schemas.v1.project import _ProjectBaseV1
12
+ from ..linkuserproject import LinkUserProject
13
+ from ..security import UserOAuth
14
14
 
15
15
 
16
- class Project(_ProjectBase, SQLModel, table=True):
16
+ class Project(_ProjectBaseV1, SQLModel, table=True):
17
17
 
18
18
  id: Optional[int] = Field(default=None, primary_key=True)
19
19
  timestamp_created: datetime = Field(
@@ -5,14 +5,15 @@ from typing import Optional
5
5
 
6
6
  from pydantic import HttpUrl
7
7
  from sqlalchemy import Column
8
+ from sqlalchemy import sql
8
9
  from sqlalchemy.types import JSON
9
10
  from sqlmodel import Field
10
11
  from sqlmodel import SQLModel
11
12
 
12
- from ..schemas.task import _TaskBase
13
+ from ...schemas.v1.task import _TaskBaseV1
13
14
 
14
15
 
15
- class Task(_TaskBase, SQLModel, table=True):
16
+ class Task(_TaskBaseV1, SQLModel, table=True):
16
17
  """
17
18
  Task model
18
19
 
@@ -48,6 +49,10 @@ class Task(_TaskBase, SQLModel, table=True):
48
49
  docs_info: Optional[str] = None
49
50
  docs_link: Optional[HttpUrl] = None
50
51
 
52
+ is_v2_compatible: bool = Field(
53
+ default=False, sa_column_kwargs={"server_default": sql.false()}
54
+ )
55
+
51
56
  @property
52
57
  def parallelization_level(self) -> Optional[str]:
53
58
  try:
@@ -12,13 +12,13 @@ from sqlmodel import Field
12
12
  from sqlmodel import Relationship
13
13
  from sqlmodel import SQLModel
14
14
 
15
- from ...utils import get_timestamp
16
- from ..schemas.workflow import _WorkflowBase
17
- from ..schemas.workflow import _WorkflowTaskBase
15
+ from ....utils import get_timestamp
16
+ from ...schemas.v1.workflow import _WorkflowBaseV1
17
+ from ...schemas.v1.workflow import _WorkflowTaskBaseV1
18
18
  from .task import Task
19
19
 
20
20
 
21
- class WorkflowTask(_WorkflowTaskBase, SQLModel, table=True):
21
+ class WorkflowTask(_WorkflowTaskBaseV1, SQLModel, table=True):
22
22
  """
23
23
  A Task as part of a Workflow
24
24
 
@@ -92,7 +92,7 @@ class WorkflowTask(_WorkflowTaskBase, SQLModel, table=True):
92
92
  return self.task.parallelization_level
93
93
 
94
94
 
95
- class Workflow(_WorkflowBase, SQLModel, table=True):
95
+ class Workflow(_WorkflowBaseV1, SQLModel, table=True):
96
96
  """
97
97
  Workflow
98
98
 
@@ -0,0 +1,20 @@
1
+ """
2
+ v2 `models` module
3
+ """
4
+ from ..linkuserproject import LinkUserProjectV2
5
+ from .dataset import DatasetV2
6
+ from .job import JobV2
7
+ from .project import ProjectV2
8
+ from .task import TaskV2
9
+ from .workflow import WorkflowV2
10
+ from .workflowtask import WorkflowTaskV2
11
+
12
+ __all__ = [
13
+ "LinkUserProjectV2",
14
+ "DatasetV2",
15
+ "JobV2",
16
+ "ProjectV2",
17
+ "TaskV2",
18
+ "WorkflowTaskV2",
19
+ "WorkflowV2",
20
+ ]
@@ -0,0 +1,55 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+ from typing import Literal
4
+ from typing import Optional
5
+
6
+ from sqlalchemy import Column
7
+ from sqlalchemy.types import DateTime
8
+ from sqlalchemy.types import JSON
9
+ from sqlmodel import Field
10
+ from sqlmodel import Relationship
11
+ from sqlmodel import SQLModel
12
+
13
+ from ....utils import get_timestamp
14
+
15
+
16
+ class DatasetV2(SQLModel, table=True):
17
+ class Config:
18
+ arbitrary_types_allowed = True
19
+
20
+ id: Optional[int] = Field(default=None, primary_key=True)
21
+ name: str
22
+
23
+ project_id: int = Field(foreign_key="projectv2.id")
24
+ project: "ProjectV2" = Relationship( # noqa: F821
25
+ sa_relationship_kwargs=dict(lazy="selectin"),
26
+ )
27
+
28
+ history: list[dict[str, Any]] = Field(
29
+ sa_column=Column(JSON, server_default="[]", nullable=False)
30
+ )
31
+ read_only: bool = False
32
+
33
+ timestamp_created: datetime = Field(
34
+ default_factory=get_timestamp,
35
+ sa_column=Column(DateTime(timezone=True), nullable=False),
36
+ )
37
+
38
+ # New in V2
39
+
40
+ zarr_dir: str
41
+ images: list[dict[str, Any]] = Field(
42
+ sa_column=Column(JSON, server_default="[]", nullable=False)
43
+ )
44
+
45
+ filters: dict[Literal["attributes", "types"], dict[str, Any]] = Field(
46
+ sa_column=Column(
47
+ JSON,
48
+ nullable=False,
49
+ server_default='{"attributes": {}, "types": {}}',
50
+ )
51
+ )
52
+
53
+ @property
54
+ def image_paths(self) -> list[str]:
55
+ return [image["path"] for image in self.images]
@@ -0,0 +1,51 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+ from typing import Optional
4
+
5
+ from sqlalchemy import Column
6
+ from sqlalchemy.types import DateTime
7
+ from sqlalchemy.types import JSON
8
+ from sqlmodel import Field
9
+ from sqlmodel import SQLModel
10
+
11
+ from ....utils import get_timestamp
12
+ from ...schemas.v1 import JobStatusTypeV1
13
+
14
+
15
+ class JobV2(SQLModel, table=True):
16
+ class Config:
17
+ arbitrary_types_allowed = True
18
+
19
+ id: Optional[int] = Field(default=None, primary_key=True)
20
+ project_id: Optional[int] = Field(foreign_key="projectv2.id")
21
+ workflow_id: Optional[int] = Field(foreign_key="workflowv2.id")
22
+ dataset_id: Optional[int] = Field(foreign_key="datasetv2.id")
23
+
24
+ user_email: str = Field(nullable=False)
25
+ slurm_account: Optional[str]
26
+
27
+ dataset_dump: dict[str, Any] = Field(
28
+ sa_column=Column(JSON, nullable=False)
29
+ )
30
+ workflow_dump: dict[str, Any] = Field(
31
+ sa_column=Column(JSON, nullable=False)
32
+ )
33
+ project_dump: dict[str, Any] = Field(
34
+ sa_column=Column(JSON, nullable=False)
35
+ )
36
+
37
+ worker_init: Optional[str]
38
+ working_dir: Optional[str]
39
+ working_dir_user: Optional[str]
40
+ first_task_index: int
41
+ last_task_index: int
42
+
43
+ start_timestamp: datetime = Field(
44
+ default_factory=get_timestamp,
45
+ sa_column=Column(DateTime(timezone=True), nullable=False),
46
+ )
47
+ end_timestamp: Optional[datetime] = Field(
48
+ default=None, sa_column=Column(DateTime(timezone=True))
49
+ )
50
+ status: str = JobStatusTypeV1.SUBMITTED
51
+ log: Optional[str] = None
@@ -0,0 +1,31 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from sqlalchemy import Column
5
+ from sqlalchemy.types import DateTime
6
+ from sqlmodel import Field
7
+ from sqlmodel import Relationship
8
+ from sqlmodel import SQLModel
9
+
10
+ from ....utils import get_timestamp
11
+ from ..linkuserproject import LinkUserProjectV2
12
+ from ..security import UserOAuth
13
+
14
+
15
+ class ProjectV2(SQLModel, table=True):
16
+
17
+ id: Optional[int] = Field(default=None, primary_key=True)
18
+ name: str
19
+ read_only: bool = False
20
+ timestamp_created: datetime = Field(
21
+ default_factory=get_timestamp,
22
+ sa_column=Column(DateTime(timezone=True), nullable=False),
23
+ )
24
+
25
+ user_list: list[UserOAuth] = Relationship(
26
+ link_model=LinkUserProjectV2,
27
+ back_populates="project_list_v2",
28
+ sa_relationship_kwargs={
29
+ "lazy": "selectin",
30
+ },
31
+ )
@@ -0,0 +1,93 @@
1
+ import json
2
+ import logging
3
+ from typing import Any
4
+ from typing import Optional
5
+
6
+ from pydantic import HttpUrl
7
+ from sqlalchemy import Column
8
+ from sqlalchemy.types import JSON
9
+ from sqlmodel import Field
10
+ from sqlmodel import SQLModel
11
+
12
+
13
+ class TaskV2(SQLModel, table=True):
14
+
15
+ id: Optional[int] = Field(default=None, primary_key=True)
16
+ name: str
17
+
18
+ type: str
19
+ command_non_parallel: Optional[str] = None
20
+ command_parallel: Optional[str] = None
21
+ source: str = Field(unique=True)
22
+
23
+ meta_non_parallel: dict[str, Any] = Field(
24
+ sa_column=Column(JSON, server_default="{}", default={}, nullable=False)
25
+ )
26
+ meta_parallel: dict[str, Any] = Field(
27
+ sa_column=Column(JSON, server_default="{}", default={}, nullable=False)
28
+ )
29
+
30
+ owner: Optional[str] = None
31
+ version: Optional[str] = None
32
+ args_schema_non_parallel: Optional[dict[str, Any]] = Field(
33
+ sa_column=Column(JSON), default=None
34
+ )
35
+ args_schema_parallel: Optional[dict[str, Any]] = Field(
36
+ sa_column=Column(JSON), default=None
37
+ )
38
+ args_schema_version: Optional[str]
39
+ docs_info: Optional[str] = None
40
+ docs_link: Optional[HttpUrl] = None
41
+
42
+ input_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
43
+ output_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
44
+
45
+ @property
46
+ def default_args_non_parallel_from_args_schema(self) -> dict[str, Any]:
47
+ """
48
+ Extract default arguments from args_schema
49
+ """
50
+ # Return {} if there is no args_schema
51
+ if self.args_schema_non_parallel is None:
52
+ return {}
53
+ # Try to construct default_args
54
+ try:
55
+ default_args = {}
56
+ properties = self.args_schema_non_parallel["properties"]
57
+ for prop_name, prop_schema in properties.items():
58
+ default_value = prop_schema.get("default", None)
59
+ if default_value is not None:
60
+ default_args[prop_name] = default_value
61
+ return default_args
62
+ except KeyError as e:
63
+ logging.warning(
64
+ "Cannot set default_args from args_schema_non_parallel="
65
+ f"{json.dumps(self.args_schema_non_parallel)}\n"
66
+ f"Original KeyError: {str(e)}"
67
+ )
68
+ return {}
69
+
70
+ @property
71
+ def default_args_parallel_from_args_schema(self) -> dict[str, Any]:
72
+ """
73
+ Extract default arguments from args_schema
74
+ """
75
+ # Return {} if there is no args_schema
76
+ if self.args_schema_parallel is None:
77
+ return {}
78
+ # Try to construct default_args
79
+ try:
80
+ default_args = {}
81
+ properties = self.args_schema_parallel["properties"]
82
+ for prop_name, prop_schema in properties.items():
83
+ default_value = prop_schema.get("default", None)
84
+ if default_value is not None:
85
+ default_args[prop_name] = default_value
86
+ return default_args
87
+ except KeyError as e:
88
+ logging.warning(
89
+ "Cannot set default_args from args_schema_parallel="
90
+ f"{json.dumps(self.args_schema_parallel)}\n"
91
+ f"Original KeyError: {str(e)}"
92
+ )
93
+ return {}
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from sqlalchemy import Column
5
+ from sqlalchemy.ext.orderinglist import ordering_list
6
+ from sqlalchemy.types import DateTime
7
+ from sqlmodel import Field
8
+ from sqlmodel import Relationship
9
+ from sqlmodel import SQLModel
10
+
11
+ from ....utils import get_timestamp
12
+ from .workflowtask import WorkflowTaskV2
13
+
14
+
15
+ class WorkflowV2(SQLModel, table=True):
16
+
17
+ id: Optional[int] = Field(default=None, primary_key=True)
18
+ name: str
19
+ project_id: int = Field(foreign_key="projectv2.id")
20
+ project: "ProjectV2" = Relationship( # noqa: F821
21
+ sa_relationship_kwargs=dict(lazy="selectin"),
22
+ )
23
+
24
+ task_list: list[WorkflowTaskV2] = Relationship(
25
+ sa_relationship_kwargs=dict(
26
+ lazy="selectin",
27
+ order_by="WorkflowTaskV2.order",
28
+ collection_class=ordering_list("order"),
29
+ cascade="all, delete-orphan",
30
+ ),
31
+ )
32
+ timestamp_created: datetime = Field(
33
+ default_factory=get_timestamp,
34
+ sa_column=Column(DateTime(timezone=True), nullable=False),
35
+ )
36
+
37
+ @property
38
+ def input_types(self):
39
+ return self.task_list[0].task.input_types
40
+
41
+ @property
42
+ def output_types(self):
43
+ return self.task_list[-1].task.output_types
@@ -0,0 +1,90 @@
1
+ from typing import Any
2
+ from typing import Literal
3
+ from typing import Optional
4
+
5
+ from pydantic import validator
6
+ from sqlalchemy import Column
7
+ from sqlalchemy.types import JSON
8
+ from sqlmodel import Field
9
+ from sqlmodel import Relationship
10
+ from sqlmodel import SQLModel
11
+
12
+ from ..v1.task import Task
13
+ from .task import TaskV2
14
+
15
+
16
+ class WorkflowTaskV2(SQLModel, table=True):
17
+ class Config:
18
+ arbitrary_types_allowed = True
19
+ fields = {"parent": {"exclude": True}}
20
+
21
+ id: Optional[int] = Field(default=None, primary_key=True)
22
+
23
+ workflow_id: int = Field(foreign_key="workflowv2.id")
24
+ order: Optional[int]
25
+ meta_parallel: Optional[dict[str, Any]] = Field(sa_column=Column(JSON))
26
+ meta_non_parallel: Optional[dict[str, Any]] = Field(sa_column=Column(JSON))
27
+ args_parallel: Optional[dict[str, Any]] = Field(sa_column=Column(JSON))
28
+ args_non_parallel: Optional[dict[str, Any]] = Field(sa_column=Column(JSON))
29
+
30
+ input_filters: dict[
31
+ Literal["attributes", "types"], dict[str, Any]
32
+ ] = Field(
33
+ sa_column=Column(
34
+ JSON,
35
+ nullable=False,
36
+ server_default='{"attributes": {}, "types": {}}',
37
+ )
38
+ )
39
+
40
+ # Task
41
+ is_legacy_task: bool
42
+ task_type: str
43
+ task_id: Optional[int] = Field(foreign_key="taskv2.id")
44
+ task: Optional[TaskV2] = Relationship(
45
+ sa_relationship_kwargs=dict(lazy="selectin")
46
+ )
47
+ task_legacy_id: Optional[int] = Field(foreign_key="task.id")
48
+ task_legacy: Optional[Task] = Relationship(
49
+ sa_relationship_kwargs=dict(lazy="selectin")
50
+ )
51
+
52
+ @validator("args_non_parallel")
53
+ def validate_args_non_parallel(cls, value):
54
+ """
55
+ FIXME V2 this requires an update
56
+ """
57
+ if value is None:
58
+ return
59
+ forbidden_args_keys = {
60
+ "metadata",
61
+ "component",
62
+ }
63
+ args_keys = set(value.keys())
64
+ intersect_keys = forbidden_args_keys.intersection(args_keys)
65
+ if intersect_keys:
66
+ raise ValueError(
67
+ "`args` contains the following forbidden keys: "
68
+ f"{intersect_keys}"
69
+ )
70
+ return value
71
+
72
+ @validator("args_parallel")
73
+ def validate_args_parallel(cls, value):
74
+ """
75
+ FIXME V2 this requires an update
76
+ """
77
+ if value is None:
78
+ return
79
+ forbidden_args_keys = {
80
+ "metadata",
81
+ "component",
82
+ }
83
+ args_keys = set(value.keys())
84
+ intersect_keys = forbidden_args_keys.intersection(args_keys)
85
+ if intersect_keys:
86
+ raise ValueError(
87
+ "`args` contains the following forbidden keys: "
88
+ f"{intersect_keys}"
89
+ )
90
+ return value