digitalhub 0.8.1__py3-none-any.whl → 0.9.0b0__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 (128) hide show
  1. digitalhub/__init__.py +19 -2
  2. digitalhub/client/_base/api_builder.py +16 -0
  3. digitalhub/client/_base/client.py +31 -0
  4. digitalhub/client/api.py +2 -38
  5. digitalhub/client/dhcore/api_builder.py +100 -0
  6. digitalhub/client/dhcore/client.py +77 -24
  7. digitalhub/client/dhcore/enums.py +26 -0
  8. digitalhub/client/dhcore/env.py +2 -2
  9. digitalhub/client/dhcore/utils.py +17 -17
  10. digitalhub/client/local/api_builder.py +100 -0
  11. digitalhub/client/local/client.py +20 -0
  12. digitalhub/context/api.py +3 -38
  13. digitalhub/context/builder.py +10 -23
  14. digitalhub/context/context.py +20 -92
  15. digitalhub/entities/_base/context/entity.py +30 -22
  16. digitalhub/entities/_base/entity/_constructors/metadata.py +12 -1
  17. digitalhub/entities/_base/entity/_constructors/name.py +1 -1
  18. digitalhub/entities/_base/entity/_constructors/spec.py +1 -1
  19. digitalhub/entities/_base/entity/_constructors/status.py +3 -2
  20. digitalhub/entities/_base/entity/builder.py +6 -1
  21. digitalhub/entities/_base/entity/entity.py +30 -10
  22. digitalhub/entities/_base/entity/metadata.py +22 -0
  23. digitalhub/entities/_base/entity/spec.py +7 -2
  24. digitalhub/entities/_base/executable/entity.py +8 -8
  25. digitalhub/entities/_base/material/entity.py +48 -16
  26. digitalhub/entities/_base/material/status.py +0 -31
  27. digitalhub/entities/_base/material/utils.py +106 -0
  28. digitalhub/entities/_base/project/entity.py +341 -0
  29. digitalhub/entities/_base/unversioned/entity.py +1 -23
  30. digitalhub/entities/_base/versioned/entity.py +0 -25
  31. digitalhub/entities/_commons/enums.py +103 -0
  32. digitalhub/entities/_commons/utils.py +83 -0
  33. digitalhub/entities/_operations/processor.py +1747 -0
  34. digitalhub/entities/artifact/_base/builder.py +1 -1
  35. digitalhub/entities/artifact/_base/entity.py +1 -1
  36. digitalhub/entities/artifact/artifact/builder.py +2 -1
  37. digitalhub/entities/artifact/crud.py +46 -29
  38. digitalhub/entities/artifact/utils.py +62 -0
  39. digitalhub/entities/dataitem/_base/builder.py +1 -1
  40. digitalhub/entities/dataitem/_base/entity.py +6 -6
  41. digitalhub/entities/dataitem/crud.py +50 -66
  42. digitalhub/entities/dataitem/dataitem/builder.py +2 -1
  43. digitalhub/entities/dataitem/iceberg/builder.py +2 -1
  44. digitalhub/entities/dataitem/table/builder.py +2 -1
  45. digitalhub/entities/dataitem/table/entity.py +5 -10
  46. digitalhub/entities/dataitem/table/models.py +4 -5
  47. digitalhub/entities/dataitem/utils.py +137 -0
  48. digitalhub/entities/function/_base/builder.py +1 -1
  49. digitalhub/entities/function/_base/entity.py +5 -1
  50. digitalhub/entities/function/crud.py +36 -17
  51. digitalhub/entities/model/_base/builder.py +1 -1
  52. digitalhub/entities/model/_base/entity.py +1 -1
  53. digitalhub/entities/model/crud.py +46 -29
  54. digitalhub/entities/model/huggingface/builder.py +2 -1
  55. digitalhub/entities/model/huggingface/spec.py +4 -2
  56. digitalhub/entities/model/mlflow/builder.py +2 -1
  57. digitalhub/entities/model/mlflow/models.py +17 -9
  58. digitalhub/entities/model/mlflow/spec.py +6 -1
  59. digitalhub/entities/model/mlflow/utils.py +4 -2
  60. digitalhub/entities/model/model/builder.py +2 -1
  61. digitalhub/entities/model/sklearn/builder.py +2 -1
  62. digitalhub/entities/model/utils.py +62 -0
  63. digitalhub/entities/project/_base/builder.py +2 -2
  64. digitalhub/entities/project/_base/entity.py +82 -272
  65. digitalhub/entities/project/crud.py +110 -91
  66. digitalhub/entities/project/utils.py +35 -0
  67. digitalhub/entities/run/_base/builder.py +3 -1
  68. digitalhub/entities/run/_base/entity.py +52 -54
  69. digitalhub/entities/run/_base/spec.py +11 -7
  70. digitalhub/entities/run/crud.py +35 -17
  71. digitalhub/entities/secret/_base/builder.py +2 -2
  72. digitalhub/entities/secret/_base/entity.py +4 -10
  73. digitalhub/entities/secret/crud.py +36 -21
  74. digitalhub/entities/task/_base/builder.py +14 -14
  75. digitalhub/entities/task/_base/entity.py +6 -6
  76. digitalhub/entities/task/_base/models.py +29 -6
  77. digitalhub/entities/task/_base/spec.py +44 -13
  78. digitalhub/entities/task/_base/utils.py +18 -0
  79. digitalhub/entities/task/crud.py +35 -15
  80. digitalhub/entities/workflow/_base/builder.py +1 -1
  81. digitalhub/entities/workflow/_base/entity.py +14 -6
  82. digitalhub/entities/workflow/crud.py +36 -17
  83. digitalhub/factory/utils.py +1 -1
  84. digitalhub/readers/_base/reader.py +2 -2
  85. digitalhub/readers/_commons/enums.py +13 -0
  86. digitalhub/readers/api.py +3 -2
  87. digitalhub/readers/factory.py +12 -6
  88. digitalhub/readers/pandas/reader.py +20 -8
  89. digitalhub/runtimes/_base.py +0 -7
  90. digitalhub/stores/_base/store.py +53 -9
  91. digitalhub/stores/builder.py +5 -5
  92. digitalhub/stores/local/store.py +37 -2
  93. digitalhub/stores/remote/store.py +25 -3
  94. digitalhub/stores/s3/store.py +34 -7
  95. digitalhub/stores/sql/store.py +112 -45
  96. digitalhub/utils/exceptions.py +6 -0
  97. digitalhub/utils/file_utils.py +60 -2
  98. digitalhub/utils/generic_utils.py +45 -4
  99. digitalhub/utils/io_utils.py +18 -0
  100. digitalhub/utils/uri_utils.py +153 -15
  101. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0b0.dist-info}/METADATA +2 -2
  102. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0b0.dist-info}/RECORD +110 -113
  103. test/testkfp.py +4 -1
  104. digitalhub/datastores/_base/datastore.py +0 -85
  105. digitalhub/datastores/api.py +0 -37
  106. digitalhub/datastores/builder.py +0 -110
  107. digitalhub/datastores/local/datastore.py +0 -50
  108. digitalhub/datastores/remote/__init__.py +0 -0
  109. digitalhub/datastores/remote/datastore.py +0 -31
  110. digitalhub/datastores/s3/__init__.py +0 -0
  111. digitalhub/datastores/s3/datastore.py +0 -46
  112. digitalhub/datastores/sql/__init__.py +0 -0
  113. digitalhub/datastores/sql/datastore.py +0 -68
  114. digitalhub/entities/_base/api_utils.py +0 -620
  115. digitalhub/entities/_base/crud.py +0 -468
  116. digitalhub/entities/function/_base/models.py +0 -118
  117. digitalhub/entities/utils/__init__.py +0 -0
  118. digitalhub/entities/utils/api.py +0 -346
  119. digitalhub/entities/utils/entity_types.py +0 -19
  120. digitalhub/entities/utils/state.py +0 -31
  121. digitalhub/entities/utils/utils.py +0 -202
  122. /digitalhub/{context → entities/_base/project}/__init__.py +0 -0
  123. /digitalhub/{datastores → entities/_commons}/__init__.py +0 -0
  124. /digitalhub/{datastores/_base → entities/_operations}/__init__.py +0 -0
  125. /digitalhub/{datastores/local → readers/_commons}/__init__.py +0 -0
  126. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0b0.dist-info}/LICENSE.txt +0 -0
  127. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0b0.dist-info}/WHEEL +0 -0
  128. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0b0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from digitalhub.client._base.api_builder import ClientApiBuilder
4
+ from digitalhub.entities._commons.enums import ApiCategories, BackendOperations
5
+ from digitalhub.utils.exceptions import BackendError
6
+
7
+ API_BASE = "/api/v1"
8
+ API_CONTEXT = f"{API_BASE}/-"
9
+
10
+
11
+ class ClientLocalApiBuilder(ClientApiBuilder):
12
+ """
13
+ This class is used to build the API for the Local client.
14
+ """
15
+
16
+ def build_api(self, category: str, operation: str, **kwargs) -> str:
17
+ """
18
+ Build the API for the client.
19
+
20
+ Parameters
21
+ ----------
22
+ category : str
23
+ API category.
24
+ operation : str
25
+ API operation.
26
+ **kwargs : dict
27
+ Additional parameters.
28
+
29
+ Returns
30
+ -------
31
+ str
32
+ API formatted.
33
+ """
34
+ if category == ApiCategories.BASE.value:
35
+ return self.build_api_base(operation, **kwargs)
36
+ return self.build_api_context(operation, **kwargs)
37
+
38
+ def build_api_base(self, operation: str, **kwargs) -> str:
39
+ """
40
+ Build the base API for the client.
41
+
42
+ Parameters
43
+ ----------
44
+ operation : str
45
+ API operation.
46
+ **kwargs : dict
47
+ Additional parameters.
48
+
49
+ Returns
50
+ -------
51
+ str
52
+ API formatted.
53
+ """
54
+ entity_type = kwargs["entity_type"] + "s"
55
+ if operation in (
56
+ BackendOperations.CREATE.value,
57
+ BackendOperations.LIST.value,
58
+ ):
59
+ return f"{API_BASE}/{entity_type}"
60
+ elif operation in (
61
+ BackendOperations.READ.value,
62
+ BackendOperations.UPDATE.value,
63
+ BackendOperations.DELETE.value,
64
+ ):
65
+ return f"{API_BASE}/{entity_type}/{kwargs['entity_name']}"
66
+ elif operation == BackendOperations.SHARE.value:
67
+ raise BackendError("Share API not implemented for Local.")
68
+ raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
69
+
70
+ def build_api_context(self, operation: str, **kwargs) -> str:
71
+ """
72
+ Build the context API for the client.
73
+ """
74
+ entity_type = kwargs["entity_type"] + "s"
75
+ project = kwargs["project"]
76
+ if operation in (
77
+ BackendOperations.CREATE.value,
78
+ BackendOperations.LIST.value,
79
+ ):
80
+ return f"{API_CONTEXT}/{project}/{entity_type}"
81
+ elif operation in (
82
+ BackendOperations.READ.value,
83
+ BackendOperations.UPDATE.value,
84
+ BackendOperations.DELETE.value,
85
+ ):
86
+ return f"{API_CONTEXT}/{project}/{entity_type}/{kwargs['entity_id']}"
87
+ elif operation == BackendOperations.LOGS.value:
88
+ raise BackendError("Logs run API not implemented for Local.")
89
+ elif operation == BackendOperations.STOP.value:
90
+ raise BackendError("Stop run API not implemented for Local.")
91
+ elif operation == BackendOperations.RESUME.value:
92
+ raise BackendError("Resume run API not implemented for Local.")
93
+ elif operation == BackendOperations.DATA.value:
94
+ raise BackendError("Secret API (read/set value) not implemented for Local.")
95
+ elif operation == BackendOperations.FILES.value:
96
+ raise BackendError("Files API not implemented for Local.")
97
+ elif operation == BackendOperations.SEARCH.value:
98
+ raise BackendError("Search API not implemented for Local.")
99
+
100
+ raise BackendError(f"Invalid operation '{operation}' for entity type '{entity_type}' in Local.")
@@ -4,6 +4,7 @@ from copy import deepcopy
4
4
  from datetime import datetime, timezone
5
5
 
6
6
  from digitalhub.client._base.client import Client
7
+ from digitalhub.client.local.api_builder import ClientLocalApiBuilder
7
8
  from digitalhub.utils.exceptions import BackendError
8
9
 
9
10
 
@@ -22,6 +23,7 @@ class ClientLocal(Client):
22
23
 
23
24
  def __init__(self) -> None:
24
25
  super().__init__()
26
+ self._api_builder = ClientLocalApiBuilder()
25
27
  self._db: dict[str, dict[str, dict]] = {}
26
28
 
27
29
  ##############################
@@ -349,6 +351,24 @@ class ClientLocal(Client):
349
351
  except IndexError:
350
352
  raise IndexError("No objects found")
351
353
 
354
+ def search_objects(self, api: str, **kwargs) -> dict:
355
+ """
356
+ Search objects from Local.
357
+
358
+ Parameters
359
+ ----------
360
+ api : str
361
+ Search API.
362
+ **kwargs : dict
363
+ Keyword arguments to pass to the request.
364
+
365
+ Returns
366
+ -------
367
+ dict
368
+ Response objects.
369
+ """
370
+ raise NotImplementedError("Local client does not support search_objects.")
371
+
352
372
  ##############################
353
373
  # Helpers
354
374
  ##############################
digitalhub/context/api.py CHANGED
@@ -3,45 +3,26 @@ from __future__ import annotations
3
3
  import typing
4
4
 
5
5
  from digitalhub.context.builder import context_builder
6
- from digitalhub.utils.exceptions import ContextError
7
6
 
8
7
  if typing.TYPE_CHECKING:
9
8
  from digitalhub.context.context import Context
10
9
  from digitalhub.entities.project._base.entity import Project
11
10
 
12
11
 
13
- def check_context(project: str) -> None:
14
- """
15
- Check if the given project is in the context.
16
-
17
- Parameters
18
- ----------
19
- project : str
20
- Project name.
21
-
22
- Returns
23
- -------
24
- bool
25
- True if the project is in the context, False otherwise.
26
- """
27
- if project not in context_builder._instances:
28
- raise ContextError
29
-
30
-
31
- def set_context(project: Project) -> None:
12
+ def build_context(project: Project, overwrite: bool = False) -> None:
32
13
  """
33
14
  Wrapper for ContextBuilder.build().
34
15
 
35
16
  Parameters
36
17
  ----------
37
18
  project : Project
38
- The project object used to set the current context.
19
+ The project object used to build the context.
39
20
 
40
21
  Returns
41
22
  -------
42
23
  None
43
24
  """
44
- context_builder.build(project)
25
+ context_builder.build(project, overwrite)
45
26
 
46
27
 
47
28
  def get_context(project: str) -> Context:
@@ -61,22 +42,6 @@ def get_context(project: str) -> Context:
61
42
  return context_builder.get(project)
62
43
 
63
44
 
64
- def set_context_object(context: Context) -> None:
65
- """
66
- Wrapper for ContextBuilder.set().
67
-
68
- Parameters
69
- ----------
70
- context : Context
71
- The context to set.
72
-
73
- Returns
74
- -------
75
- None
76
- """
77
- context_builder.set(context)
78
-
79
-
80
45
  def delete_context(project: str) -> None:
81
46
  """
82
47
  Wrapper for ContextBuilder.remove().
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import typing
4
4
 
5
5
  from digitalhub.context.context import Context
6
+ from digitalhub.utils.exceptions import ContextError
6
7
 
7
8
  if typing.TYPE_CHECKING:
8
9
  from digitalhub.entities.project._base.entity import Project
@@ -19,7 +20,7 @@ class ContextBuilder:
19
20
  def __init__(self) -> None:
20
21
  self._instances: dict[str, Context] = {}
21
22
 
22
- def build(self, project_object: Project) -> None:
23
+ def build(self, project_object: Project, overwrite: bool = False) -> None:
23
24
  """
24
25
  Add a project as context.
25
26
 
@@ -27,12 +28,15 @@ class ContextBuilder:
27
28
  ----------
28
29
  project_object : Project
29
30
  The project to add.
31
+ overwrite : bool
32
+ If True, the project will be overwritten if it already exists.
30
33
 
31
34
  Returns
32
35
  -------
33
36
  None
34
37
  """
35
- self._instances[project_object.name] = Context(project_object)
38
+ if (project_object.name not in self._instances) or overwrite:
39
+ self._instances[project_object.name] = Context(project_object)
36
40
 
37
41
  def get(self, project: str) -> Context:
38
42
  """
@@ -53,12 +57,10 @@ class ContextBuilder:
53
57
  ValueError
54
58
  If the project is not in the context.
55
59
  """
56
- ctx = self._instances.get(project)
57
- if ctx is None:
58
- raise ValueError(
59
- f"Context '{project}' not found. Please get or create a project named '{project}' to access its objects."
60
- )
61
- return ctx
60
+ try:
61
+ return self._instances[project]
62
+ except KeyError:
63
+ raise ContextError(f"Context '{project}' not found. Get or create a project named '{project}'.")
62
64
 
63
65
  def remove(self, project: str) -> None:
64
66
  """
@@ -75,20 +77,5 @@ class ContextBuilder:
75
77
  """
76
78
  self._instances.pop(project, None)
77
79
 
78
- def set(self, context: Context) -> None:
79
- """
80
- Set the context.
81
-
82
- Parameters
83
- ----------
84
- context : Context
85
- The context to set.
86
-
87
- Returns
88
- -------
89
- None
90
- """
91
- self._instances[context.name] = context
92
-
93
80
 
94
81
  context_builder = ContextBuilder()
@@ -13,8 +13,6 @@ class Context:
13
13
  some information about the project, such as the project name,
14
14
  a client instance (local or non-local), the local context
15
15
  project path and information about client locality.
16
- It exposes CRUD operations for the entities and act as a layer
17
- between the project object and its client.
18
16
  """
19
17
 
20
18
  def __init__(self, project: Project) -> None:
@@ -22,115 +20,45 @@ class Context:
22
20
  self.client = project._client
23
21
  self.local = project._client.is_local()
24
22
  self.root = Path(project.spec.context)
23
+ self.root.mkdir(parents=True, exist_ok=True)
25
24
 
26
- def create_object(self, api: str, obj: dict, **kwargs) -> dict:
27
- """
28
- Create an object.
29
-
30
- Parameters
31
- ----------
32
- api : str
33
- Create API.
34
- obj : dict
35
- Object to create.
36
- **kwargs : dict
37
- Keyword arguments passed to the request.
38
-
39
- Returns
40
- -------
41
- dict
42
- Response object.
43
- """
44
- return self.client.create_object(api, obj, **kwargs)
45
-
46
- def read_object(self, api: str, **kwargs) -> dict:
47
- """
48
- Read an object.
49
-
50
- Parameters
51
- ----------
52
- api : str
53
- Read API.
54
- **kwargs : dict
55
- Keyword arguments passed to the request.
56
-
57
- Returns
58
- -------
59
- dict
60
- Response object.
61
- """
62
- return self.client.read_object(api, **kwargs)
63
-
64
- def update_object(self, api: str, obj: dict, **kwargs) -> dict:
65
- """
66
- Update an object.
67
-
68
- Parameters
69
- ----------
70
- api : str
71
- Update API.
72
- obj : dict
73
- Object to update.
74
- **kwargs : dict
75
- Keyword arguments passed to the request.
76
-
77
- Returns
78
- -------
79
- dict
80
- Response object.
81
- """
82
- return self.client.update_object(api, obj, **kwargs)
25
+ self.is_running: bool = False
26
+ self._run_ctx: str = None
83
27
 
84
- def delete_object(self, api: str, **kwargs) -> dict:
28
+ def set_run(self, run_ctx: str) -> None:
85
29
  """
86
- Delete an object.
30
+ Set run identifier.
87
31
 
88
32
  Parameters
89
33
  ----------
90
- api : str
91
- Delete API.
92
- **kwargs : dict
93
- Keyword arguments passed to the request.
34
+ run_ctx : str
35
+ Run key.
94
36
 
95
37
  Returns
96
38
  -------
97
- dict
98
- Response object.
39
+ None
99
40
  """
100
- return self.client.delete_object(api, **kwargs)
41
+ self.is_running = True
42
+ self._run_ctx = run_ctx
101
43
 
102
- def list_objects(self, api: str, **kwargs) -> dict:
44
+ def unset_run(self) -> None:
103
45
  """
104
- List objects.
105
-
106
- Parameters
107
- ----------
108
- api : str
109
- The api to list the objects with.
110
- **kwargs : dict
111
- Keyword arguments passed to the request.
46
+ Unset run identifier.
112
47
 
113
48
  Returns
114
49
  -------
115
- dict
116
- The list of objects.
50
+ None
117
51
  """
118
- return self.client.list_objects(api, **kwargs)
52
+ self.is_running = False
53
+ self._run_ctx = None
119
54
 
120
- def list_first_object(self, api: str, **kwargs) -> dict:
55
+ def get_run_ctx(self) -> str:
121
56
  """
122
- List first objects.
123
-
124
- Parameters
125
- ----------
126
- api : str
127
- The api to list the objects with.
128
- **kwargs : dict
129
- Keyword arguments passed to the request.
57
+ Get run identifier.
130
58
 
131
59
  Returns
132
60
  -------
133
- dict
134
- The list of objects.
61
+ str
62
+ Run key.
135
63
  """
136
- return self.client.list_first_object(api, **kwargs)
64
+ return self._run_ctx
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
  import typing
4
4
 
5
5
  from digitalhub.context.api import get_context
6
- from digitalhub.entities._base.api_utils import create_entity_api_ctx, read_entity_api_ctx, update_entity_api_ctx
7
6
  from digitalhub.entities._base.entity.entity import Entity
7
+ from digitalhub.entities._operations.processor import processor
8
8
  from digitalhub.utils.generic_utils import get_timestamp
9
+ from digitalhub.utils.io_utils import write_yaml
9
10
 
10
11
  if typing.TYPE_CHECKING:
11
12
  from digitalhub.context.context import Context
@@ -26,7 +27,11 @@ class ContextEntity(Entity):
26
27
  ) -> None:
27
28
  super().__init__(kind, metadata, spec, status, user)
28
29
  self.project = project
29
- self._obj_attr.extend(["project"])
30
+ self.name: str
31
+ self.id: str
32
+
33
+ # Different behaviour for versioned and unversioned
34
+ self._obj_attr.extend(["project", "id", "name"])
30
35
 
31
36
  ##############################
32
37
  # Save / Refresh / Export
@@ -46,49 +51,52 @@ class ContextEntity(Entity):
46
51
  ContextEntity
47
52
  Entity saved.
48
53
  """
49
- obj = self.to_dict()
50
- if not update:
51
- return self._save(obj)
52
- return self._update(obj)
54
+ if update:
55
+ return self._update()
56
+ return self._save()
53
57
 
54
- def _save(self, obj: dict) -> ContextEntity:
58
+ def _save(self) -> ContextEntity:
55
59
  """
56
60
  Save entity into backend.
57
61
 
58
- Parameters
59
- ----------
60
- obj : dict
61
- Object instance as dictionary.
62
-
63
62
  Returns
64
63
  -------
65
64
  ContextEntity
66
65
  Entity saved.
67
66
  """
68
- new_obj = create_entity_api_ctx(self.project, self.ENTITY_TYPE, obj)
67
+ new_obj = processor.create_context_entity(_entity=self)
69
68
  self._update_attributes(new_obj)
70
69
  return self
71
70
 
72
- def _update(self, obj: dict) -> ContextEntity:
71
+ def _update(self) -> ContextEntity:
73
72
  """
74
73
  Update entity in backend.
75
74
 
76
- Parameters
77
- ----------
78
- obj : dict
79
- Object instance as dictionary.
80
-
81
75
  Returns
82
76
  -------
83
77
  ContextEntity
84
78
  Entity updated.
85
79
  """
86
80
  if self._context().local:
87
- self.metadata.updated = obj["metadata"]["updated"] = get_timestamp()
88
- new_obj = update_entity_api_ctx(self.project, self.ENTITY_TYPE, self.id, obj)
81
+ self.metadata.updated = self.metadata.updated = get_timestamp()
82
+ new_obj = processor.update_context_entity(self.project, self.ENTITY_TYPE, self.id, self.to_dict())
89
83
  self._update_attributes(new_obj)
90
84
  return self
91
85
 
86
+ def export(self) -> str:
87
+ """
88
+ Export object as a YAML file in the context folder.
89
+
90
+ Returns
91
+ -------
92
+ str
93
+ Exported filepath.
94
+ """
95
+ obj = self.to_dict()
96
+ pth = self._context().root / f"{self.ENTITY_TYPE}s-{self.id}.yaml"
97
+ write_yaml(pth, obj)
98
+ return str(pth)
99
+
92
100
  def refresh(self) -> ContextEntity:
93
101
  """
94
102
  Refresh object from backend.
@@ -98,7 +106,7 @@ class ContextEntity(Entity):
98
106
  ContextEntity
99
107
  Entity refreshed.
100
108
  """
101
- new_obj = read_entity_api_ctx(self.key)
109
+ new_obj = processor.read_context_entity(self.key)
102
110
  self._update_attributes(new_obj)
103
111
  return self
104
112
 
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from digitalhub.entities._base.entity.metadata import Metadata
3
+ from pydantic import ValidationError
4
+
5
+ from digitalhub.entities._base.entity.metadata import Metadata, RelationshipValidator
6
+ from digitalhub.utils.exceptions import BuilderError
4
7
  from digitalhub.utils.generic_utils import get_timestamp
5
8
 
6
9
 
@@ -41,4 +44,12 @@ def parse_arguments(**kwargs) -> dict:
41
44
  kwargs["created"] = get_timestamp()
42
45
  if "updated" not in kwargs or kwargs["updated"] is None:
43
46
  kwargs["updated"] = kwargs["created"]
47
+ if "relationships" in kwargs:
48
+ if not isinstance(kwargs["relationships"], list):
49
+ raise BuilderError("Invalid relationships format. Must be a list of maps.")
50
+ for relationship in kwargs["relationships"]:
51
+ try:
52
+ RelationshipValidator(**relationship)
53
+ except ValidationError as e:
54
+ raise BuilderError(f"Malformed relationship: {e}") from e
44
55
  return kwargs
@@ -10,7 +10,7 @@ class NameValidator(BaseModel):
10
10
  Validate name format.
11
11
  """
12
12
 
13
- name: str = Field(min_length=1, max_length=256, regex=NAME_REGEX)
13
+ name: str = Field(min_length=1, max_length=256, pattern=NAME_REGEX)
14
14
 
15
15
 
16
16
  def build_name(name: str) -> str:
@@ -29,5 +29,5 @@ def build_spec(spec_cls: Spec, spec_validator: SpecValidator, validate: bool = T
29
29
  Spec object.
30
30
  """
31
31
  if validate:
32
- kwargs = spec_validator(**kwargs).dict(by_alias=True, exclude_none=True)
32
+ kwargs = spec_validator(**kwargs).to_dict()
33
33
  return spec_cls(**kwargs)
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
 
5
- from digitalhub.entities.utils.state import State
5
+ from digitalhub.entities._commons.enums import State
6
+ from digitalhub.utils.exceptions import BuilderError
6
7
 
7
8
  if typing.TYPE_CHECKING:
8
9
  from digitalhub.entities._base.entity.status import Status
@@ -48,5 +49,5 @@ def parse_arguments(**kwargs) -> dict:
48
49
  kwargs["state"] = State.CREATED.value
49
50
  else:
50
51
  if kwargs["state"] not in State.__members__:
51
- raise ValueError(f"Invalid state: {state}")
52
+ raise BuilderError(f"Invalid state: {state}")
52
53
  return kwargs
@@ -106,7 +106,12 @@ class EntityBuilder:
106
106
  Spec
107
107
  Spec object.
108
108
  """
109
- return build_spec(self.ENTITY_SPEC_CLASS, self.ENTITY_SPEC_VALIDATOR, validate=validate, **kwargs)
109
+ return build_spec(
110
+ self.ENTITY_SPEC_CLASS,
111
+ self.ENTITY_SPEC_VALIDATOR,
112
+ validate=validate,
113
+ **kwargs,
114
+ )
110
115
 
111
116
  def build_status(self, **kwargs) -> Status:
112
117
  """
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import typing
4
- from abc import ABCMeta, abstractmethod
4
+ from abc import abstractmethod
5
5
 
6
6
  from digitalhub.entities._base._base.entity import Base
7
- from digitalhub.factory.api import build_entity_from_dict
8
7
 
9
8
  if typing.TYPE_CHECKING:
10
9
  from digitalhub.entities._base.entity.metadata import Metadata
@@ -12,7 +11,7 @@ if typing.TYPE_CHECKING:
12
11
  from digitalhub.entities._base.entity.status import Status
13
12
 
14
13
 
15
- class Entity(Base, metaclass=ABCMeta):
14
+ class Entity(Base):
16
15
  """
17
16
  Abstract class for entities.
18
17
 
@@ -56,7 +55,7 @@ class Entity(Base, metaclass=ABCMeta):
56
55
  Abstract refresh method.
57
56
  """
58
57
 
59
- def _update_attributes(self, obj: dict) -> None:
58
+ def _update_attributes(self, obj: Entity) -> None:
60
59
  """
61
60
  Update attributes.
62
61
 
@@ -69,18 +68,39 @@ class Entity(Base, metaclass=ABCMeta):
69
68
  -------
70
69
  None
71
70
  """
72
- new_obj = build_entity_from_dict(obj)
73
- self.metadata = new_obj.metadata
74
- self.spec = new_obj.spec
75
- self.status = new_obj.status
76
- self.user = new_obj.user
71
+ self.metadata = obj.metadata
72
+ self.spec = obj.spec
73
+ self.status = obj.status
74
+ self.user = obj.user
77
75
 
78
76
  @abstractmethod
79
- def export(self, filename: str | None = None) -> str:
77
+ def export(self) -> str:
80
78
  """
81
79
  Abstract export method.
82
80
  """
83
81
 
82
+ def add_relationship(self, relation: str, source: str, dest: str) -> None:
83
+ """
84
+ Add relationship to entity metadata.
85
+
86
+ Parameters
87
+ ----------
88
+ relation : str
89
+ The type of relationship.
90
+ source : str
91
+ The source entity.
92
+ dest : str
93
+ The target entity..
94
+
95
+ Returns
96
+ -------
97
+ None
98
+ """
99
+ if self.metadata.relationships is None:
100
+ self.metadata.relationships = []
101
+ obj = {"type": relation, "source": source, "dest": dest}
102
+ self.metadata.relationships.append(obj)
103
+
84
104
  def to_dict(self) -> dict:
85
105
  """
86
106
  Override default to_dict method to add the possibility to exclude