digitalhub 0.8.0b0__py3-none-any.whl → 0.8.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.
- digitalhub/__init__.py +62 -94
- digitalhub/client/__init__.py +0 -0
- digitalhub/client/builder.py +105 -0
- digitalhub/client/objects/__init__.py +0 -0
- digitalhub/client/objects/base.py +56 -0
- digitalhub/client/objects/dhcore.py +681 -0
- digitalhub/client/objects/local.py +533 -0
- digitalhub/context/__init__.py +0 -0
- digitalhub/context/builder.py +178 -0
- digitalhub/context/context.py +136 -0
- digitalhub/datastores/__init__.py +0 -0
- digitalhub/datastores/builder.py +134 -0
- digitalhub/datastores/objects/__init__.py +0 -0
- digitalhub/datastores/objects/base.py +85 -0
- digitalhub/datastores/objects/local.py +42 -0
- digitalhub/datastores/objects/remote.py +23 -0
- digitalhub/datastores/objects/s3.py +38 -0
- digitalhub/datastores/objects/sql.py +60 -0
- digitalhub/entities/__init__.py +0 -0
- digitalhub/entities/_base/__init__.py +0 -0
- digitalhub/entities/_base/api.py +346 -0
- digitalhub/entities/_base/base.py +82 -0
- digitalhub/entities/_base/crud.py +610 -0
- digitalhub/entities/_base/entity/__init__.py +0 -0
- digitalhub/entities/_base/entity/base.py +132 -0
- digitalhub/entities/_base/entity/context.py +118 -0
- digitalhub/entities/_base/entity/executable.py +380 -0
- digitalhub/entities/_base/entity/material.py +214 -0
- digitalhub/entities/_base/entity/unversioned.py +87 -0
- digitalhub/entities/_base/entity/versioned.py +94 -0
- digitalhub/entities/_base/metadata.py +59 -0
- digitalhub/entities/_base/spec/__init__.py +0 -0
- digitalhub/entities/_base/spec/base.py +58 -0
- digitalhub/entities/_base/spec/material.py +22 -0
- digitalhub/entities/_base/state.py +31 -0
- digitalhub/entities/_base/status/__init__.py +0 -0
- digitalhub/entities/_base/status/base.py +32 -0
- digitalhub/entities/_base/status/material.py +49 -0
- digitalhub/entities/_builders/__init__.py +0 -0
- digitalhub/entities/_builders/metadata.py +60 -0
- digitalhub/entities/_builders/name.py +31 -0
- digitalhub/entities/_builders/spec.py +43 -0
- digitalhub/entities/_builders/status.py +62 -0
- digitalhub/entities/_builders/uuid.py +33 -0
- digitalhub/entities/artifact/__init__.py +0 -0
- digitalhub/entities/artifact/builder.py +133 -0
- digitalhub/entities/artifact/crud.py +358 -0
- digitalhub/entities/artifact/entity/__init__.py +0 -0
- digitalhub/entities/artifact/entity/_base.py +39 -0
- digitalhub/entities/artifact/entity/artifact.py +9 -0
- digitalhub/entities/artifact/spec.py +39 -0
- digitalhub/entities/artifact/status.py +15 -0
- digitalhub/entities/dataitem/__init__.py +0 -0
- digitalhub/entities/dataitem/builder.py +144 -0
- digitalhub/entities/dataitem/crud.py +395 -0
- digitalhub/entities/dataitem/entity/__init__.py +0 -0
- digitalhub/entities/dataitem/entity/_base.py +75 -0
- digitalhub/entities/dataitem/entity/dataitem.py +9 -0
- digitalhub/entities/dataitem/entity/iceberg.py +7 -0
- digitalhub/entities/dataitem/entity/table.py +125 -0
- digitalhub/entities/dataitem/models.py +62 -0
- digitalhub/entities/dataitem/spec.py +61 -0
- digitalhub/entities/dataitem/status.py +38 -0
- digitalhub/entities/entity_types.py +19 -0
- digitalhub/entities/function/__init__.py +0 -0
- digitalhub/entities/function/builder.py +86 -0
- digitalhub/entities/function/crud.py +305 -0
- digitalhub/entities/function/entity.py +101 -0
- digitalhub/entities/function/models.py +118 -0
- digitalhub/entities/function/spec.py +81 -0
- digitalhub/entities/function/status.py +9 -0
- digitalhub/entities/model/__init__.py +0 -0
- digitalhub/entities/model/builder.py +152 -0
- digitalhub/entities/model/crud.py +358 -0
- digitalhub/entities/model/entity/__init__.py +0 -0
- digitalhub/entities/model/entity/_base.py +34 -0
- digitalhub/entities/model/entity/huggingface.py +9 -0
- digitalhub/entities/model/entity/mlflow.py +90 -0
- digitalhub/entities/model/entity/model.py +9 -0
- digitalhub/entities/model/entity/sklearn.py +9 -0
- digitalhub/entities/model/models.py +26 -0
- digitalhub/entities/model/spec.py +146 -0
- digitalhub/entities/model/status.py +33 -0
- digitalhub/entities/project/__init__.py +0 -0
- digitalhub/entities/project/builder.py +82 -0
- digitalhub/entities/project/crud.py +350 -0
- digitalhub/entities/project/entity.py +2060 -0
- digitalhub/entities/project/spec.py +50 -0
- digitalhub/entities/project/status.py +9 -0
- digitalhub/entities/registries.py +48 -0
- digitalhub/entities/run/__init__.py +0 -0
- digitalhub/entities/run/builder.py +77 -0
- digitalhub/entities/run/crud.py +232 -0
- digitalhub/entities/run/entity.py +461 -0
- digitalhub/entities/run/spec.py +153 -0
- digitalhub/entities/run/status.py +114 -0
- digitalhub/entities/secret/__init__.py +0 -0
- digitalhub/entities/secret/builder.py +93 -0
- digitalhub/entities/secret/crud.py +294 -0
- digitalhub/entities/secret/entity.py +73 -0
- digitalhub/entities/secret/spec.py +35 -0
- digitalhub/entities/secret/status.py +9 -0
- digitalhub/entities/task/__init__.py +0 -0
- digitalhub/entities/task/builder.py +74 -0
- digitalhub/entities/task/crud.py +241 -0
- digitalhub/entities/task/entity.py +135 -0
- digitalhub/entities/task/models.py +199 -0
- digitalhub/entities/task/spec.py +51 -0
- digitalhub/entities/task/status.py +9 -0
- digitalhub/entities/utils.py +184 -0
- digitalhub/entities/workflow/__init__.py +0 -0
- digitalhub/entities/workflow/builder.py +91 -0
- digitalhub/entities/workflow/crud.py +304 -0
- digitalhub/entities/workflow/entity.py +77 -0
- digitalhub/entities/workflow/spec.py +15 -0
- digitalhub/entities/workflow/status.py +9 -0
- digitalhub/readers/__init__.py +0 -0
- digitalhub/readers/builder.py +54 -0
- digitalhub/readers/objects/__init__.py +0 -0
- digitalhub/readers/objects/base.py +70 -0
- digitalhub/readers/objects/pandas.py +207 -0
- digitalhub/readers/registry.py +15 -0
- digitalhub/registry/__init__.py +0 -0
- digitalhub/registry/models.py +87 -0
- digitalhub/registry/registry.py +74 -0
- digitalhub/registry/utils.py +150 -0
- digitalhub/runtimes/__init__.py +0 -0
- digitalhub/runtimes/base.py +164 -0
- digitalhub/runtimes/builder.py +53 -0
- digitalhub/runtimes/kind_registry.py +170 -0
- digitalhub/stores/__init__.py +0 -0
- digitalhub/stores/builder.py +257 -0
- digitalhub/stores/objects/__init__.py +0 -0
- digitalhub/stores/objects/base.py +189 -0
- digitalhub/stores/objects/local.py +230 -0
- digitalhub/stores/objects/remote.py +143 -0
- digitalhub/stores/objects/s3.py +563 -0
- digitalhub/stores/objects/sql.py +328 -0
- digitalhub/utils/__init__.py +0 -0
- digitalhub/utils/data_utils.py +127 -0
- digitalhub/utils/env_utils.py +123 -0
- digitalhub/utils/exceptions.py +55 -0
- digitalhub/utils/file_utils.py +204 -0
- digitalhub/utils/generic_utils.py +207 -0
- digitalhub/utils/git_utils.py +148 -0
- digitalhub/utils/io_utils.py +79 -0
- digitalhub/utils/logger.py +17 -0
- digitalhub/utils/uri_utils.py +56 -0
- {digitalhub-0.8.0b0.dist-info → digitalhub-0.8.0b1.dist-info}/METADATA +27 -12
- digitalhub-0.8.0b1.dist-info/RECORD +161 -0
- test/test_crud_artifacts.py +1 -1
- test/test_crud_dataitems.py +1 -1
- test/test_crud_functions.py +1 -1
- test/test_crud_runs.py +1 -1
- test/test_crud_tasks.py +1 -1
- digitalhub-0.8.0b0.dist-info/RECORD +0 -14
- {digitalhub-0.8.0b0.dist-info → digitalhub-0.8.0b1.dist-info}/LICENSE.txt +0 -0
- {digitalhub-0.8.0b0.dist-info → digitalhub-0.8.0b1.dist-info}/WHEEL +0 -0
- {digitalhub-0.8.0b0.dist-info → digitalhub-0.8.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from tempfile import mkdtemp
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from digitalhub.utils.exceptions import StoreError
|
|
11
|
+
from digitalhub.utils.uri_utils import map_uri_scheme
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Store(metaclass=ABCMeta):
|
|
15
|
+
"""
|
|
16
|
+
Store abstract class.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, name: str, store_type: str) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Constructor.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
name : str
|
|
26
|
+
Store name.
|
|
27
|
+
store_type : str
|
|
28
|
+
Store type. Used to choose the right store implementation.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
None
|
|
33
|
+
"""
|
|
34
|
+
self.name = name
|
|
35
|
+
self.type = store_type
|
|
36
|
+
|
|
37
|
+
##############################
|
|
38
|
+
# IO methods
|
|
39
|
+
##############################
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def download(
|
|
43
|
+
self,
|
|
44
|
+
root: str,
|
|
45
|
+
dst: Path,
|
|
46
|
+
src: list[str],
|
|
47
|
+
overwrite: bool = False,
|
|
48
|
+
) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Method to download artifact from storage.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def upload(self, src: str | list[str], dst: str | None = None) -> list[tuple[str, str]]:
|
|
55
|
+
"""
|
|
56
|
+
Method to upload artifact to storage.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def get_file_info(self, paths: list[str]) -> list[dict]:
|
|
61
|
+
"""
|
|
62
|
+
Method to get file metadata.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
##############################
|
|
66
|
+
# Helpers methods
|
|
67
|
+
##############################
|
|
68
|
+
|
|
69
|
+
def _check_local_src(self, src: str) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Check if the source path is local.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
src : str
|
|
76
|
+
The source path.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
None
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
StoreError
|
|
85
|
+
If the source is not a local path.
|
|
86
|
+
"""
|
|
87
|
+
if map_uri_scheme(src) != "local":
|
|
88
|
+
raise StoreError(f"Source '{src}' is not a local path.")
|
|
89
|
+
|
|
90
|
+
def _check_local_dst(self, dst: str) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Check if the destination path is local.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
dst : str
|
|
97
|
+
The destination path.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
None
|
|
102
|
+
|
|
103
|
+
Raises
|
|
104
|
+
------
|
|
105
|
+
StoreError
|
|
106
|
+
If the destination is not a local path.
|
|
107
|
+
"""
|
|
108
|
+
if map_uri_scheme(dst) != "local":
|
|
109
|
+
raise StoreError(f"Destination '{dst}' is not a local path.")
|
|
110
|
+
|
|
111
|
+
def _check_overwrite(self, dst: Path, overwrite: bool) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Check if destination path exists for overwrite.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
dst : Path
|
|
118
|
+
Destination path as filename.
|
|
119
|
+
overwrite : bool
|
|
120
|
+
Specify if overwrite an existing file.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
None
|
|
125
|
+
|
|
126
|
+
Raises
|
|
127
|
+
------
|
|
128
|
+
StoreError
|
|
129
|
+
If destination path exists and overwrite is False.
|
|
130
|
+
"""
|
|
131
|
+
if dst.exists() and not overwrite:
|
|
132
|
+
raise StoreError(f"Destination {str(dst)} already exists.")
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _build_path(path: str | Path) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Get path from store path and path.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
path : str
|
|
142
|
+
The path to build.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
None
|
|
147
|
+
"""
|
|
148
|
+
if not isinstance(path, Path):
|
|
149
|
+
path = Path(path)
|
|
150
|
+
if path.suffix != "":
|
|
151
|
+
path = path.parent
|
|
152
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _build_temp() -> Path:
|
|
156
|
+
"""
|
|
157
|
+
Build a temporary path.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
Path
|
|
162
|
+
Temporary path.
|
|
163
|
+
"""
|
|
164
|
+
tmpdir = mkdtemp()
|
|
165
|
+
return Path(tmpdir)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class StoreConfig(BaseModel):
|
|
169
|
+
"""
|
|
170
|
+
Store configuration base class.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class StoreParameters(BaseModel):
|
|
175
|
+
"""
|
|
176
|
+
Store configuration class.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
name: str
|
|
180
|
+
"""Store id."""
|
|
181
|
+
|
|
182
|
+
type: Literal["local", "s3", "remote", "sql"]
|
|
183
|
+
"""Store type to instantiate."""
|
|
184
|
+
|
|
185
|
+
config: StoreConfig = None
|
|
186
|
+
"""Configuration for the store."""
|
|
187
|
+
|
|
188
|
+
is_default: bool = False
|
|
189
|
+
"""Flag to determine if the store is the default one."""
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from digitalhub.stores.objects.base import Store, StoreConfig
|
|
7
|
+
from digitalhub.utils.exceptions import StoreError
|
|
8
|
+
from digitalhub.utils.file_utils import get_file_info_from_local
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LocalStoreConfig(StoreConfig):
|
|
12
|
+
"""
|
|
13
|
+
Local store configuration class.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
path: str
|
|
17
|
+
"""Local path."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LocalStore(Store):
|
|
21
|
+
"""
|
|
22
|
+
Local store class. It implements the Store interface and provides methods to fetch and persist
|
|
23
|
+
artifacts on local filesystem based storage.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, name: str, store_type: str, config: LocalStoreConfig) -> None:
|
|
27
|
+
super().__init__(name, store_type)
|
|
28
|
+
self.config = config
|
|
29
|
+
|
|
30
|
+
##############################
|
|
31
|
+
# IO methods
|
|
32
|
+
##############################
|
|
33
|
+
|
|
34
|
+
def download(
|
|
35
|
+
self,
|
|
36
|
+
root: str,
|
|
37
|
+
dst: Path,
|
|
38
|
+
src: list[str],
|
|
39
|
+
overwrite: bool = False,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Download artifacts from storage.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
root : str
|
|
47
|
+
The root path of the artifact.
|
|
48
|
+
dst : str
|
|
49
|
+
The destination of the artifact on local filesystem.
|
|
50
|
+
src : list[str]
|
|
51
|
+
List of sources.
|
|
52
|
+
overwrite : bool
|
|
53
|
+
Specify if overwrite existing file(s).
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
str
|
|
58
|
+
Destination path of the downloaded artifact.
|
|
59
|
+
"""
|
|
60
|
+
raise StoreError("Local store does not support download.")
|
|
61
|
+
|
|
62
|
+
def upload(self, src: str | list[str], dst: str | None = None) -> list[tuple[str, str]]:
|
|
63
|
+
"""
|
|
64
|
+
Upload an artifact to storage.
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
StoreError
|
|
69
|
+
This method is not implemented.
|
|
70
|
+
"""
|
|
71
|
+
raise StoreError("Local store does not support upload.")
|
|
72
|
+
|
|
73
|
+
def get_file_info(self, paths: list[str]) -> list[dict]:
|
|
74
|
+
"""
|
|
75
|
+
Method to get file metadata.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
paths : list
|
|
80
|
+
List of source paths.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
list[dict]
|
|
85
|
+
Returns files metadata.
|
|
86
|
+
"""
|
|
87
|
+
return [get_file_info_from_local(p) for p in paths]
|
|
88
|
+
|
|
89
|
+
##############################
|
|
90
|
+
# Private I/O methods
|
|
91
|
+
##############################
|
|
92
|
+
|
|
93
|
+
def _get_src_dst_files(self, src: Path, dst: Path) -> list[str]:
|
|
94
|
+
"""
|
|
95
|
+
Copy files from source to destination.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
src : Path
|
|
100
|
+
The source path.
|
|
101
|
+
dst : Path
|
|
102
|
+
The destination path.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
list[str]
|
|
107
|
+
Returns the list of destination and source paths of the
|
|
108
|
+
copied files.
|
|
109
|
+
"""
|
|
110
|
+
return [self._get_src_dst_file(i, dst) for i in src.rglob("*") if i.is_file()]
|
|
111
|
+
|
|
112
|
+
def _get_src_dst_file(self, src: Path, dst: Path) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Copy file from source to destination.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
src : Path
|
|
119
|
+
The source path.
|
|
120
|
+
dst : Path
|
|
121
|
+
The destination path.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
str
|
|
126
|
+
"""
|
|
127
|
+
dst_pth = self._copy_file(src, dst, True)
|
|
128
|
+
return str(dst_pth), str(src)
|
|
129
|
+
|
|
130
|
+
def _copy_dir(self, src: Path, dst: Path, overwrite: bool) -> list[str]:
|
|
131
|
+
"""
|
|
132
|
+
Download file from source to destination.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
src : Path
|
|
137
|
+
The source path.
|
|
138
|
+
dst : Path
|
|
139
|
+
The destination path.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
list[str]
|
|
144
|
+
"""
|
|
145
|
+
dst = self._rebuild_path(dst, src)
|
|
146
|
+
shutil.copytree(src, dst, dirs_exist_ok=overwrite)
|
|
147
|
+
return [str(i) for i in dst.rglob("*") if i.is_file()]
|
|
148
|
+
|
|
149
|
+
def _copy_file(self, src: Path, dst: Path, overwrite: bool) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Copy file from source to destination.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
src : Path
|
|
156
|
+
The source path.
|
|
157
|
+
dst : Path
|
|
158
|
+
The destination path.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
str
|
|
163
|
+
"""
|
|
164
|
+
dst = self._rebuild_path(dst, src)
|
|
165
|
+
self._check_overwrite(dst, overwrite)
|
|
166
|
+
return str(shutil.copy2(src, dst))
|
|
167
|
+
|
|
168
|
+
def _rebuild_path(self, dst: Path, src: Path) -> Path:
|
|
169
|
+
"""
|
|
170
|
+
Rebuild path.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
dst : Path
|
|
175
|
+
The destination path.
|
|
176
|
+
src : Path
|
|
177
|
+
The source path.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
Path
|
|
182
|
+
The rebuilt path.
|
|
183
|
+
"""
|
|
184
|
+
if dst.is_dir():
|
|
185
|
+
if src.is_absolute():
|
|
186
|
+
raise StoreError("Source must be a relative path if the destination is a directory.")
|
|
187
|
+
dst = dst / src
|
|
188
|
+
self._build_path(dst)
|
|
189
|
+
return dst
|
|
190
|
+
|
|
191
|
+
##############################
|
|
192
|
+
# Static methods
|
|
193
|
+
##############################
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def is_partition_or_dir(path: str) -> bool:
|
|
197
|
+
"""
|
|
198
|
+
Check if path is a directory or a partition.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
path : str
|
|
203
|
+
The path to check.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
bool
|
|
208
|
+
"""
|
|
209
|
+
return Path(path).is_dir()
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def build_object_path(root: str, paths: str | list[str]) -> list[str]:
|
|
213
|
+
"""
|
|
214
|
+
Method to build object path.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
root : str
|
|
219
|
+
The root of the object path.
|
|
220
|
+
paths : str | list[str]
|
|
221
|
+
The path to build.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
list[str]
|
|
226
|
+
Returns the path of the object.
|
|
227
|
+
"""
|
|
228
|
+
if isinstance(paths, str):
|
|
229
|
+
paths = [paths]
|
|
230
|
+
return [str(Path(root) / path) for path in paths]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from digitalhub.stores.objects.base import Store, StoreConfig
|
|
8
|
+
from digitalhub.utils.exceptions import StoreError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RemoteStoreConfig(StoreConfig):
|
|
12
|
+
"""
|
|
13
|
+
Remote store configuration class.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RemoteStore(Store):
|
|
18
|
+
"""
|
|
19
|
+
HTTP store class. It implements the Store interface and provides methods to fetch
|
|
20
|
+
artifacts from remote HTTP based storage.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, name: str, store_type: str, config: RemoteStoreConfig) -> None:
|
|
24
|
+
super().__init__(name, store_type)
|
|
25
|
+
self.config = config
|
|
26
|
+
|
|
27
|
+
##############################
|
|
28
|
+
# IO methods
|
|
29
|
+
##############################
|
|
30
|
+
|
|
31
|
+
def download(
|
|
32
|
+
self,
|
|
33
|
+
root: str,
|
|
34
|
+
dst: Path,
|
|
35
|
+
src: list[str],
|
|
36
|
+
overwrite: bool = False,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Download artifacts from storage.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
root : str
|
|
44
|
+
The root path of the artifact.
|
|
45
|
+
dst : str
|
|
46
|
+
The destination of the artifact on local filesystem.
|
|
47
|
+
src : list[str]
|
|
48
|
+
List of sources.
|
|
49
|
+
overwrite : bool
|
|
50
|
+
Specify if overwrite existing file(s).
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
str
|
|
55
|
+
Destination path of the downloaded artifact.
|
|
56
|
+
"""
|
|
57
|
+
# Handle destination
|
|
58
|
+
if dst is None:
|
|
59
|
+
dst = self._build_temp()
|
|
60
|
+
else:
|
|
61
|
+
self._check_local_dst(str(dst))
|
|
62
|
+
|
|
63
|
+
if dst.suffix == "":
|
|
64
|
+
dst = dst / "data.file"
|
|
65
|
+
|
|
66
|
+
self._check_overwrite(dst, overwrite)
|
|
67
|
+
self._build_path(dst)
|
|
68
|
+
|
|
69
|
+
return self._download_file(root, dst, overwrite)
|
|
70
|
+
|
|
71
|
+
def upload(self, src: str | list[str], dst: str | None = None) -> list[tuple[str, str]]:
|
|
72
|
+
"""
|
|
73
|
+
Upload an artifact to storage.
|
|
74
|
+
|
|
75
|
+
Raises
|
|
76
|
+
------
|
|
77
|
+
StoreError
|
|
78
|
+
This method is not implemented.
|
|
79
|
+
"""
|
|
80
|
+
raise StoreError("Remote HTTP store does not support upload.")
|
|
81
|
+
|
|
82
|
+
def get_file_info(self, paths: list[str]) -> list[dict]:
|
|
83
|
+
"""
|
|
84
|
+
Get file information from HTTP(s) storage.
|
|
85
|
+
|
|
86
|
+
Raises
|
|
87
|
+
------
|
|
88
|
+
NotImplementedError
|
|
89
|
+
This method is not implemented.
|
|
90
|
+
"""
|
|
91
|
+
raise NotImplementedError("Remote store does not support get_file_info.")
|
|
92
|
+
|
|
93
|
+
##############################
|
|
94
|
+
# Private helper methods
|
|
95
|
+
##############################
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _check_head(src) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Check if the source exists.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
src : str
|
|
105
|
+
The source location.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
None
|
|
110
|
+
|
|
111
|
+
Raises
|
|
112
|
+
------
|
|
113
|
+
HTTPError
|
|
114
|
+
If an error occurs while checking the source.
|
|
115
|
+
"""
|
|
116
|
+
r = requests.head(src, timeout=60)
|
|
117
|
+
r.raise_for_status()
|
|
118
|
+
|
|
119
|
+
def _download_file(self, url: str, dst: Path, overwrite: bool) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Method to download a file from a given url.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
url : str
|
|
126
|
+
The url of the file to download.
|
|
127
|
+
dst : Path
|
|
128
|
+
The destination of the file.
|
|
129
|
+
overwrite : bool
|
|
130
|
+
Whether to overwrite existing files.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
str
|
|
135
|
+
The path of the downloaded file.
|
|
136
|
+
"""
|
|
137
|
+
self._check_head(url)
|
|
138
|
+
with requests.get(url, stream=True, timeout=60) as r:
|
|
139
|
+
r.raise_for_status()
|
|
140
|
+
with open(dst, "wb") as f:
|
|
141
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
142
|
+
f.write(chunk)
|
|
143
|
+
return str(dst)
|