digitalhub 0.13.0b3__py3-none-any.whl → 0.14.9__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 (139) hide show
  1. digitalhub/__init__.py +3 -8
  2. digitalhub/context/api.py +43 -6
  3. digitalhub/context/builder.py +1 -5
  4. digitalhub/context/context.py +28 -13
  5. digitalhub/entities/_base/_base/entity.py +0 -15
  6. digitalhub/entities/_base/context/entity.py +1 -4
  7. digitalhub/entities/_base/entity/builder.py +5 -5
  8. digitalhub/entities/_base/entity/entity.py +0 -8
  9. digitalhub/entities/_base/executable/entity.py +195 -87
  10. digitalhub/entities/_base/material/entity.py +11 -23
  11. digitalhub/entities/_base/material/utils.py +28 -4
  12. digitalhub/entities/_base/runtime_entity/builder.py +53 -18
  13. digitalhub/entities/_base/unversioned/entity.py +1 -1
  14. digitalhub/entities/_base/versioned/entity.py +1 -1
  15. digitalhub/entities/_commons/enums.py +1 -31
  16. digitalhub/entities/_commons/metrics.py +64 -30
  17. digitalhub/entities/_commons/utils.py +119 -30
  18. digitalhub/entities/_constructors/_resources.py +151 -0
  19. digitalhub/entities/{_base/entity/_constructors → _constructors}/name.py +18 -0
  20. digitalhub/entities/_processors/base/crud.py +381 -0
  21. digitalhub/entities/_processors/base/import_export.py +118 -0
  22. digitalhub/entities/_processors/base/processor.py +299 -0
  23. digitalhub/entities/_processors/base/special_ops.py +104 -0
  24. digitalhub/entities/_processors/context/crud.py +652 -0
  25. digitalhub/entities/_processors/context/import_export.py +242 -0
  26. digitalhub/entities/_processors/context/material.py +123 -0
  27. digitalhub/entities/_processors/context/processor.py +400 -0
  28. digitalhub/entities/_processors/context/special_ops.py +476 -0
  29. digitalhub/entities/_processors/processors.py +12 -0
  30. digitalhub/entities/_processors/utils.py +38 -102
  31. digitalhub/entities/artifact/crud.py +58 -22
  32. digitalhub/entities/artifact/utils.py +28 -13
  33. digitalhub/entities/builders.py +2 -0
  34. digitalhub/entities/dataitem/crud.py +63 -20
  35. digitalhub/entities/dataitem/table/entity.py +27 -22
  36. digitalhub/entities/dataitem/utils.py +82 -32
  37. digitalhub/entities/function/_base/entity.py +3 -6
  38. digitalhub/entities/function/crud.py +55 -24
  39. digitalhub/entities/model/_base/entity.py +62 -20
  40. digitalhub/entities/model/crud.py +59 -23
  41. digitalhub/entities/model/mlflow/utils.py +29 -20
  42. digitalhub/entities/model/utils.py +28 -13
  43. digitalhub/entities/project/_base/builder.py +0 -6
  44. digitalhub/entities/project/_base/entity.py +337 -164
  45. digitalhub/entities/project/_base/spec.py +4 -4
  46. digitalhub/entities/project/crud.py +28 -71
  47. digitalhub/entities/project/utils.py +7 -3
  48. digitalhub/entities/run/_base/builder.py +0 -4
  49. digitalhub/entities/run/_base/entity.py +70 -63
  50. digitalhub/entities/run/crud.py +79 -26
  51. digitalhub/entities/secret/_base/entity.py +1 -5
  52. digitalhub/entities/secret/crud.py +31 -28
  53. digitalhub/entities/task/_base/builder.py +0 -4
  54. digitalhub/entities/task/_base/entity.py +5 -5
  55. digitalhub/entities/task/_base/models.py +13 -16
  56. digitalhub/entities/task/crud.py +61 -29
  57. digitalhub/entities/trigger/_base/entity.py +1 -5
  58. digitalhub/entities/trigger/crud.py +89 -30
  59. digitalhub/entities/workflow/_base/entity.py +3 -8
  60. digitalhub/entities/workflow/crud.py +55 -24
  61. digitalhub/factory/entity.py +283 -0
  62. digitalhub/factory/enums.py +18 -0
  63. digitalhub/factory/registry.py +197 -0
  64. digitalhub/factory/runtime.py +44 -0
  65. digitalhub/factory/utils.py +3 -54
  66. digitalhub/runtimes/_base.py +2 -2
  67. digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +3 -3
  68. digitalhub/stores/client/builder.py +19 -31
  69. digitalhub/stores/client/client.py +322 -0
  70. digitalhub/stores/client/configurator.py +408 -0
  71. digitalhub/stores/client/enums.py +50 -0
  72. digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -4
  73. digitalhub/stores/client/header_manager.py +61 -0
  74. digitalhub/stores/client/http_handler.py +152 -0
  75. digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
  76. digitalhub/stores/client/params_builder.py +330 -0
  77. digitalhub/stores/client/response_processor.py +102 -0
  78. digitalhub/stores/client/utils.py +35 -0
  79. digitalhub/stores/{credentials → configurator}/api.py +5 -9
  80. digitalhub/stores/configurator/configurator.py +123 -0
  81. digitalhub/stores/{credentials → configurator}/enums.py +27 -10
  82. digitalhub/stores/configurator/handler.py +213 -0
  83. digitalhub/stores/{credentials → configurator}/ini_module.py +31 -22
  84. digitalhub/stores/data/_base/store.py +0 -20
  85. digitalhub/stores/data/api.py +5 -7
  86. digitalhub/stores/data/builder.py +53 -27
  87. digitalhub/stores/data/local/store.py +0 -103
  88. digitalhub/stores/data/remote/store.py +0 -4
  89. digitalhub/stores/data/s3/configurator.py +39 -77
  90. digitalhub/stores/data/s3/store.py +57 -37
  91. digitalhub/stores/data/sql/configurator.py +66 -46
  92. digitalhub/stores/data/sql/store.py +171 -104
  93. digitalhub/stores/readers/data/factory.py +0 -8
  94. digitalhub/stores/readers/data/pandas/reader.py +9 -19
  95. digitalhub/utils/file_utils.py +0 -17
  96. digitalhub/utils/generic_utils.py +1 -14
  97. digitalhub/utils/git_utils.py +0 -8
  98. digitalhub/utils/io_utils.py +0 -12
  99. digitalhub/utils/store_utils.py +44 -0
  100. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/METADATA +5 -4
  101. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/RECORD +112 -113
  102. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
  103. digitalhub/entities/_commons/types.py +0 -9
  104. digitalhub/entities/_processors/base.py +0 -531
  105. digitalhub/entities/_processors/context.py +0 -1299
  106. digitalhub/entities/task/_base/utils.py +0 -22
  107. digitalhub/factory/factory.py +0 -381
  108. digitalhub/stores/client/_base/api_builder.py +0 -34
  109. digitalhub/stores/client/_base/client.py +0 -243
  110. digitalhub/stores/client/_base/params_builder.py +0 -34
  111. digitalhub/stores/client/api.py +0 -36
  112. digitalhub/stores/client/dhcore/client.py +0 -613
  113. digitalhub/stores/client/dhcore/configurator.py +0 -675
  114. digitalhub/stores/client/dhcore/enums.py +0 -34
  115. digitalhub/stores/client/dhcore/key_builder.py +0 -62
  116. digitalhub/stores/client/dhcore/models.py +0 -40
  117. digitalhub/stores/client/dhcore/params_builder.py +0 -278
  118. digitalhub/stores/client/dhcore/utils.py +0 -94
  119. digitalhub/stores/client/local/api_builder.py +0 -116
  120. digitalhub/stores/client/local/client.py +0 -573
  121. digitalhub/stores/client/local/enums.py +0 -15
  122. digitalhub/stores/client/local/key_builder.py +0 -62
  123. digitalhub/stores/client/local/params_builder.py +0 -120
  124. digitalhub/stores/credentials/__init__.py +0 -3
  125. digitalhub/stores/credentials/configurator.py +0 -210
  126. digitalhub/stores/credentials/handler.py +0 -176
  127. digitalhub/stores/credentials/store.py +0 -81
  128. digitalhub/stores/data/enums.py +0 -15
  129. digitalhub/stores/data/s3/utils.py +0 -78
  130. /digitalhub/entities/{_base/entity/_constructors → _constructors}/__init__.py +0 -0
  131. /digitalhub/entities/{_base/entity/_constructors → _constructors}/metadata.py +0 -0
  132. /digitalhub/entities/{_base/entity/_constructors → _constructors}/spec.py +0 -0
  133. /digitalhub/entities/{_base/entity/_constructors → _constructors}/status.py +0 -0
  134. /digitalhub/entities/{_base/entity/_constructors → _constructors}/uuid.py +0 -0
  135. /digitalhub/{stores/client/_base → entities/_processors/base}/__init__.py +0 -0
  136. /digitalhub/{stores/client/dhcore → entities/_processors/context}/__init__.py +0 -0
  137. /digitalhub/stores/{client/local → configurator}/__init__.py +0 -0
  138. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
  139. {digitalhub-0.13.0b3.dist-info → digitalhub-0.14.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,573 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from __future__ import annotations
6
-
7
- from copy import deepcopy
8
- from datetime import datetime, timezone
9
- from typing import Any
10
-
11
- from digitalhub.stores.client._base.client import Client
12
- from digitalhub.stores.client.local.api_builder import ClientLocalApiBuilder
13
- from digitalhub.stores.client.local.enums import LocalClientVar
14
- from digitalhub.stores.client.local.key_builder import ClientLocalKeyBuilder
15
- from digitalhub.stores.client.local.params_builder import ClientLocalParametersBuilder
16
- from digitalhub.utils.exceptions import BackendError
17
-
18
-
19
- class ClientLocal(Client):
20
- """
21
- Local client.
22
-
23
- The Local client can be used when a remote Digitalhub backend is not available.
24
- It handles the creation, reading, updating and deleting of objects in memory,
25
- storing them in a local dictionary.
26
- The functionality of the Local client is almost the same as the DHCore client.
27
- Main differences are:
28
- - Local client does delete objects on cascade.
29
- - The run execution are forced to be local.
30
- """
31
-
32
- def __init__(self) -> None:
33
- super().__init__()
34
- self._api_builder = ClientLocalApiBuilder()
35
- self._key_builder = ClientLocalKeyBuilder()
36
- self._params_builder = ClientLocalParametersBuilder()
37
- self._db: dict[str, dict[str, dict]] = {}
38
-
39
- ##############################
40
- # CRUD
41
- ##############################
42
-
43
- def create_object(self, api: str, obj: Any, **kwargs) -> dict:
44
- """
45
- Create an object in local.
46
-
47
- Parameters
48
- ----------
49
- api : str
50
- Create API.
51
- obj : dict
52
- Object to create.
53
-
54
- Returns
55
- -------
56
- dict
57
- The created object.
58
- """
59
- if api == LocalClientVar.EMPTY.value:
60
- return {}
61
- if not isinstance(obj, dict):
62
- raise TypeError("Object must be a dictionary")
63
-
64
- entity_type, _, context_api = self._parse_api(api)
65
- try:
66
- # Check if entity_type is valid
67
- if entity_type is None:
68
- raise TypeError
69
-
70
- # Check if entity_type exists, if not, create a mapping
71
- self._db.setdefault(entity_type, {})
72
-
73
- # Base API
74
- #
75
- # POST /api/v1/projects
76
- #
77
- # Project are not versioned, everything is stored on "entity_id" key
78
- if not context_api:
79
- if entity_type == "projects":
80
- entity_id = obj["name"]
81
- if entity_id in self._db[entity_type]:
82
- raise ValueError
83
- self._db[entity_type][entity_id] = obj
84
-
85
- # Context API
86
- #
87
- # POST /api/v1/-/<project-name>/artifacts
88
- # POST /api/v1/-/<project-name>/functions
89
- # POST /api/v1/-/<project-name>/runs
90
- #
91
- # Runs and tasks are not versioned, so we keep name as entity_id.
92
- # We have both "name" and "id" attributes for versioned objects so we use them as storage keys.
93
- # The "latest" key is used to store the latest version of the object.
94
- else:
95
- entity_id = obj["id"]
96
- name = obj.get("name", entity_id)
97
- self._db[entity_type].setdefault(name, {})
98
- if entity_id in self._db[entity_type][name]:
99
- raise ValueError
100
- self._db[entity_type][name][entity_id] = obj
101
- self._db[entity_type][name]["latest"] = obj
102
-
103
- # Return the created object
104
- return obj
105
-
106
- # Key error are possibly raised by accessing invalid objects
107
- except (KeyError, TypeError):
108
- msg = self._format_msg(1, entity_type=entity_type)
109
- raise BackendError(msg)
110
-
111
- # If try to create already existing object
112
- except ValueError:
113
- msg = self._format_msg(2, entity_type=entity_type, entity_id=entity_id)
114
- raise BackendError(msg)
115
-
116
- def read_object(self, api: str, **kwargs) -> dict:
117
- """
118
- Get an object from local.
119
-
120
- Parameters
121
- ----------
122
- api : str
123
- Read API.
124
-
125
- Returns
126
- -------
127
- dict
128
- The read object.
129
- """
130
- if api == LocalClientVar.EMPTY.value:
131
- return {}
132
- entity_type, entity_id, context_api = self._parse_api(api)
133
- if entity_id is None:
134
- msg = self._format_msg(4)
135
- raise BackendError(msg)
136
- try:
137
- # Base API
138
- #
139
- # GET /api/v1/projects/<entity_id>
140
- #
141
- # self._parse_api() should return only entity_type
142
-
143
- if not context_api:
144
- obj = self._db[entity_type][entity_id]
145
-
146
- # If the object is a project, we need to add the project spec,
147
- # for example artifacts, functions, workflows, etc.
148
- # Technically we have only projects that access base apis,
149
- # we check entity_type just in case we add something else.
150
- if entity_type == "projects":
151
- obj = self._get_project_spec(obj, entity_id)
152
- return obj
153
-
154
- # Context API
155
- #
156
- # GET /api/v1/-/<project-name>/runs/<entity_id>
157
- # GET /api/v1/-/<project-name>/artifacts/<entity_id>
158
- # GET /api/v1/-/<project-name>/functions/<entity_id>
159
- #
160
- # self._parse_api() should return entity_type and entity_id/version
161
-
162
- else:
163
- for _, v in self._db[entity_type].items():
164
- if entity_id in v:
165
- return v[entity_id]
166
- else:
167
- raise KeyError
168
-
169
- except KeyError:
170
- msg = self._format_msg(3, entity_type=entity_type, entity_id=entity_id)
171
- raise BackendError(msg)
172
-
173
- def update_object(self, api: str, obj: Any, **kwargs) -> dict:
174
- """
175
- Update an object in local.
176
-
177
- Parameters
178
- ----------
179
- api : str
180
- Update API.
181
- obj : dict
182
- Object to update.
183
-
184
- Returns
185
- -------
186
- dict
187
- The updated object.
188
- """
189
- if api == LocalClientVar.EMPTY.value:
190
- return {}
191
- if not isinstance(obj, dict):
192
- raise TypeError("Object must be a dictionary")
193
-
194
- entity_type, entity_id, context_api = self._parse_api(api)
195
- try:
196
- # API example
197
- #
198
- # PUT /api/v1/projects/<entity_id>
199
-
200
- if not context_api:
201
- self._db[entity_type][entity_id] = obj
202
-
203
- # Context API
204
- #
205
- # PUT /api/v1/-/<project-name>/runs/<entity_id>
206
- # PUT /api/v1/-/<project-name>/artifacts/<entity_id>
207
-
208
- else:
209
- name = obj.get("name", entity_id)
210
- self._db[entity_type][name][entity_id] = obj
211
-
212
- except KeyError:
213
- msg = self._format_msg(3, entity_type=entity_type, entity_id=entity_id)
214
- raise BackendError(msg)
215
-
216
- return obj
217
-
218
- def delete_object(self, api: str, **kwargs) -> dict:
219
- """
220
- Delete an object from local.
221
-
222
- Parameters
223
- ----------
224
- api : str
225
- Delete API.
226
- **kwargs : dict
227
- Keyword arguments parsed from request.
228
-
229
- Returns
230
- -------
231
- dict
232
- Response object.
233
- """
234
- entity_type, entity_id, context_api = self._parse_api(api)
235
- try:
236
- # Base API
237
- #
238
- # DELETE /api/v1/projects/<entity_id>
239
-
240
- if not context_api:
241
- self._db[entity_type].pop(entity_id)
242
-
243
- # Context API
244
- #
245
- # DELETE /api/v1/-/<project-name>/artifacts/<entity_id>
246
- #
247
- # We do not handle cascade in local client and
248
- # in the sdk we selectively delete objects by id,
249
- # not by name nor entity_type.
250
-
251
- else:
252
- reset_latest = False
253
-
254
- # Name is optional and extracted from kwargs
255
- # "params": {"name": <name>}
256
- name = kwargs.get("params", {}).get("name")
257
-
258
- # Delete by name
259
- if entity_id is None and name is not None:
260
- self._db[entity_type].pop(name, None)
261
- return {"deleted": True}
262
-
263
- # Delete by id
264
- for _, v in self._db[entity_type].items():
265
- if entity_id in v:
266
- v.pop(entity_id)
267
-
268
- # Handle latest
269
- if v["latest"]["id"] == entity_id:
270
- name = v["latest"].get("name", entity_id)
271
- v.pop("latest")
272
- reset_latest = True
273
- break
274
- else:
275
- raise KeyError
276
-
277
- if name is not None:
278
- # Pop name if empty
279
- if not self._db[entity_type][name]:
280
- self._db[entity_type].pop(name)
281
-
282
- # Handle latest
283
- elif reset_latest:
284
- latest_uuid = None
285
- latest_date = None
286
- for k, v in self._db[entity_type][name].items():
287
- # Get created from metadata. If tzinfo is None, set it to UTC
288
- # If created is not in ISO format, use fallback
289
- fallback = datetime.fromtimestamp(0, timezone.utc)
290
- try:
291
- current_created = datetime.fromisoformat(v.get("metadata", {}).get("created"))
292
- if current_created.tzinfo is None:
293
- current_created = current_created.replace(tzinfo=timezone.utc)
294
- except ValueError:
295
- current_created = fallback
296
-
297
- # Update latest date and uuid
298
- if latest_date is None or current_created > latest_date:
299
- latest_uuid = k
300
- latest_date = current_created
301
-
302
- # Set new latest
303
- if latest_uuid is not None:
304
- self._db[entity_type][name]["latest"] = self._db[entity_type][name][latest_uuid]
305
-
306
- except KeyError:
307
- msg = self._format_msg(3, entity_type=entity_type, entity_id=entity_id)
308
- raise BackendError(msg)
309
- return {"deleted": True}
310
-
311
- def list_objects(self, api: str, **kwargs) -> list:
312
- """
313
- List objects.
314
-
315
- Parameters
316
- ----------
317
- api : str
318
- List API.
319
- **kwargs : dict
320
- Keyword arguments parsed from request.
321
-
322
- Returns
323
- -------
324
- list | None
325
- The list of objects.
326
- """
327
- entity_type, _, _ = self._parse_api(api)
328
-
329
- # Name is optional and extracted from kwargs
330
- # "params": {"name": <name>}
331
- name = kwargs.get("params", {}).get("name")
332
- if name is not None:
333
- return [self._db[entity_type][name]["latest"]]
334
-
335
- try:
336
- # If no name is provided, get latest objects
337
- listed_objects = [v["latest"] for _, v in self._db[entity_type].items()]
338
- except KeyError:
339
- listed_objects = []
340
-
341
- # If kind is provided, return objects by kind
342
- kind = kwargs.get("params", {}).get("kind")
343
- if kind is not None:
344
- listed_objects = [obj for obj in listed_objects if obj["kind"] == kind]
345
-
346
- # If function is provided, return objects by function
347
- spec_params = ["function", "task"]
348
- for i in spec_params:
349
- p = kwargs.get("params", {}).get(i)
350
- if p is not None:
351
- listed_objects = [obj for obj in listed_objects if obj["spec"][i] == p]
352
-
353
- return listed_objects
354
-
355
- def list_first_object(self, api: str, **kwargs) -> dict:
356
- """
357
- List first objects.
358
-
359
- Parameters
360
- ----------
361
- api : str
362
- The api to list the objects with.
363
- **kwargs : dict
364
- Keyword arguments passed to the request.
365
-
366
- Returns
367
- -------
368
- dict
369
- The list of objects.
370
- """
371
- try:
372
- return self.list_objects(api, **kwargs)[0]
373
- except IndexError:
374
- raise IndexError("No objects found")
375
-
376
- def search_objects(self, api: str, **kwargs) -> dict:
377
- """
378
- Search objects from Local.
379
-
380
- Parameters
381
- ----------
382
- api : str
383
- Search API.
384
- **kwargs : dict
385
- Keyword arguments to pass to the request.
386
-
387
- Returns
388
- -------
389
- dict
390
- Response objects.
391
- """
392
- raise NotImplementedError("Local client does not support search_objects.")
393
-
394
- ##############################
395
- # Helpers
396
- ##############################
397
-
398
- def _parse_api(self, api: str) -> tuple:
399
- """
400
- Parse the given API to extract the entity_type, entity_id
401
- and if its a context API.
402
-
403
- Parameters
404
- ----------
405
- api : str
406
- API to parse.
407
-
408
- Returns
409
- -------
410
- tuple
411
- Parsed elements.
412
- """
413
- # Remove prefix from API
414
- api = api.removeprefix("/api/v1/")
415
-
416
- # Set context flag by default to False
417
- context_api = False
418
-
419
- # Remove context prefix from API and set context flag to True
420
- if api.startswith("-/"):
421
- context_api = True
422
- api = api[2:]
423
-
424
- # Return parsed elements
425
- return self._parse_api_elements(api, context_api)
426
-
427
- @staticmethod
428
- def _parse_api_elements(api: str, context_api: bool) -> tuple:
429
- """
430
- Parse the elements from the given API.
431
- Elements returned are: entity_type, entity_id, context_api.
432
-
433
- Parameters
434
- ----------
435
- api : str
436
- Parsed API.
437
- context_api : bool
438
- True if the API is a context API.
439
-
440
- Returns
441
- -------
442
- tuple
443
- Parsed elements from the API.
444
- """
445
- # Split API path
446
- parsed = api.split("/")
447
-
448
- # Base API for versioned objects
449
-
450
- # POST /api/v1/<entity_type>
451
- # Returns entity_type, None, False
452
- if len(parsed) == 1 and not context_api:
453
- return parsed[0], None, context_api
454
-
455
- # GET/DELETE/UPDATE /api/v1/<entity_type>/<entity_id>
456
- # Return entity_type, entity_id, False
457
- if len(parsed) == 2 and not context_api:
458
- return parsed[0], parsed[1], context_api
459
-
460
- # Context API for versioned objects
461
-
462
- # POST /api/v1/-/<project>/<entity_type>
463
- # Returns entity_type, None, True
464
- if len(parsed) == 2 and context_api:
465
- return parsed[1], None, context_api
466
-
467
- # GET/DELETE/UPDATE /api/v1/-/<project>/<entity_type>/<entity_id>
468
- # Return entity_type, entity_id, True
469
- if len(parsed) == 3 and context_api:
470
- return parsed[1], parsed[2], context_api
471
-
472
- raise ValueError(f"Invalid API: {api}")
473
-
474
- def _get_project_spec(self, obj: dict, name: str) -> dict:
475
- """
476
- Enrich project object with spec (artifacts, functions, etc.).
477
-
478
- Parameters
479
- ----------
480
- obj : dict
481
- The project object.
482
- name : str
483
- The project name.
484
-
485
- Returns
486
- -------
487
- dict
488
- The project object with the spec.
489
- """
490
- # Deepcopy to avoid modifying the original object
491
- project = deepcopy(obj)
492
- spec = project.get("spec", {})
493
-
494
- # Get all entities associated with the project specs
495
- projects_entities = [k for k, _ in self._db.items() if k not in ["projects", "runs", "tasks"]]
496
-
497
- for entity_type in projects_entities:
498
- # Get all objects of the entity type for the project
499
- objs = self._db[entity_type]
500
-
501
- # Set empty list
502
- spec[entity_type] = []
503
-
504
- # Cycle through named objects
505
- for _, named_entities in objs.items():
506
- # Get latest version
507
- for version, entity in named_entities.items():
508
- if version != "latest":
509
- continue
510
-
511
- # Deepcopy to avoid modifying the original object
512
- copied = deepcopy(entity)
513
-
514
- # Remove spec if not embedded
515
- if not copied.get("metadata", {}).get("embedded", False):
516
- copied.pop("spec", None)
517
-
518
- # Add to project spec
519
- if copied["project"] == name:
520
- spec[entity_type].append(copied)
521
-
522
- return project
523
-
524
- ##############################
525
- # Utils
526
- ##############################
527
-
528
- @staticmethod
529
- def _format_msg(
530
- error_code: int,
531
- entity_type: str | None = None,
532
- entity_id: str | None = None,
533
- ) -> str:
534
- """
535
- Format a message.
536
-
537
- Parameters
538
- ----------
539
- error_code : int
540
- Error code identifying the type of error.
541
- entity_type : str, optional
542
- Entity type that caused the error.
543
- entity_id : str, optional
544
- Entity ID that caused the error.
545
-
546
- Returns
547
- -------
548
- str
549
- The formatted error message.
550
- """
551
- msg = {
552
- 1: f"Object '{entity_type}' to create is not valid",
553
- 2: f"Object '{entity_type}' with id '{entity_id}' already exists",
554
- 3: f"Object '{entity_type}' with id '{entity_id}' not found",
555
- 4: "Must provide entity_id to read an object",
556
- }
557
- return msg[error_code]
558
-
559
- ##############################
560
- # Interface methods
561
- ##############################
562
-
563
- @staticmethod
564
- def is_local() -> bool:
565
- """
566
- Declare if Client is local.
567
-
568
- Returns
569
- -------
570
- bool
571
- True
572
- """
573
- return True
@@ -1,15 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from __future__ import annotations
6
-
7
- from enum import Enum
8
-
9
-
10
- class LocalClientVar(Enum):
11
- """
12
- Variables for Local.
13
- """
14
-
15
- EMPTY = "EMPTY"
@@ -1,62 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from __future__ import annotations
6
-
7
- from digitalhub.stores.client._base.key_builder import ClientKeyBuilder
8
-
9
-
10
- class ClientLocalKeyBuilder(ClientKeyBuilder):
11
- """
12
- Class that build the key of entities.
13
- """
14
-
15
- def base_entity_key(self, entity_id: str) -> str:
16
- """
17
- Build for base entity key.
18
-
19
- Parameters
20
- ----------
21
- entity_id : str
22
- Entity id.
23
-
24
- Returns
25
- -------
26
- str
27
- Key.
28
- """
29
- return f"store://{entity_id}"
30
-
31
- def context_entity_key(
32
- self,
33
- project: str,
34
- entity_type: str,
35
- entity_kind: str,
36
- entity_name: str,
37
- entity_id: str | None = None,
38
- ) -> str:
39
- """
40
- Build for context entity key.
41
-
42
- Parameters
43
- ----------
44
- project : str
45
- Project name.
46
- entity_type : str
47
- Entity type.
48
- entity_kind : str
49
- Entity kind.
50
- entity_name : str
51
- Entity name.
52
- entity_id : str
53
- Entity ID.
54
-
55
- Returns
56
- -------
57
- str
58
- Key.
59
- """
60
- if entity_id is None:
61
- return f"store://{project}/{entity_type}/{entity_kind}/{entity_name}"
62
- return f"store://{project}/{entity_type}/{entity_kind}/{entity_name}:{entity_id}"