digitalhub 0.10.2__py3-none-any.whl → 0.11.0b1__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.

Potentially problematic release.


This version of digitalhub might be problematic. Click here for more details.

Files changed (61) hide show
  1. digitalhub/__init__.py +10 -0
  2. digitalhub/context/api.py +10 -4
  3. digitalhub/context/builder.py +35 -20
  4. digitalhub/context/context.py +35 -24
  5. digitalhub/entities/_base/entity/builder.py +11 -0
  6. digitalhub/entities/_base/executable/entity.py +30 -4
  7. digitalhub/entities/_base/material/utils.py +11 -11
  8. digitalhub/entities/_commons/enums.py +2 -0
  9. digitalhub/entities/_processors/base.py +15 -15
  10. digitalhub/entities/_processors/context.py +13 -13
  11. digitalhub/entities/_processors/utils.py +2 -2
  12. digitalhub/entities/builders.py +2 -0
  13. digitalhub/entities/function/_base/entity.py +49 -3
  14. digitalhub/entities/project/_base/builder.py +4 -0
  15. digitalhub/entities/project/_base/entity.py +5 -2
  16. digitalhub/entities/project/_base/models.py +18 -0
  17. digitalhub/entities/project/_base/spec.py +6 -0
  18. digitalhub/entities/project/crud.py +2 -13
  19. digitalhub/entities/run/_base/entity.py +6 -12
  20. digitalhub/entities/task/_base/entity.py +4 -4
  21. digitalhub/entities/task/_base/models.py +22 -2
  22. digitalhub/entities/trigger/__init__.py +0 -0
  23. digitalhub/entities/trigger/_base/__init__.py +0 -0
  24. digitalhub/entities/trigger/_base/builder.py +70 -0
  25. digitalhub/entities/trigger/_base/entity.py +34 -0
  26. digitalhub/entities/trigger/_base/spec.py +30 -0
  27. digitalhub/entities/trigger/_base/status.py +9 -0
  28. digitalhub/entities/trigger/crud.py +303 -0
  29. digitalhub/entities/trigger/scheduler/__init__.py +0 -0
  30. digitalhub/entities/trigger/scheduler/builder.py +19 -0
  31. digitalhub/entities/trigger/scheduler/entity.py +32 -0
  32. digitalhub/entities/trigger/scheduler/spec.py +22 -0
  33. digitalhub/entities/trigger/scheduler/status.py +9 -0
  34. digitalhub/entities/workflow/_base/entity.py +3 -3
  35. digitalhub/factory/factory.py +113 -26
  36. digitalhub/factory/utils.py +31 -14
  37. digitalhub/runtimes/_base.py +22 -11
  38. digitalhub/runtimes/builder.py +16 -3
  39. digitalhub/runtimes/enums.py +11 -1
  40. digitalhub/stores/client/dhcore/client.py +1 -0
  41. digitalhub/stores/client/dhcore/configurator.py +19 -0
  42. digitalhub/stores/client/dhcore/utils.py +1 -0
  43. digitalhub/stores/configurator/configurator.py +5 -2
  44. digitalhub/stores/configurator/enums.py +9 -0
  45. digitalhub/stores/configurator/ini_module.py +58 -4
  46. digitalhub/stores/data/api.py +2 -2
  47. digitalhub/stores/data/builder.py +5 -6
  48. digitalhub/stores/data/enums.py +11 -0
  49. digitalhub/stores/data/local/store.py +0 -3
  50. digitalhub/stores/data/remote/store.py +0 -3
  51. digitalhub/stores/data/s3/configurator.py +2 -19
  52. digitalhub/stores/data/s3/store.py +3 -9
  53. digitalhub/stores/data/sql/configurator.py +11 -21
  54. digitalhub/stores/data/sql/store.py +1 -3
  55. digitalhub/stores/data/utils.py +34 -0
  56. digitalhub/utils/uri_utils.py +5 -0
  57. {digitalhub-0.10.2.dist-info → digitalhub-0.11.0b1.dist-info}/METADATA +1 -1
  58. {digitalhub-0.10.2.dist-info → digitalhub-0.11.0b1.dist-info}/RECORD +60 -46
  59. digitalhub/factory/api.py +0 -277
  60. {digitalhub-0.10.2.dist-info → digitalhub-0.11.0b1.dist-info}/WHEEL +0 -0
  61. {digitalhub-0.10.2.dist-info → digitalhub-0.11.0b1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,303 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from digitalhub.entities._commons.enums import EntityTypes
6
+ from digitalhub.entities._processors.context import context_processor
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from digitalhub.entities.trigger._base.entity import Trigger
10
+
11
+
12
+ ENTITY_TYPE = EntityTypes.TRIGGER.value
13
+
14
+
15
+ def new_trigger(
16
+ project: str,
17
+ name: str,
18
+ kind: str,
19
+ task: str,
20
+ function: str | None = None,
21
+ uuid: str | None = None,
22
+ description: str | None = None,
23
+ labels: list[str] | None = None,
24
+ embedded: bool = False,
25
+ template: dict | None = None,
26
+ **kwargs,
27
+ ) -> Trigger:
28
+ """
29
+ Create a new object.
30
+
31
+ Parameters
32
+ ----------
33
+ project : str
34
+ Project name.
35
+ name : str
36
+ Object name.
37
+ kind : str
38
+ Kind the object.
39
+ uuid : str
40
+ ID of the object.
41
+ description : str
42
+ Description of the object (human readable).
43
+ labels : list[str]
44
+ List of labels.
45
+ embedded : bool
46
+ Flag to determine if object spec must be embedded in project spec.
47
+ **kwargs : dict
48
+ Spec keyword arguments.
49
+
50
+ Returns
51
+ -------
52
+ Trigger
53
+ Object instance.
54
+
55
+ Examples
56
+ --------
57
+ >>> obj = new_trigger(project="my-project",
58
+ >>> kind="trigger",
59
+ >>> name="my-trigger",)
60
+ """
61
+ if kwargs is None:
62
+ kwargs = {}
63
+ if template is None:
64
+ template = {}
65
+ template["task"] = task
66
+ template["function"] = function
67
+ kwargs["template"] = template
68
+ return context_processor.create_context_entity(
69
+ project=project,
70
+ name=name,
71
+ kind=kind,
72
+ uuid=uuid,
73
+ description=description,
74
+ labels=labels,
75
+ embedded=embedded,
76
+ **kwargs,
77
+ )
78
+
79
+
80
+ def get_trigger(
81
+ identifier: str,
82
+ project: str | None = None,
83
+ entity_id: str | None = None,
84
+ **kwargs,
85
+ ) -> Trigger:
86
+ """
87
+ Get object from backend.
88
+
89
+ Parameters
90
+ ----------
91
+ identifier : str
92
+ Entity key (store://...) or entity name.
93
+ project : str
94
+ Project name.
95
+ entity_id : str
96
+ Entity ID.
97
+ **kwargs : dict
98
+ Parameters to pass to the API call.
99
+
100
+ Returns
101
+ -------
102
+ Trigger
103
+ Object instance.
104
+
105
+ Examples
106
+ --------
107
+ Using entity key:
108
+ >>> obj = get_trigger("store://my-trigger-key")
109
+
110
+ Using entity name:
111
+ >>> obj = get_trigger("my-trigger-name"
112
+ >>> project="my-project",
113
+ >>> entity_id="my-trigger-id")
114
+ """
115
+ return context_processor.read_context_entity(
116
+ identifier,
117
+ entity_type=ENTITY_TYPE,
118
+ project=project,
119
+ entity_id=entity_id,
120
+ **kwargs,
121
+ )
122
+
123
+
124
+ def get_trigger_versions(
125
+ identifier: str,
126
+ project: str | None = None,
127
+ **kwargs,
128
+ ) -> list[Trigger]:
129
+ """
130
+ Get object versions from backend.
131
+
132
+ Parameters
133
+ ----------
134
+ identifier : str
135
+ Entity key (store://...) or entity name.
136
+ project : str
137
+ Project name.
138
+ **kwargs : dict
139
+ Parameters to pass to the API call.
140
+
141
+ Returns
142
+ -------
143
+ list[Trigger]
144
+ List of object instances.
145
+
146
+ Examples
147
+ --------
148
+ Using entity key:
149
+ >>> objs = get_trigger_versions("store://my-trigger-key")
150
+
151
+ Using entity name:
152
+ >>> objs = get_trigger_versions("my-trigger-name",
153
+ >>> project="my-project")
154
+ """
155
+ return context_processor.read_context_entity_versions(
156
+ identifier,
157
+ entity_type=ENTITY_TYPE,
158
+ project=project,
159
+ **kwargs,
160
+ )
161
+
162
+
163
+ def list_triggers(project: str, **kwargs) -> list[Trigger]:
164
+ """
165
+ List all latest version objects from backend.
166
+
167
+ Parameters
168
+ ----------
169
+ project : str
170
+ Project name.
171
+ **kwargs : dict
172
+ Parameters to pass to the API call.
173
+
174
+ Returns
175
+ -------
176
+ list[Trigger]
177
+ List of object instances.
178
+
179
+ Examples
180
+ --------
181
+ >>> objs = list_triggers(project="my-project")
182
+ """
183
+ return context_processor.list_context_entities(
184
+ project=project,
185
+ entity_type=ENTITY_TYPE,
186
+ **kwargs,
187
+ )
188
+
189
+
190
+ def import_trigger(file: str) -> Trigger:
191
+ """
192
+ Import object from a YAML file and create a new object into the backend.
193
+
194
+ Parameters
195
+ ----------
196
+ file : str
197
+ Path to YAML file.
198
+
199
+ Returns
200
+ -------
201
+ Trigger
202
+ Object instance.
203
+
204
+ Examples
205
+ --------
206
+ >>> obj = import_trigger("my-trigger.yaml")
207
+ """
208
+ return context_processor.import_context_entity(file)
209
+
210
+
211
+ def load_trigger(file: str) -> Trigger:
212
+ """
213
+ Load object from a YAML file and update an existing object into the backend.
214
+
215
+ Parameters
216
+ ----------
217
+ file : str
218
+ Path to YAML file.
219
+
220
+ Returns
221
+ -------
222
+ Trigger
223
+ Object instance.
224
+
225
+ Examples
226
+ --------
227
+ >>> obj = load_trigger("my-trigger.yaml")
228
+ """
229
+ return context_processor.load_context_entity(file)
230
+
231
+
232
+ def update_trigger(entity: Trigger) -> Trigger:
233
+ """
234
+ Update object. Note that object spec are immutable.
235
+
236
+ Parameters
237
+ ----------
238
+ entity : Trigger
239
+ Object to update.
240
+
241
+ Returns
242
+ -------
243
+ Trigger
244
+ Entity updated.
245
+
246
+ Examples
247
+ --------
248
+ >>> obj = update_trigger(obj)
249
+ """
250
+ return context_processor.update_context_entity(
251
+ project=entity.project,
252
+ entity_type=entity.ENTITY_TYPE,
253
+ entity_id=entity.id,
254
+ entity_dict=entity.to_dict(),
255
+ )
256
+
257
+
258
+ def delete_trigger(
259
+ identifier: str,
260
+ project: str | None = None,
261
+ entity_id: str | None = None,
262
+ delete_all_versions: bool = False,
263
+ **kwargs,
264
+ ) -> dict:
265
+ """
266
+ Delete object from backend.
267
+
268
+ Parameters
269
+ ----------
270
+ identifier : str
271
+ Entity key (store://...) or entity name.
272
+ project : str
273
+ Project name.
274
+ entity_id : str
275
+ Entity ID.
276
+ delete_all_versions : bool
277
+ Delete all versions of the named entity. If True, use entity name instead of entity key as identifier.
278
+ **kwargs : dict
279
+ Parameters to pass to the API call.
280
+
281
+ Returns
282
+ -------
283
+ dict
284
+ Response from backend.
285
+
286
+ Examples
287
+ --------
288
+ If delete_all_versions is False:
289
+ >>> obj = delete_trigger("store://my-trigger-key")
290
+
291
+ Otherwise:
292
+ >>> obj = delete_trigger("my-trigger-name"
293
+ >>> project="my-project",
294
+ >>> delete_all_versions=True)
295
+ """
296
+ return context_processor.delete_context_entity(
297
+ identifier=identifier,
298
+ entity_type=ENTITY_TYPE,
299
+ project=project,
300
+ entity_id=entity_id,
301
+ delete_all_versions=delete_all_versions,
302
+ **kwargs,
303
+ )
File without changes
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.entities._commons.enums import EntityKinds
4
+ from digitalhub.entities.trigger._base.builder import TriggerBuilder
5
+ from digitalhub.entities.trigger.scheduler.entity import TriggerScheduler
6
+ from digitalhub.entities.trigger.scheduler.spec import TriggerSpecScheduler, TriggerValidatorScheduler
7
+ from digitalhub.entities.trigger.scheduler.status import TriggerStatusScheduler
8
+
9
+
10
+ class TriggerSchedulerBuilder(TriggerBuilder):
11
+ """
12
+ TriggerScheduler builder.
13
+ """
14
+
15
+ ENTITY_CLASS = TriggerScheduler
16
+ ENTITY_SPEC_CLASS = TriggerSpecScheduler
17
+ ENTITY_SPEC_VALIDATOR = TriggerValidatorScheduler
18
+ ENTITY_STATUS_CLASS = TriggerStatusScheduler
19
+ ENTITY_KIND = EntityKinds.TRIGGER_SCHEDULER.value
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from digitalhub.entities.trigger._base.entity import Trigger
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from digitalhub.entities._base.entity.metadata import Metadata
9
+ from digitalhub.entities.trigger.scheduler.spec import TriggerSpecScheduler
10
+ from digitalhub.entities.trigger.scheduler.status import TriggerStatusScheduler
11
+
12
+
13
+ class TriggerScheduler(Trigger):
14
+ """
15
+ TriggerScheduler class.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ project: str,
21
+ name: str,
22
+ uuid: str,
23
+ kind: str,
24
+ metadata: Metadata,
25
+ spec: TriggerSpecScheduler,
26
+ status: TriggerStatusScheduler,
27
+ user: str | None = None,
28
+ ) -> None:
29
+ super().__init__(project, name, uuid, kind, metadata, spec, status, user)
30
+
31
+ self.spec: TriggerSpecScheduler
32
+ self.status: TriggerStatusScheduler
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.entities.trigger._base.spec import TriggerSpec, TriggerValidator
4
+
5
+
6
+ class TriggerSpecScheduler(TriggerSpec):
7
+ """
8
+ TriggerSpecScheduler specifications.
9
+ """
10
+
11
+ def __init__(self, task: str, function: str, template: dict, schedule: str) -> None:
12
+ super().__init__(task, function, template)
13
+ self.schedule = schedule
14
+
15
+
16
+ class TriggerValidatorScheduler(TriggerValidator):
17
+ """
18
+ TriggerValidatorScheduler validator.
19
+ """
20
+
21
+ schedule: str
22
+ """Quartz cron expression."""
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.entities.trigger._base.status import TriggerStatus
4
+
5
+
6
+ class TriggerStatusScheduler(TriggerStatus):
7
+ """
8
+ TriggerStatusScheduler status.
9
+ """
@@ -4,7 +4,7 @@ import typing
4
4
 
5
5
  from digitalhub.entities._base.executable.entity import ExecutableEntity
6
6
  from digitalhub.entities._commons.enums import EntityTypes, Relationship
7
- from digitalhub.factory.api import get_run_kind, get_task_kind_from_action
7
+ from digitalhub.factory.factory import factory
8
8
  from digitalhub.utils.exceptions import BackendError
9
9
 
10
10
  if typing.TYPE_CHECKING:
@@ -68,8 +68,8 @@ class Workflow(ExecutableEntity):
68
68
  Run instance.
69
69
  """
70
70
  # Get task and run kind
71
- task_kind = get_task_kind_from_action(self.kind, action)
72
- run_kind = get_run_kind(self.kind)
71
+ task_kind = factory.get_task_kind_from_action(self.kind, action)
72
+ run_kind = factory.get_run_kind(self.kind)
73
73
 
74
74
  # Create or update new task
75
75
  task = self._get_or_create_task(task_kind)
@@ -8,7 +8,7 @@ if typing.TYPE_CHECKING:
8
8
  from digitalhub.entities._base.entity.builder import EntityBuilder
9
9
  from digitalhub.entities._base.entity.entity import Entity
10
10
  from digitalhub.entities._base.entity.metadata import Metadata
11
- from digitalhub.entities._base.entity.spec import Spec
11
+ from digitalhub.entities._base.entity.spec import Spec, SpecValidator
12
12
  from digitalhub.entities._base.entity.status import Status
13
13
  from digitalhub.entities._base.runtime_entity.builder import RuntimeEntityBuilder
14
14
  from digitalhub.runtimes._base import Runtime
@@ -17,7 +17,26 @@ if typing.TYPE_CHECKING:
17
17
 
18
18
  class Factory:
19
19
  """
20
- Factory class for building entities and runtimes.
20
+ Factory for creating and managing entity and runtime builders.
21
+
22
+ This class implements the Factory pattern to manage the creation of
23
+ entities and runtimes through their respective builders. It maintains
24
+ separate registries for entity and runtime builders.
25
+
26
+ Many function arguments are called kind_to_build_from to avoid overwriting
27
+ kind in kwargs.
28
+
29
+ Attributes
30
+ ----------
31
+ _entity_builders : dict[str, EntityBuilder | RuntimeEntityBuilder]
32
+ Registry of entity builders indexed by kind.
33
+ _runtime_builders : dict[str, RuntimeBuilder]
34
+ Registry of runtime builders indexed by kind.
35
+
36
+ Notes
37
+ -----
38
+ All builder methods may raise BuilderError if the requested kind
39
+ is not found in the registry.
21
40
  """
22
41
 
23
42
  def __init__(self):
@@ -26,18 +45,19 @@ class Factory:
26
45
 
27
46
  def add_entity_builder(self, name: str, builder: EntityBuilder | RuntimeEntityBuilder) -> None:
28
47
  """
29
- Add a builder to the factory.
48
+ Register an entity builder.
30
49
 
31
50
  Parameters
32
51
  ----------
33
52
  name : str
34
- Builder name.
35
- builder : EntityBuilder
36
- Builder object.
37
-
38
- Returns
39
- -------
40
- None
53
+ The unique identifier for the builder.
54
+ builder : EntityBuilder | RuntimeEntityBuilder
55
+ The builder instance to register.
56
+
57
+ Raises
58
+ ------
59
+ BuilderError
60
+ If a builder with the same name already exists.
41
61
  """
42
62
  if name in self._entity_builders:
43
63
  raise BuilderError(f"Builder {name} already exists.")
@@ -45,31 +65,30 @@ class Factory:
45
65
 
46
66
  def add_runtime_builder(self, name: str, builder: RuntimeBuilder) -> None:
47
67
  """
48
- Add a builder to the factory.
68
+ Register a runtime builder.
49
69
 
50
70
  Parameters
51
71
  ----------
52
72
  name : str
53
- Builder name.
73
+ The unique identifier for the builder.
54
74
  builder : RuntimeBuilder
55
- Builder object.
75
+ The builder instance to register.
56
76
 
57
- Returns
58
- -------
59
- None
77
+ Raises
78
+ ------
79
+ BuilderError
80
+ If a builder with the same name already exists.
60
81
  """
61
82
  if name in self._runtime_builders:
62
83
  raise BuilderError(f"Builder {name} already exists.")
63
84
  self._runtime_builders[name] = builder()
64
85
 
65
- def build_entity_from_params(self, kind_to_build_from: str, **kwargs) -> Entity:
86
+ def build_entity_from_params(self, **kwargs) -> Entity:
66
87
  """
67
88
  Build an entity from parameters.
68
89
 
69
90
  Parameters
70
91
  ----------
71
- kind_to_build_from : str
72
- Entity type.
73
92
  **kwargs
74
93
  Entity parameters.
75
94
 
@@ -78,17 +97,20 @@ class Factory:
78
97
  Entity
79
98
  Entity object.
80
99
  """
81
- return self._entity_builders[kind_to_build_from].build(**kwargs)
82
-
83
- def build_entity_from_dict(self, kind_to_build_from: str, obj: dict) -> Entity:
100
+ try:
101
+ kind = kwargs["kind"]
102
+ except KeyError:
103
+ raise BuilderError("Missing 'kind' parameter.")
104
+ self._raise_if_entity_builder_not_found(kind)
105
+ return self._entity_builders[kind].build(**kwargs)
106
+
107
+ def build_entity_from_dict(self, obj: dict) -> Entity:
84
108
  """
85
109
  Build an entity from a dictionary.
86
110
 
87
111
  Parameters
88
112
  ----------
89
- kind_to_build_from : str
90
- Entity type.
91
- dict_data : dict
113
+ obj : dict
92
114
  Dictionary with entity data.
93
115
 
94
116
  Returns
@@ -96,7 +118,12 @@ class Factory:
96
118
  Entity
97
119
  Entity object.
98
120
  """
99
- return self._entity_builders[kind_to_build_from].from_dict(obj)
121
+ try:
122
+ kind = obj["kind"]
123
+ except KeyError:
124
+ raise BuilderError("Missing 'kind' parameter.")
125
+ self._raise_if_entity_builder_not_found(kind)
126
+ return self._entity_builders[kind].from_dict(obj)
100
127
 
101
128
  def build_spec(self, kind_to_build_from: str, **kwargs) -> Spec:
102
129
  """
@@ -112,6 +139,7 @@ class Factory:
112
139
  Spec
113
140
  Spec object.
114
141
  """
142
+ self._raise_if_entity_builder_not_found(kind_to_build_from)
115
143
  return self._entity_builders[kind_to_build_from].build_spec(**kwargs)
116
144
 
117
145
  def build_metadata(self, kind_to_build_from: str, **kwargs) -> Metadata:
@@ -128,6 +156,7 @@ class Factory:
128
156
  Metadata
129
157
  Metadata object.
130
158
  """
159
+ self._raise_if_entity_builder_not_found(kind_to_build_from)
131
160
  return self._entity_builders[kind_to_build_from].build_metadata(**kwargs)
132
161
 
133
162
  def build_status(self, kind_to_build_from: str, **kwargs) -> Status:
@@ -144,6 +173,7 @@ class Factory:
144
173
  Status
145
174
  Status object.
146
175
  """
176
+ self._raise_if_entity_builder_not_found(kind_to_build_from)
147
177
  return self._entity_builders[kind_to_build_from].build_status(**kwargs)
148
178
 
149
179
  def build_runtime(self, kind_to_build_from: str, project: str) -> Runtime:
@@ -162,6 +192,7 @@ class Factory:
162
192
  Runtime
163
193
  Runtime object.
164
194
  """
195
+ self._raise_if_runtime_builder_not_found(kind_to_build_from)
165
196
  return self._runtime_builders[kind_to_build_from].build(project=project)
166
197
 
167
198
  def get_entity_type_from_kind(self, kind: str) -> str:
@@ -178,6 +209,7 @@ class Factory:
178
209
  str
179
210
  Entity type.
180
211
  """
212
+ self._raise_if_entity_builder_not_found(kind)
181
213
  return self._entity_builders[kind].get_entity_type()
182
214
 
183
215
  def get_executable_kind(self, kind: str) -> str:
@@ -194,6 +226,7 @@ class Factory:
194
226
  str
195
227
  Executable kind.
196
228
  """
229
+ self._raise_if_entity_builder_not_found(kind)
197
230
  return self._entity_builders[kind].get_executable_kind()
198
231
 
199
232
  def get_action_from_task_kind(self, kind: str, task_kind: str) -> str:
@@ -212,6 +245,7 @@ class Factory:
212
245
  str
213
246
  Action.
214
247
  """
248
+ self._raise_if_entity_builder_not_found(kind)
215
249
  return self._entity_builders[kind].get_action_from_task_kind(task_kind)
216
250
 
217
251
  def get_task_kind_from_action(self, kind: str, action: str) -> list[str]:
@@ -230,6 +264,7 @@ class Factory:
230
264
  list[str]
231
265
  Task kinds.
232
266
  """
267
+ self._raise_if_entity_builder_not_found(kind)
233
268
  return self._entity_builders[kind].get_task_kind_from_action(action)
234
269
 
235
270
  def get_run_kind(self, kind: str) -> str:
@@ -246,6 +281,7 @@ class Factory:
246
281
  str
247
282
  Run kind.
248
283
  """
284
+ self._raise_if_entity_builder_not_found(kind)
249
285
  return self._entity_builders[kind].get_run_kind()
250
286
 
251
287
  def get_all_kinds(self, kind: str) -> list[str]:
@@ -264,5 +300,56 @@ class Factory:
264
300
  """
265
301
  return self._entity_builders[kind].get_all_kinds()
266
302
 
303
+ def get_spec_validator(self, kind: str) -> SpecValidator:
304
+ """
305
+ Get spec validators.
306
+
307
+ Parameters
308
+ ----------
309
+ kind : str
310
+ Kind.
311
+
312
+ Returns
313
+ -------
314
+ SpecValidator
315
+ Spec validator.
316
+ """
317
+ self._raise_if_entity_builder_not_found(kind)
318
+ return self._entity_builders[kind].get_spec_validator()
319
+
320
+ def _raise_if_entity_builder_not_found(self, kind: str) -> None:
321
+ """
322
+ Verify entity builder existence.
323
+
324
+ Parameters
325
+ ----------
326
+ kind : str
327
+ The entity kind to verify.
328
+
329
+ Raises
330
+ ------
331
+ BuilderError
332
+ If no builder exists for the specified kind.
333
+ """
334
+ if kind not in self._entity_builders:
335
+ raise BuilderError(f"Entity builder for kind '{kind}' not found.")
336
+
337
+ def _raise_if_runtime_builder_not_found(self, kind: str) -> None:
338
+ """
339
+ Verify runtime builder existence.
340
+
341
+ Parameters
342
+ ----------
343
+ kind : str
344
+ The runtime kind to verify.
345
+
346
+ Raises
347
+ ------
348
+ BuilderError
349
+ If no builder exists for the specified kind.
350
+ """
351
+ if kind not in self._runtime_builders:
352
+ raise BuilderError(f"Runtime builder for kind '{kind}' not found.")
353
+
267
354
 
268
355
  factory = Factory()