digitalhub 0.8.1__py3-none-any.whl → 0.9.0__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 (134) hide show
  1. digitalhub/__init__.py +19 -2
  2. digitalhub/client/_base/api_builder.py +16 -0
  3. digitalhub/client/_base/client.py +67 -0
  4. digitalhub/client/_base/key_builder.py +52 -0
  5. digitalhub/client/api.py +2 -38
  6. digitalhub/client/dhcore/api_builder.py +100 -0
  7. digitalhub/client/dhcore/client.py +81 -25
  8. digitalhub/client/dhcore/enums.py +27 -0
  9. digitalhub/client/dhcore/env.py +2 -2
  10. digitalhub/client/dhcore/key_builder.py +58 -0
  11. digitalhub/client/dhcore/utils.py +17 -17
  12. digitalhub/client/local/api_builder.py +100 -0
  13. digitalhub/client/local/client.py +22 -0
  14. digitalhub/client/local/key_builder.py +58 -0
  15. digitalhub/context/api.py +3 -38
  16. digitalhub/context/builder.py +10 -23
  17. digitalhub/context/context.py +20 -92
  18. digitalhub/entities/_base/context/entity.py +30 -22
  19. digitalhub/entities/_base/entity/_constructors/metadata.py +12 -1
  20. digitalhub/entities/_base/entity/_constructors/name.py +1 -1
  21. digitalhub/entities/_base/entity/_constructors/spec.py +1 -1
  22. digitalhub/entities/_base/entity/_constructors/status.py +3 -2
  23. digitalhub/entities/_base/entity/builder.py +6 -1
  24. digitalhub/entities/_base/entity/entity.py +32 -10
  25. digitalhub/entities/_base/entity/metadata.py +22 -0
  26. digitalhub/entities/_base/entity/spec.py +7 -2
  27. digitalhub/entities/_base/executable/entity.py +8 -8
  28. digitalhub/entities/_base/material/entity.py +49 -17
  29. digitalhub/entities/_base/material/status.py +0 -31
  30. digitalhub/entities/_base/material/utils.py +106 -0
  31. digitalhub/entities/_base/project/entity.py +341 -0
  32. digitalhub/entities/_base/unversioned/entity.py +3 -24
  33. digitalhub/entities/_base/versioned/entity.py +2 -26
  34. digitalhub/entities/_commons/enums.py +103 -0
  35. digitalhub/entities/_commons/utils.py +83 -0
  36. digitalhub/entities/_operations/processor.py +1873 -0
  37. digitalhub/entities/artifact/_base/builder.py +1 -1
  38. digitalhub/entities/artifact/_base/entity.py +1 -1
  39. digitalhub/entities/artifact/artifact/builder.py +2 -1
  40. digitalhub/entities/artifact/crud.py +46 -29
  41. digitalhub/entities/artifact/utils.py +62 -0
  42. digitalhub/entities/dataitem/_base/builder.py +1 -1
  43. digitalhub/entities/dataitem/_base/entity.py +6 -6
  44. digitalhub/entities/dataitem/crud.py +50 -66
  45. digitalhub/entities/dataitem/dataitem/builder.py +2 -1
  46. digitalhub/entities/dataitem/iceberg/builder.py +2 -1
  47. digitalhub/entities/dataitem/table/builder.py +2 -1
  48. digitalhub/entities/dataitem/table/entity.py +5 -10
  49. digitalhub/entities/dataitem/table/models.py +4 -5
  50. digitalhub/entities/dataitem/utils.py +137 -0
  51. digitalhub/entities/function/_base/builder.py +1 -1
  52. digitalhub/entities/function/_base/entity.py +6 -2
  53. digitalhub/entities/function/crud.py +36 -17
  54. digitalhub/entities/model/_base/builder.py +1 -1
  55. digitalhub/entities/model/_base/entity.py +1 -1
  56. digitalhub/entities/model/crud.py +46 -29
  57. digitalhub/entities/model/huggingface/builder.py +2 -1
  58. digitalhub/entities/model/huggingface/spec.py +4 -2
  59. digitalhub/entities/model/mlflow/builder.py +2 -1
  60. digitalhub/entities/model/mlflow/models.py +17 -9
  61. digitalhub/entities/model/mlflow/spec.py +6 -1
  62. digitalhub/entities/model/mlflow/utils.py +4 -2
  63. digitalhub/entities/model/model/builder.py +2 -1
  64. digitalhub/entities/model/sklearn/builder.py +2 -1
  65. digitalhub/entities/model/utils.py +62 -0
  66. digitalhub/entities/project/_base/builder.py +2 -2
  67. digitalhub/entities/project/_base/entity.py +82 -272
  68. digitalhub/entities/project/crud.py +110 -91
  69. digitalhub/entities/project/utils.py +35 -0
  70. digitalhub/entities/run/_base/builder.py +3 -1
  71. digitalhub/entities/run/_base/entity.py +52 -54
  72. digitalhub/entities/run/_base/spec.py +15 -7
  73. digitalhub/entities/run/crud.py +35 -17
  74. digitalhub/entities/secret/_base/builder.py +2 -2
  75. digitalhub/entities/secret/_base/entity.py +4 -10
  76. digitalhub/entities/secret/crud.py +36 -21
  77. digitalhub/entities/task/_base/builder.py +14 -14
  78. digitalhub/entities/task/_base/entity.py +21 -14
  79. digitalhub/entities/task/_base/models.py +35 -6
  80. digitalhub/entities/task/_base/spec.py +50 -13
  81. digitalhub/entities/task/_base/utils.py +18 -0
  82. digitalhub/entities/task/crud.py +35 -15
  83. digitalhub/entities/workflow/_base/builder.py +1 -1
  84. digitalhub/entities/workflow/_base/entity.py +22 -6
  85. digitalhub/entities/workflow/crud.py +36 -17
  86. digitalhub/factory/utils.py +1 -1
  87. digitalhub/readers/_base/reader.py +2 -2
  88. digitalhub/readers/_commons/enums.py +13 -0
  89. digitalhub/readers/api.py +3 -2
  90. digitalhub/readers/factory.py +12 -6
  91. digitalhub/readers/pandas/reader.py +20 -8
  92. digitalhub/runtimes/_base.py +0 -7
  93. digitalhub/runtimes/enums.py +12 -0
  94. digitalhub/stores/_base/store.py +59 -11
  95. digitalhub/stores/builder.py +5 -5
  96. digitalhub/stores/local/store.py +43 -4
  97. digitalhub/stores/remote/store.py +31 -5
  98. digitalhub/stores/s3/store.py +129 -48
  99. digitalhub/stores/sql/store.py +122 -47
  100. digitalhub/utils/exceptions.py +6 -0
  101. digitalhub/utils/file_utils.py +60 -2
  102. digitalhub/utils/generic_utils.py +45 -4
  103. digitalhub/utils/io_utils.py +18 -0
  104. digitalhub/utils/s3_utils.py +17 -0
  105. digitalhub/utils/uri_utils.py +153 -15
  106. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0.dist-info}/LICENSE.txt +1 -1
  107. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0.dist-info}/METADATA +3 -3
  108. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0.dist-info}/RECORD +116 -114
  109. test/local/instances/test_validate.py +55 -0
  110. test/testkfp.py +4 -1
  111. digitalhub/datastores/_base/datastore.py +0 -85
  112. digitalhub/datastores/api.py +0 -37
  113. digitalhub/datastores/builder.py +0 -110
  114. digitalhub/datastores/local/datastore.py +0 -50
  115. digitalhub/datastores/remote/__init__.py +0 -0
  116. digitalhub/datastores/remote/datastore.py +0 -31
  117. digitalhub/datastores/s3/__init__.py +0 -0
  118. digitalhub/datastores/s3/datastore.py +0 -46
  119. digitalhub/datastores/sql/__init__.py +0 -0
  120. digitalhub/datastores/sql/datastore.py +0 -68
  121. digitalhub/entities/_base/api_utils.py +0 -620
  122. digitalhub/entities/_base/crud.py +0 -468
  123. digitalhub/entities/function/_base/models.py +0 -118
  124. digitalhub/entities/utils/__init__.py +0 -0
  125. digitalhub/entities/utils/api.py +0 -346
  126. digitalhub/entities/utils/entity_types.py +0 -19
  127. digitalhub/entities/utils/state.py +0 -31
  128. digitalhub/entities/utils/utils.py +0 -202
  129. /digitalhub/{context → entities/_base/project}/__init__.py +0 -0
  130. /digitalhub/{datastores → entities/_commons}/__init__.py +0 -0
  131. /digitalhub/{datastores/_base → entities/_operations}/__init__.py +0 -0
  132. /digitalhub/{datastores/local → readers/_commons}/__init__.py +0 -0
  133. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0.dist-info}/WHEEL +0 -0
  134. {digitalhub-0.8.1.dist-info → digitalhub-0.9.0.dist-info}/top_level.txt +0 -0
@@ -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,41 @@ 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, dest: str, source: str | None = None) -> None:
83
+ """
84
+ Add relationship to entity metadata.
85
+
86
+ Parameters
87
+ ----------
88
+ relation : str
89
+ The type of relationship.
90
+ dest : str
91
+ The target entity.
92
+ source : str
93
+ The source entity.
94
+
95
+ Returns
96
+ -------
97
+ None
98
+ """
99
+ if self.metadata.relationships is None:
100
+ self.metadata.relationships = []
101
+ obj = {"type": relation, "dest": dest}
102
+ if source is not None:
103
+ obj["source"] = source
104
+ self.metadata.relationships.append(obj)
105
+
84
106
  def to_dict(self) -> dict:
85
107
  """
86
108
  Override default to_dict method to add the possibility to exclude
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
3
5
  from digitalhub.entities._base._base.entity import Base
6
+ from digitalhub.entities._commons.enums import Relationship
4
7
 
5
8
 
6
9
  class Metadata(Base):
@@ -24,6 +27,7 @@ class Metadata(Base):
24
27
  updated: str | None = None,
25
28
  updated_by: str | None = None,
26
29
  embedded: bool | None = None,
30
+ relationships: list[dict] | None = None,
27
31
  ref: str | None = None,
28
32
  **kwargs,
29
33
  ) -> None:
@@ -37,6 +41,7 @@ class Metadata(Base):
37
41
  self.created_by = created_by
38
42
  self.updated_by = updated_by
39
43
  self.embedded = embedded
44
+ self.relationships = relationships
40
45
  self.ref = ref
41
46
 
42
47
  self._any_setter(**kwargs)
@@ -57,3 +62,20 @@ class Metadata(Base):
57
62
  An entity metadata object.
58
63
  """
59
64
  return cls(**obj)
65
+
66
+
67
+ class RelationshipValidator(BaseModel):
68
+ """
69
+ A class representing the relationship of an entity.
70
+ """
71
+
72
+ model_config = ConfigDict(use_enum_values=True)
73
+
74
+ type_: Relationship = Field(default=None, alias="type")
75
+ """The type of relationship."""
76
+
77
+ source: str = None
78
+ """The source entity."""
79
+
80
+ dest: str = None
81
+ """The target entity."""
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pydantic import BaseModel
3
+ from pydantic import BaseModel, ConfigDict
4
4
 
5
5
  from digitalhub.entities._base._base.entity import Base
6
6
 
@@ -30,7 +30,7 @@ class Spec(Base):
30
30
  return cls(**obj)
31
31
 
32
32
 
33
- class SpecValidator(BaseModel, extra="ignore"):
33
+ class SpecValidator(BaseModel):
34
34
  """
35
35
  A class representing the parameters of an entity.
36
36
  This base class is used to define the parameters of an entity
@@ -38,6 +38,11 @@ class SpecValidator(BaseModel, extra="ignore"):
38
38
  to the constructor.
39
39
  """
40
40
 
41
+ model_config = ConfigDict(extra="ignore", use_enum_values=True)
42
+
43
+ def to_dict(self) -> dict:
44
+ return self.model_dump(by_alias=True, exclude_none=True)
45
+
41
46
 
42
47
  class MaterialSpec(Spec):
43
48
  """
@@ -2,11 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
 
5
- from digitalhub.entities._base.api_utils import list_entity_api_ctx
6
5
  from digitalhub.entities._base.versioned.entity import VersionedEntity
6
+ from digitalhub.entities._commons.enums import EntityTypes
7
+ from digitalhub.entities._operations.processor import processor
7
8
  from digitalhub.entities.run.crud import delete_run, get_run, list_runs
8
9
  from digitalhub.entities.task.crud import delete_task
9
- from digitalhub.entities.utils.entity_types import EntityTypes
10
10
  from digitalhub.factory.api import build_entity_from_dict, build_entity_from_params
11
11
  from digitalhub.utils.exceptions import EntityAlreadyExistsError, EntityError
12
12
 
@@ -141,7 +141,7 @@ class ExecutableEntity(VersionedEntity):
141
141
 
142
142
  # Override kwargs
143
143
  kwargs["project"] = self.project
144
- kwargs["function"] = self._get_executable_string()
144
+ kwargs[self.ENTITY_TYPE] = self._get_executable_string()
145
145
  kwargs["kind"] = task_kind
146
146
 
147
147
  # Create object instance
@@ -176,7 +176,7 @@ class ExecutableEntity(VersionedEntity):
176
176
  resp = self._get_task_from_backend(kind)
177
177
  if not resp:
178
178
  raise EntityError(f"Task {kind} is not created")
179
- self._tasks[kind] = build_entity_from_dict(resp[0])
179
+ self._tasks[kind] = resp[0]
180
180
  return self._tasks[kind]
181
181
 
182
182
  def update_task(self, kind: str, **kwargs) -> Task:
@@ -203,7 +203,7 @@ class ExecutableEntity(VersionedEntity):
203
203
  # Update kwargs
204
204
  kwargs["project"] = self.project
205
205
  kwargs["kind"] = kind
206
- kwargs["function"] = self._get_executable_string()
206
+ kwargs[self.ENTITY_TYPE] = self._get_executable_string()
207
207
  kwargs["uuid"] = self._tasks[kind].id
208
208
 
209
209
  # Update task
@@ -246,8 +246,8 @@ class ExecutableEntity(VersionedEntity):
246
246
  list
247
247
  Response from backend.
248
248
  """
249
- params = {"function": self._get_executable_string(), "kind": kind}
250
- return list_entity_api_ctx(self.project, EntityTypes.TASK.value, params=params)
249
+ params = {self.ENTITY_TYPE: self._get_executable_string(), "kind": kind}
250
+ return processor.list_context_entities(self.project, EntityTypes.TASK.value, params=params)
251
251
 
252
252
  def _check_task_in_backend(self, kind: str) -> bool:
253
253
  """
@@ -370,7 +370,7 @@ class ExecutableEntity(VersionedEntity):
370
370
  """
371
371
  if kwargs is None:
372
372
  kwargs = {}
373
- kwargs["params"] = {"function": self._get_executable_string()}
373
+ kwargs["params"] = {self.ENTITY_TYPE: self._get_executable_string()}
374
374
  return list_runs(self.project, **kwargs)
375
375
 
376
376
  def delete_run(
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import typing
4
4
  from pathlib import Path
5
5
 
6
- from digitalhub.entities._base.api_utils import files_info_get_api, files_info_put_api
7
6
  from digitalhub.entities._base.versioned.entity import VersionedEntity
7
+ from digitalhub.entities._operations.processor import processor
8
8
  from digitalhub.stores.api import get_store
9
9
 
10
10
  if typing.TYPE_CHECKING:
@@ -48,23 +48,20 @@ class MaterialEntity(VersionedEntity):
48
48
  MaterialEntity
49
49
  Entity saved.
50
50
  """
51
- obj = self.to_dict()
52
-
51
+ # Evaluate files info list length
53
52
  files = None
54
53
  if self.status.files is not None and len(self.status.files) > 5 and not self._context().local:
55
- files = obj["status"].pop("files")
54
+ files = self.status.files
55
+ self.status.files = []
56
56
 
57
- if not update:
58
- new_obj: MaterialEntity = self._save(obj)
59
- else:
60
- new_obj: MaterialEntity = self._update(obj)
57
+ obj: MaterialEntity = super().save(update)
61
58
 
62
59
  # Handle files info
63
60
  if files is not None:
64
- files_info_put_api(self.project, self.ENTITY_TYPE, self.id, files)
65
- self.status.add_files_info(files)
61
+ processor.update_files_info(self.project, self.ENTITY_TYPE, self.id, files)
62
+ self.add_files_info(files)
66
63
 
67
- return new_obj
64
+ return obj
68
65
 
69
66
  ##############################
70
67
  # I/O Methods
@@ -81,7 +78,7 @@ class MaterialEntity(VersionedEntity):
81
78
  List of file paths.
82
79
  """
83
80
  store = get_store(self.spec.path)
84
- paths = self.status.get_file_paths()
81
+ paths = self.get_file_paths()
85
82
  dst = store._build_temp()
86
83
  return store.download(self.spec.path, dst=dst, src=paths)
87
84
 
@@ -130,7 +127,7 @@ class MaterialEntity(VersionedEntity):
130
127
  dataitem/data.csv
131
128
  """
132
129
  store = get_store(self.spec.path)
133
- paths = self.status.get_file_paths()
130
+ paths = self.get_file_paths()
134
131
 
135
132
  if destination is None:
136
133
  dst = self._context().root / self.ENTITY_TYPE
@@ -171,9 +168,44 @@ class MaterialEntity(VersionedEntity):
171
168
  paths = store.upload(source, self.spec.path)
172
169
 
173
170
  # Update files info
174
- files_info = store.get_file_info(paths)
171
+ files_info = store.get_file_info(self.spec.path, paths)
175
172
  self._update_files_info(files_info)
176
173
 
174
+ ##############################
175
+ # Public Helpers
176
+ ##############################
177
+
178
+ def add_files_info(self, files: list[dict]) -> None:
179
+ """
180
+ Add a file to the status.
181
+
182
+ Parameters
183
+ ----------
184
+ files : list[dict]
185
+ Files to add.
186
+
187
+ Returns
188
+ -------
189
+ None
190
+ """
191
+ path_list = self.get_file_paths()
192
+ for f in files:
193
+ if f.get("path") not in path_list:
194
+ self.status.files.append(f)
195
+
196
+ def get_file_paths(self) -> list[str]:
197
+ """
198
+ Get the paths of the files in the status.
199
+
200
+ Returns
201
+ -------
202
+ list[str]
203
+ Paths of the files in the status.
204
+ """
205
+ if self.status.files is None:
206
+ self.status.files = []
207
+ return [f.get("path") for f in self.status.files]
208
+
177
209
  ##############################
178
210
  # Private Helpers
179
211
  ##############################
@@ -194,7 +226,7 @@ class MaterialEntity(VersionedEntity):
194
226
  if files_info is None:
195
227
  return
196
228
  self.refresh()
197
- self.status.add_files_info(files_info)
229
+ self.add_files_info(files_info)
198
230
  self.save(update=True)
199
231
 
200
232
  def _get_files_info(self) -> None:
@@ -206,9 +238,9 @@ class MaterialEntity(VersionedEntity):
206
238
  None
207
239
  """
208
240
  if not self._context().local and not self.status.files:
209
- files = files_info_get_api(
241
+ files = processor.read_files_info(
210
242
  project=self.project,
211
243
  entity_type=self.ENTITY_TYPE,
212
244
  entity_id=self.id,
213
245
  )
214
- self.status.add_files_info(files)
246
+ self.add_files_info(files)
@@ -15,35 +15,4 @@ class MaterialStatus(Status):
15
15
  files: list[dict] | None = None,
16
16
  ) -> None:
17
17
  super().__init__(state, message)
18
- if files is None:
19
- files = []
20
18
  self.files = files
21
-
22
- def add_files_info(self, files: list[dict]) -> None:
23
- """
24
- Add a file to the status.
25
-
26
- Parameters
27
- ----------
28
- files : list[dict]
29
- Files to add.
30
-
31
- Returns
32
- -------
33
- None
34
- """
35
- path_list = self.get_file_paths()
36
- for f in files:
37
- if f.get("path") not in path_list:
38
- self.files.append(f)
39
-
40
- def get_file_paths(self) -> list[str]:
41
- """
42
- Get the paths of the files in the status.
43
-
44
- Returns
45
- -------
46
- list[str]
47
- Paths of the files in the status.
48
- """
49
- return [f.get("path") for f in self.files]
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from digitalhub.utils.file_utils import eval_zip_type
6
+ from digitalhub.utils.s3_utils import get_s3_bucket
7
+ from digitalhub.utils.uri_utils import S3Schemes, has_local_scheme
8
+
9
+
10
+ def eval_local_source(source: str | list[str]) -> None:
11
+ """
12
+ Evaluate if source is local.
13
+
14
+ Parameters
15
+ ----------
16
+ source : str | list[str]
17
+ Source(s).
18
+
19
+ Returns
20
+ -------
21
+ None
22
+ """
23
+ if isinstance(source, list):
24
+ if not source:
25
+ raise ValueError("Empty list of sources.")
26
+ source_is_local = all(has_local_scheme(s) for s in source)
27
+ for s in source:
28
+ if Path(s).is_dir():
29
+ raise ValueError(f"Invalid source path: {s}. List of paths must be list of files, not directories.")
30
+ else:
31
+ source_is_local = has_local_scheme(source)
32
+
33
+ if not source_is_local:
34
+ raise ValueError("Invalid source path. Source must be a local path.")
35
+
36
+
37
+ def eval_zip_sources(source: str | list[str]) -> str:
38
+ """
39
+ Evaluate zip sources.
40
+
41
+ Parameters
42
+ ----------
43
+ source : str | list[str]
44
+ Source(s).
45
+
46
+ Returns
47
+ -------
48
+ str
49
+ S3Schemes.
50
+ """
51
+ if isinstance(source, list):
52
+ if len(source) > 1:
53
+ return S3Schemes.S3.value
54
+ path = source[0]
55
+ else:
56
+ if Path(source).is_dir():
57
+ return S3Schemes.S3.value
58
+ path = source
59
+
60
+ if not eval_zip_type(path):
61
+ return S3Schemes.S3.value
62
+ return S3Schemes.ZIP_S3.value
63
+
64
+
65
+ def build_log_path_from_source(
66
+ project: str,
67
+ entity_type: str,
68
+ name: str,
69
+ uuid: str,
70
+ source: str | list[str],
71
+ ) -> str:
72
+ """
73
+ Build log path.
74
+
75
+ Parameters
76
+ ----------
77
+ project : str
78
+ Project name.
79
+ entity_type : str
80
+ Entity type.
81
+ name : str
82
+ Object name.
83
+ uuid : str
84
+ Object UUID.
85
+ source : str | list[str]
86
+ Source(s).
87
+
88
+ Returns
89
+ -------
90
+ str
91
+ Log path.
92
+ """
93
+ scheme = eval_zip_sources(source)
94
+ path = f"{scheme}://{get_s3_bucket()}/{project}/{entity_type}/{name}/{uuid}"
95
+
96
+ if isinstance(source, list) and len(source) >= 1:
97
+ if len(source) > 1:
98
+ path += "/"
99
+ else:
100
+ path += f"/{Path(source[0]).name}"
101
+ elif Path(source).is_dir():
102
+ path += "/"
103
+ elif Path(source).is_file():
104
+ path += f"/{Path(source).name}"
105
+
106
+ return path