fractal-server 2.12.1__py3-none-any.whl → 2.13.1__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 (87) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/security.py +9 -12
  3. fractal_server/app/models/v2/__init__.py +4 -0
  4. fractal_server/app/models/v2/accounting.py +35 -0
  5. fractal_server/app/models/v2/dataset.py +2 -2
  6. fractal_server/app/models/v2/job.py +11 -9
  7. fractal_server/app/models/v2/task.py +2 -3
  8. fractal_server/app/models/v2/task_group.py +6 -2
  9. fractal_server/app/models/v2/workflowtask.py +15 -8
  10. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  11. fractal_server/app/routes/admin/v2/accounting.py +108 -0
  12. fractal_server/app/routes/admin/v2/impersonate.py +35 -0
  13. fractal_server/app/routes/admin/v2/job.py +5 -13
  14. fractal_server/app/routes/admin/v2/task.py +1 -1
  15. fractal_server/app/routes/admin/v2/task_group.py +5 -13
  16. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  17. fractal_server/app/routes/api/v2/dataset.py +4 -4
  18. fractal_server/app/routes/api/v2/images.py +11 -11
  19. fractal_server/app/routes/api/v2/project.py +2 -2
  20. fractal_server/app/routes/api/v2/status.py +1 -1
  21. fractal_server/app/routes/api/v2/submit.py +9 -6
  22. fractal_server/app/routes/api/v2/task.py +4 -2
  23. fractal_server/app/routes/api/v2/task_collection.py +3 -2
  24. fractal_server/app/routes/api/v2/task_group.py +4 -7
  25. fractal_server/app/routes/api/v2/workflow.py +3 -3
  26. fractal_server/app/routes/api/v2/workflow_import.py +3 -3
  27. fractal_server/app/routes/api/v2/workflowtask.py +3 -1
  28. fractal_server/app/routes/auth/_aux_auth.py +4 -1
  29. fractal_server/app/routes/auth/current_user.py +3 -5
  30. fractal_server/app/routes/auth/group.py +1 -1
  31. fractal_server/app/routes/auth/users.py +2 -4
  32. fractal_server/app/routes/aux/__init__.py +0 -20
  33. fractal_server/app/routes/aux/_runner.py +1 -1
  34. fractal_server/app/routes/aux/validate_user_settings.py +1 -2
  35. fractal_server/app/runner/executors/_job_states.py +13 -0
  36. fractal_server/app/runner/executors/slurm/_slurm_config.py +26 -18
  37. fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -3
  38. fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +31 -22
  39. fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +2 -5
  40. fractal_server/app/runner/executors/slurm/ssh/executor.py +21 -27
  41. fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -3
  42. fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +1 -2
  43. fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +37 -47
  44. fractal_server/app/runner/executors/slurm/sudo/executor.py +25 -24
  45. fractal_server/app/runner/v2/__init__.py +4 -9
  46. fractal_server/app/runner/v2/_local/__init__.py +3 -0
  47. fractal_server/app/runner/v2/_local/_local_config.py +5 -4
  48. fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +4 -4
  49. fractal_server/app/runner/v2/_slurm_ssh/__init__.py +2 -0
  50. fractal_server/app/runner/v2/_slurm_sudo/__init__.py +4 -2
  51. fractal_server/app/runner/v2/deduplicate_list.py +1 -1
  52. fractal_server/app/runner/v2/runner.py +25 -10
  53. fractal_server/app/runner/v2/runner_functions.py +12 -11
  54. fractal_server/app/runner/v2/task_interface.py +15 -7
  55. fractal_server/app/schemas/_filter_validators.py +6 -3
  56. fractal_server/app/schemas/_validators.py +7 -5
  57. fractal_server/app/schemas/user.py +23 -18
  58. fractal_server/app/schemas/user_group.py +25 -11
  59. fractal_server/app/schemas/user_settings.py +31 -24
  60. fractal_server/app/schemas/v2/__init__.py +1 -0
  61. fractal_server/app/schemas/v2/accounting.py +18 -0
  62. fractal_server/app/schemas/v2/dataset.py +48 -35
  63. fractal_server/app/schemas/v2/dumps.py +16 -14
  64. fractal_server/app/schemas/v2/job.py +49 -29
  65. fractal_server/app/schemas/v2/manifest.py +32 -28
  66. fractal_server/app/schemas/v2/project.py +18 -8
  67. fractal_server/app/schemas/v2/task.py +86 -75
  68. fractal_server/app/schemas/v2/task_collection.py +41 -30
  69. fractal_server/app/schemas/v2/task_group.py +39 -20
  70. fractal_server/app/schemas/v2/workflow.py +24 -12
  71. fractal_server/app/schemas/v2/workflowtask.py +63 -61
  72. fractal_server/app/security/__init__.py +1 -1
  73. fractal_server/config.py +86 -73
  74. fractal_server/images/models.py +18 -12
  75. fractal_server/main.py +1 -1
  76. fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +57 -0
  77. fractal_server/tasks/v2/utils_background.py +2 -2
  78. fractal_server/tasks/v2/utils_database.py +1 -1
  79. {fractal_server-2.12.1.dist-info → fractal_server-2.13.1.dist-info}/METADATA +9 -10
  80. {fractal_server-2.12.1.dist-info → fractal_server-2.13.1.dist-info}/RECORD +83 -81
  81. fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -121
  82. fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -108
  83. fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -42
  84. fractal_server/app/runner/v2/_local_experimental/executor.py +0 -157
  85. {fractal_server-2.12.1.dist-info → fractal_server-2.13.1.dist-info}/LICENSE +0 -0
  86. {fractal_server-2.12.1.dist-info → fractal_server-2.13.1.dist-info}/WHEEL +0 -0
  87. {fractal_server-2.12.1.dist-info → fractal_server-2.13.1.dist-info}/entry_points.txt +0 -0
@@ -3,11 +3,11 @@ from typing import Literal
3
3
  from typing import Optional
4
4
 
5
5
  from pydantic import BaseModel
6
- from pydantic import Extra
6
+ from pydantic import ConfigDict
7
7
  from pydantic import Field
8
+ from pydantic import field_validator
8
9
  from pydantic import HttpUrl
9
- from pydantic import root_validator
10
- from pydantic import validator
10
+ from pydantic import model_validator
11
11
 
12
12
  from fractal_server.app.schemas._validators import val_unique_list
13
13
  from fractal_server.app.schemas._validators import valdict_keys
@@ -15,7 +15,8 @@ from fractal_server.app.schemas._validators import valstr
15
15
  from fractal_server.string_tools import validate_cmd
16
16
 
17
17
 
18
- class TaskCreateV2(BaseModel, extra=Extra.forbid):
18
+ class TaskCreateV2(BaseModel):
19
+ model_config = ConfigDict(extra="forbid")
19
20
 
20
21
  name: str
21
22
 
@@ -29,7 +30,7 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
29
30
  args_schema_parallel: Optional[dict[str, Any]] = None
30
31
  args_schema_version: Optional[str] = None
31
32
  docs_info: Optional[str] = None
32
- docs_link: Optional[HttpUrl] = None
33
+ docs_link: Optional[str] = None
33
34
 
34
35
  input_types: dict[str, bool] = Field(default={})
35
36
  output_types: dict[str, bool] = Field(default={})
@@ -40,10 +41,10 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
40
41
  authors: Optional[str] = None
41
42
 
42
43
  # Validators
43
- @root_validator
44
- def validate_commands(cls, values):
45
- command_parallel = values.get("command_parallel")
46
- command_non_parallel = values.get("command_non_parallel")
44
+ @model_validator(mode="after")
45
+ def validate_commands(self):
46
+ command_parallel = self.command_parallel
47
+ command_non_parallel = self.command_non_parallel
47
48
  if (command_parallel is None) and (command_non_parallel is None):
48
49
  raise ValueError(
49
50
  "Task must have at least one valid command "
@@ -54,58 +55,65 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
54
55
  if command_non_parallel is not None:
55
56
  validate_cmd(command_non_parallel)
56
57
 
57
- return values
58
+ return self
58
59
 
59
- _name = validator("name", allow_reuse=True)(valstr("name"))
60
- _command_non_parallel = validator(
61
- "command_non_parallel", allow_reuse=True
62
- )(valstr("command_non_parallel"))
63
- _command_parallel = validator("command_parallel", allow_reuse=True)(
64
- valstr("command_parallel")
60
+ _name = field_validator("name")(classmethod(valstr("name")))
61
+ _command_non_parallel = field_validator("command_non_parallel")(
62
+ classmethod(valstr("command_non_parallel"))
65
63
  )
66
- _version = validator("version", allow_reuse=True)(valstr("version"))
64
+ _command_parallel = field_validator("command_parallel")(
65
+ classmethod(valstr("command_parallel"))
66
+ )
67
+ _version = field_validator("version")(classmethod(valstr("version")))
67
68
 
68
- _meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
69
- valdict_keys("meta_non_parallel")
69
+ _meta_non_parallel = field_validator("meta_non_parallel")(
70
+ classmethod(valdict_keys("meta_non_parallel"))
71
+ )
72
+ _meta_parallel = field_validator("meta_parallel")(
73
+ classmethod(valdict_keys("meta_parallel"))
70
74
  )
71
- _meta_parallel = validator("meta_parallel", allow_reuse=True)(
72
- valdict_keys("meta_parallel")
75
+ _args_schema_non_parallel = field_validator("args_schema_non_parallel")(
76
+ classmethod(valdict_keys("args_schema_non_parallel"))
73
77
  )
74
- _args_schema_non_parallel = validator(
75
- "args_schema_non_parallel", allow_reuse=True
76
- )(valdict_keys("args_schema_non_parallel"))
77
- _args_schema_parallel = validator(
78
- "args_schema_parallel", allow_reuse=True
79
- )(valdict_keys("args_schema_parallel"))
80
- _args_schema_version = validator("args_schema_version", allow_reuse=True)(
81
- valstr("args_schema_version")
78
+ _args_schema_parallel = field_validator("args_schema_parallel")(
79
+ classmethod(valdict_keys("args_schema_parallel"))
82
80
  )
83
- _input_types = validator("input_types", allow_reuse=True)(
84
- valdict_keys("input_types")
81
+ _args_schema_version = field_validator("args_schema_version")(
82
+ classmethod(valstr("args_schema_version"))
85
83
  )
86
- _output_types = validator("output_types", allow_reuse=True)(
87
- valdict_keys("output_types")
84
+ _input_types = field_validator("input_types")(
85
+ classmethod(valdict_keys("input_types"))
86
+ )
87
+ _output_types = field_validator("output_types")(
88
+ classmethod(valdict_keys("output_types"))
88
89
  )
89
90
 
90
- _category = validator("category", allow_reuse=True)(
91
- valstr("category", accept_none=True)
91
+ _category = field_validator("category")(
92
+ classmethod(valstr("category", accept_none=True))
92
93
  )
93
- _modality = validator("modality", allow_reuse=True)(
94
- valstr("modality", accept_none=True)
94
+ _modality = field_validator("modality")(
95
+ classmethod(valstr("modality", accept_none=True))
95
96
  )
96
- _authors = validator("authors", allow_reuse=True)(
97
- valstr("authors", accept_none=True)
97
+ _authors = field_validator("authors")(
98
+ classmethod(valstr("authors", accept_none=True))
98
99
  )
99
100
 
100
- @validator("tags")
101
+ @field_validator("tags")
102
+ @classmethod
101
103
  def validate_list_of_strings(cls, value):
102
104
  for i, tag in enumerate(value):
103
- value[i] = valstr(f"tags[{i}]")(tag)
104
- return val_unique_list("tags")(value)
105
+ value[i] = valstr(f"tags[{i}]")(cls, tag)
106
+ return val_unique_list("tags")(cls, value)
105
107
 
108
+ @field_validator("docs_link", mode="after")
109
+ @classmethod
110
+ def validate_docs_link(cls, value):
111
+ if value is not None:
112
+ HttpUrl(value)
113
+ return value
106
114
 
107
- class TaskReadV2(BaseModel):
108
115
 
116
+ class TaskReadV2(BaseModel):
109
117
  id: int
110
118
  name: str
111
119
  type: Literal["parallel", "non_parallel", "compound"]
@@ -120,7 +128,7 @@ class TaskReadV2(BaseModel):
120
128
  args_schema_parallel: Optional[dict[str, Any]] = None
121
129
  args_schema_version: Optional[str] = None
122
130
  docs_info: Optional[str] = None
123
- docs_link: Optional[HttpUrl] = None
131
+ docs_link: Optional[str] = None
124
132
  input_types: dict[str, bool]
125
133
  output_types: dict[str, bool]
126
134
 
@@ -132,7 +140,8 @@ class TaskReadV2(BaseModel):
132
140
  tags: list[str]
133
141
 
134
142
 
135
- class TaskUpdateV2(BaseModel, extra=Extra.forbid):
143
+ class TaskUpdateV2(BaseModel):
144
+ model_config = ConfigDict(extra="forbid")
136
145
 
137
146
  command_parallel: Optional[str] = None
138
147
  command_non_parallel: Optional[str] = None
@@ -145,67 +154,69 @@ class TaskUpdateV2(BaseModel, extra=Extra.forbid):
145
154
  tags: Optional[list[str]] = None
146
155
 
147
156
  # Validators
148
- @validator("input_types", "output_types")
157
+ @field_validator("input_types", "output_types")
158
+ @classmethod
149
159
  def val_is_dict(cls, v):
150
160
  if not isinstance(v, dict):
151
161
  raise ValueError
152
162
  return v
153
163
 
154
- _command_parallel = validator("command_parallel", allow_reuse=True)(
155
- valstr("command_parallel")
164
+ _command_parallel = field_validator("command_parallel")(
165
+ classmethod(valstr("command_parallel"))
166
+ )
167
+ _command_non_parallel = field_validator("command_non_parallel")(
168
+ classmethod(valstr("command_non_parallel"))
156
169
  )
157
- _command_non_parallel = validator(
158
- "command_non_parallel", allow_reuse=True
159
- )(valstr("command_non_parallel"))
160
- _input_types = validator("input_types", allow_reuse=True)(
161
- valdict_keys("input_types")
170
+ _input_types = field_validator("input_types")(
171
+ classmethod(valdict_keys("input_types"))
162
172
  )
163
- _output_types = validator("output_types", allow_reuse=True)(
164
- valdict_keys("output_types")
173
+ _output_types = field_validator("output_types")(
174
+ classmethod(valdict_keys("output_types"))
165
175
  )
166
176
 
167
- _category = validator("category", allow_reuse=True)(
168
- valstr("category", accept_none=True)
177
+ _category = field_validator("category")(
178
+ classmethod(valstr("category", accept_none=True))
169
179
  )
170
- _modality = validator("modality", allow_reuse=True)(
171
- valstr("modality", accept_none=True)
180
+ _modality = field_validator("modality")(
181
+ classmethod(valstr("modality", accept_none=True))
172
182
  )
173
- _authors = validator("authors", allow_reuse=True)(
174
- valstr("authors", accept_none=True)
183
+ _authors = field_validator("authors")(
184
+ classmethod(valstr("authors", accept_none=True))
175
185
  )
176
186
 
177
- @validator("tags")
187
+ @field_validator("tags")
188
+ @classmethod
178
189
  def validate_tags(cls, value):
179
190
  for i, tag in enumerate(value):
180
- value[i] = valstr(f"tags[{i}]")(tag)
181
- return val_unique_list("tags")(value)
191
+ value[i] = valstr(f"tags[{i}]")(cls, tag)
192
+ return val_unique_list("tags")(cls, value)
182
193
 
183
194
 
184
- class TaskImportV2(BaseModel, extra=Extra.forbid):
195
+ class TaskImportV2(BaseModel):
196
+ model_config = ConfigDict(extra="forbid")
185
197
 
186
198
  pkg_name: str
187
199
  version: Optional[str] = None
188
200
  name: str
189
- _pkg_name = validator("pkg_name", allow_reuse=True)(valstr("pkg_name"))
190
- _version = validator("version", allow_reuse=True)(
191
- valstr("version", accept_none=True)
201
+ _pkg_name = field_validator("pkg_name")(classmethod(valstr("pkg_name")))
202
+ _version = field_validator("version")(
203
+ classmethod(valstr("version", accept_none=True))
192
204
  )
193
- _name = validator("name", allow_reuse=True)(valstr("name"))
205
+ _name = field_validator("name")(classmethod(valstr("name")))
194
206
 
195
207
 
196
208
  class TaskImportV2Legacy(BaseModel):
197
209
  source: str
198
- _source = validator("source", allow_reuse=True)(valstr("source"))
210
+ _source = field_validator("source")(classmethod(valstr("source")))
199
211
 
200
212
 
201
213
  class TaskExportV2(BaseModel):
202
-
203
214
  pkg_name: str
204
215
  version: Optional[str] = None
205
216
  name: str
206
217
 
207
- _pkg_name = validator("pkg_name", allow_reuse=True)(valstr("pkg_name"))
208
- _version = validator("version", allow_reuse=True)(
209
- valstr("version", accept_none=True)
218
+ _pkg_name = field_validator("pkg_name")(classmethod(valstr("pkg_name")))
219
+ _version = field_validator("version")(
220
+ classmethod(valstr("version", accept_none=True))
210
221
  )
211
- _name = validator("name", allow_reuse=True)(valstr("name"))
222
+ _name = field_validator("name")(classmethod(valstr("name")))
@@ -3,9 +3,9 @@ from typing import Literal
3
3
  from typing import Optional
4
4
 
5
5
  from pydantic import BaseModel
6
- from pydantic import Extra
7
- from pydantic import root_validator
8
- from pydantic import validator
6
+ from pydantic import ConfigDict
7
+ from pydantic import field_validator
8
+ from pydantic import model_validator
9
9
 
10
10
  from .._validators import valstr
11
11
  from fractal_server.app.schemas.v2 import ManifestV2
@@ -21,7 +21,7 @@ class WheelFile(BaseModel):
21
21
  contents: bytes
22
22
 
23
23
 
24
- class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
24
+ class TaskCollectPipV2(BaseModel):
25
25
  """
26
26
  TaskCollectPipV2 class
27
27
 
@@ -45,35 +45,40 @@ class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
45
45
 
46
46
  """
47
47
 
48
+ model_config = ConfigDict(extra="forbid")
48
49
  package: Optional[str] = None
49
50
  package_version: Optional[str] = None
50
51
  package_extras: Optional[str] = None
51
52
  python_version: Optional[Literal["3.9", "3.10", "3.11", "3.12"]] = None
52
53
  pinned_package_versions: Optional[dict[str, str]] = None
53
54
 
54
- @validator("package")
55
+ @field_validator("package")
56
+ @classmethod
55
57
  def package_validator(cls, value: Optional[str]) -> Optional[str]:
56
58
  if value is None:
57
59
  return value
58
- value = valstr("package")(value)
60
+ value = valstr("package")(cls, value)
59
61
  validate_cmd(value, attribute_name="package")
60
62
  return value
61
63
 
62
- @validator("package_version")
64
+ @field_validator("package_version")
65
+ @classmethod
63
66
  def package_version_validator(cls, value: Optional[str]) -> Optional[str]:
64
67
  if value is None:
65
68
  return value
66
- value = valstr("package_version")(value)
69
+ value = valstr("package_version")(cls, value)
67
70
  validate_cmd(value, attribute_name="package_version")
68
71
  return value
69
72
 
70
- @validator("pinned_package_versions")
73
+ @field_validator("pinned_package_versions")
74
+ @classmethod
71
75
  def pinned_package_versions_validator(cls, value):
72
76
  if value is None:
73
77
  return value
74
78
  old_keys = list(value.keys())
75
79
  new_keys = [
76
- valstr(f"pinned_package_versions[{key}]")(key) for key in old_keys
80
+ valstr(f"pinned_package_versions[{key}]")(cls, key)
81
+ for key in old_keys
77
82
  ]
78
83
  if len(new_keys) != len(set(new_keys)):
79
84
  raise ValueError(
@@ -87,16 +92,17 @@ class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
87
92
  validate_cmd(version)
88
93
  return value
89
94
 
90
- @validator("package_extras")
95
+ @field_validator("package_extras")
96
+ @classmethod
91
97
  def package_extras_validator(cls, value: Optional[str]) -> Optional[str]:
92
98
  if value is None:
93
99
  return value
94
- value = valstr("package_extras")(value)
100
+ value = valstr("package_extras")(cls, value)
95
101
  validate_cmd(value, attribute_name="package_extras")
96
102
  return value
97
103
 
98
104
 
99
- class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
105
+ class TaskCollectCustomV2(BaseModel):
100
106
  """
101
107
  Attributes:
102
108
  manifest: Manifest of a Fractal task package (this is typically the
@@ -114,29 +120,31 @@ class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
114
120
  version: Optional version of tasks to be collected.
115
121
  """
116
122
 
123
+ model_config = ConfigDict(extra="forbid")
117
124
  manifest: ManifestV2
118
125
  python_interpreter: str
119
126
  label: str
120
- package_root: Optional[str]
121
- package_name: Optional[str]
122
- version: Optional[str]
127
+ package_root: Optional[str] = None
128
+ package_name: Optional[str] = None
129
+ version: Optional[str] = None
123
130
 
124
131
  # Valstr
125
- _python_interpreter = validator("python_interpreter", allow_reuse=True)(
126
- valstr("python_interpreter")
132
+ _python_interpreter = field_validator("python_interpreter")(
133
+ classmethod(valstr("python_interpreter"))
127
134
  )
128
- _label = validator("label", allow_reuse=True)(valstr("label"))
129
- _package_root = validator("package_root", allow_reuse=True)(
130
- valstr("package_root", accept_none=True)
135
+ _label = field_validator("label")(classmethod(valstr("label")))
136
+ _package_root = field_validator("package_root")(
137
+ classmethod(valstr("package_root", accept_none=True))
131
138
  )
132
- _package_name = validator("package_name", allow_reuse=True)(
133
- valstr("package_name", accept_none=True)
139
+ _package_name = field_validator("package_name")(
140
+ classmethod(valstr("package_name", accept_none=True))
134
141
  )
135
- _version = validator("version", allow_reuse=True)(
136
- valstr("version", accept_none=True)
142
+ _version = field_validator("version")(
143
+ classmethod(valstr("version", accept_none=True))
137
144
  )
138
145
 
139
- @root_validator(pre=True)
146
+ @model_validator(mode="before")
147
+ @classmethod
140
148
  def one_of_package_root_or_name(cls, values):
141
149
  package_root = values.get("package_root")
142
150
  package_name = values.get("package_name")
@@ -149,18 +157,20 @@ class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
149
157
  )
150
158
  return values
151
159
 
152
- @validator("package_name")
160
+ @field_validator("package_name")
161
+ @classmethod
153
162
  def package_name_validator(cls, value: str):
154
163
  """
155
164
  Remove all whitespace characters, then check for invalid code.
156
165
  """
157
166
  if value is not None:
158
167
  validate_cmd(value)
159
- value = valstr("package_name")(value)
168
+ value = valstr("package_name")(cls, value)
160
169
  value = value.replace(" ", "")
161
170
  return value
162
171
 
163
- @validator("package_root")
172
+ @field_validator("package_root")
173
+ @classmethod
164
174
  def package_root_validator(cls, value):
165
175
  if (value is not None) and (not Path(value).is_absolute()):
166
176
  raise ValueError(
@@ -168,7 +178,8 @@ class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
168
178
  )
169
179
  return value
170
180
 
171
- @validator("python_interpreter")
181
+ @field_validator("python_interpreter")
182
+ @classmethod
172
183
  def python_interpreter_validator(cls, value):
173
184
  if not Path(value).is_absolute():
174
185
  raise ValueError(
@@ -3,9 +3,11 @@ from enum import Enum
3
3
  from typing import Optional
4
4
 
5
5
  from pydantic import BaseModel
6
- from pydantic import Extra
6
+ from pydantic import ConfigDict
7
7
  from pydantic import Field
8
- from pydantic import validator
8
+ from pydantic import field_serializer
9
+ from pydantic import field_validator
10
+ from pydantic.types import AwareDatetime
9
11
 
10
12
  from .._validators import val_absolute_path
11
13
  from .._validators import valdict_keys
@@ -32,7 +34,8 @@ class TaskGroupActivityActionV2(str, Enum):
32
34
  REACTIVATE = "reactivate"
33
35
 
34
36
 
35
- class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
37
+ class TaskGroupCreateV2(BaseModel):
38
+ model_config = ConfigDict(extra="forbid")
36
39
  user_id: int
37
40
  user_group_id: Optional[int] = None
38
41
  active: bool = True
@@ -48,21 +51,21 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
48
51
  pinned_package_versions: dict[str, str] = Field(default_factory=dict)
49
52
 
50
53
  # Validators
51
- _path = validator("path", allow_reuse=True)(val_absolute_path("path"))
52
- _venv_path = validator("venv_path", allow_reuse=True)(
53
- val_absolute_path("venv_path")
54
+ _path = field_validator("path")(classmethod(val_absolute_path("path")))
55
+ _venv_path = field_validator("venv_path")(
56
+ classmethod(val_absolute_path("venv_path"))
54
57
  )
55
- _wheel_path = validator("wheel_path", allow_reuse=True)(
56
- val_absolute_path("wheel_path")
58
+ _wheel_path = field_validator("wheel_path")(
59
+ classmethod(val_absolute_path("wheel_path"))
57
60
  )
58
- _pinned_package_versions = validator(
59
- "pinned_package_versions", allow_reuse=True
60
- )(valdict_keys("pinned_package_versions"))
61
- _pip_extras = validator("pip_extras", allow_reuse=True)(
62
- valstr("pip_extras")
61
+ _pinned_package_versions = field_validator("pinned_package_versions")(
62
+ valdict_keys("pinned_package_versions")
63
63
  )
64
- _python_version = validator("python_version", allow_reuse=True)(
65
- valstr("python_version")
64
+ _pip_extras = field_validator("pip_extras")(
65
+ classmethod(valstr("pip_extras"))
66
+ )
67
+ _python_version = field_validator("python_version")(
68
+ classmethod(valstr("python_version"))
66
69
  )
67
70
 
68
71
 
@@ -99,11 +102,16 @@ class TaskGroupReadV2(BaseModel):
99
102
  venv_file_number: Optional[int] = None
100
103
 
101
104
  active: bool
102
- timestamp_created: datetime
103
- timestamp_last_used: datetime
105
+ timestamp_created: AwareDatetime
106
+ timestamp_last_used: AwareDatetime
107
+
108
+ @field_serializer("timestamp_created", "timestamp_last_used")
109
+ def serialize_datetime(v: datetime) -> str:
110
+ return v.isoformat()
104
111
 
105
112
 
106
- class TaskGroupUpdateV2(BaseModel, extra=Extra.forbid):
113
+ class TaskGroupUpdateV2(BaseModel):
114
+ model_config = ConfigDict(extra="forbid")
107
115
  user_group_id: Optional[int] = None
108
116
 
109
117
 
@@ -111,10 +119,21 @@ class TaskGroupActivityV2Read(BaseModel):
111
119
  id: int
112
120
  user_id: int
113
121
  taskgroupv2_id: Optional[int] = None
114
- timestamp_started: datetime
115
- timestamp_ended: Optional[datetime] = None
122
+ timestamp_started: AwareDatetime
123
+ timestamp_ended: Optional[AwareDatetime] = None
116
124
  pkg_name: str
117
125
  version: str
118
126
  status: TaskGroupActivityStatusV2
119
127
  action: TaskGroupActivityActionV2
120
128
  log: Optional[str] = None
129
+
130
+ @field_serializer("timestamp_started")
131
+ def serialize_datetime_start(v: datetime) -> str:
132
+ return v.isoformat()
133
+
134
+ @field_serializer("timestamp_ended")
135
+ def serialize_datetime_end(v: Optional[datetime]) -> Optional[str]:
136
+ if v is None:
137
+ return None
138
+ else:
139
+ return v.isoformat()
@@ -2,8 +2,10 @@ from datetime import datetime
2
2
  from typing import Optional
3
3
 
4
4
  from pydantic import BaseModel
5
- from pydantic import Extra
6
- from pydantic import validator
5
+ from pydantic import ConfigDict
6
+ from pydantic import field_serializer
7
+ from pydantic import field_validator
8
+ from pydantic.types import AwareDatetime
7
9
 
8
10
  from .._validators import valstr
9
11
  from .project import ProjectReadV2
@@ -13,12 +15,14 @@ from .workflowtask import WorkflowTaskReadV2
13
15
  from .workflowtask import WorkflowTaskReadV2WithWarning
14
16
 
15
17
 
16
- class WorkflowCreateV2(BaseModel, extra=Extra.forbid):
18
+ class WorkflowCreateV2(BaseModel):
19
+
20
+ model_config = ConfigDict(extra="forbid")
17
21
 
18
22
  name: str
19
23
 
20
24
  # Validators
21
- _name = validator("name", allow_reuse=True)(valstr("name"))
25
+ _name = field_validator("name")(classmethod(valstr("name")))
22
26
 
23
27
 
24
28
  class WorkflowReadV2(BaseModel):
@@ -28,22 +32,29 @@ class WorkflowReadV2(BaseModel):
28
32
  project_id: int
29
33
  task_list: list[WorkflowTaskReadV2]
30
34
  project: ProjectReadV2
31
- timestamp_created: datetime
35
+ timestamp_created: AwareDatetime
36
+
37
+ @field_serializer("timestamp_created")
38
+ def serialize_datetime(v: datetime) -> str:
39
+ return v.isoformat()
32
40
 
33
41
 
34
42
  class WorkflowReadV2WithWarnings(WorkflowReadV2):
35
43
  task_list: list[WorkflowTaskReadV2WithWarning]
36
44
 
37
45
 
38
- class WorkflowUpdateV2(BaseModel, extra=Extra.forbid):
46
+ class WorkflowUpdateV2(BaseModel):
47
+
48
+ model_config = ConfigDict(extra="forbid")
39
49
 
40
- name: Optional[str]
41
- reordered_workflowtask_ids: Optional[list[int]]
50
+ name: Optional[str] = None
51
+ reordered_workflowtask_ids: Optional[list[int]] = None
42
52
 
43
53
  # Validators
44
- _name = validator("name", allow_reuse=True)(valstr("name"))
54
+ _name = field_validator("name")(classmethod(valstr("name")))
45
55
 
46
- @validator("reordered_workflowtask_ids")
56
+ @field_validator("reordered_workflowtask_ids")
57
+ @classmethod
47
58
  def check_positive_and_unique(cls, value):
48
59
  if any(i < 0 for i in value):
49
60
  raise ValueError("Negative `id` in `reordered_workflowtask_ids`")
@@ -52,7 +63,7 @@ class WorkflowUpdateV2(BaseModel, extra=Extra.forbid):
52
63
  return value
53
64
 
54
65
 
55
- class WorkflowImportV2(BaseModel, extra=Extra.forbid):
66
+ class WorkflowImportV2(BaseModel):
56
67
  """
57
68
  Class for `Workflow` import.
58
69
 
@@ -60,11 +71,12 @@ class WorkflowImportV2(BaseModel, extra=Extra.forbid):
60
71
  task_list:
61
72
  """
62
73
 
74
+ model_config = ConfigDict(extra="forbid")
63
75
  name: str
64
76
  task_list: list[WorkflowTaskImportV2]
65
77
 
66
78
  # Validators
67
- _name = validator("name", allow_reuse=True)(valstr("name"))
79
+ _name = field_validator("name")(classmethod(valstr("name")))
68
80
 
69
81
 
70
82
  class WorkflowExportV2(BaseModel):