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,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
from mimetypes import guess_type
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileInfo(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
File info class.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
path: str = None
|
|
17
|
+
name: str = None
|
|
18
|
+
content_type: str = None
|
|
19
|
+
size: int = None
|
|
20
|
+
hash: str = None
|
|
21
|
+
last_modified: str = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def calculate_blob_hash(data_path: str) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Calculate the hash of a file.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
data_path : str
|
|
31
|
+
Path to the file.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
str
|
|
36
|
+
The hash of the file.
|
|
37
|
+
"""
|
|
38
|
+
with open(data_path, "rb") as f:
|
|
39
|
+
data = f.read()
|
|
40
|
+
return f"sha256:{sha256(data).hexdigest()}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_file_size(data_path: str) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Get the size of a file.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
data_path : str
|
|
50
|
+
Path to the file.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
int
|
|
55
|
+
The size of the file.
|
|
56
|
+
"""
|
|
57
|
+
return Path(data_path).stat().st_size
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_file_mime_type(data_path: str) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Get the mime type of a file.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
data_path : str
|
|
67
|
+
Path to the file.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
str
|
|
72
|
+
The mime type of the file.
|
|
73
|
+
"""
|
|
74
|
+
return guess_type(data_path)[0]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_path_name(data_path: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Get the name of a file.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
data_path : str
|
|
84
|
+
Path to the file.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
str
|
|
89
|
+
The name of the file.
|
|
90
|
+
"""
|
|
91
|
+
return Path(data_path).name
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_last_modified(data_path: str) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Get the last modified date of a file.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
data_path : str
|
|
101
|
+
Path to the file.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
str
|
|
106
|
+
The last modified date of the file.
|
|
107
|
+
"""
|
|
108
|
+
path = Path(data_path)
|
|
109
|
+
timestamp = path.stat().st_mtime
|
|
110
|
+
return datetime.fromtimestamp(timestamp).astimezone().isoformat()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_s3_path(src_path: str) -> str:
|
|
114
|
+
"""
|
|
115
|
+
Get the S3 path of a file.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
src_path : str
|
|
120
|
+
Path to the file.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
str
|
|
125
|
+
The S3 path of the file.
|
|
126
|
+
"""
|
|
127
|
+
return Path(src_path).as_uri()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_file_info_from_local(path: str, src_path: str) -> None | dict:
|
|
131
|
+
"""
|
|
132
|
+
Get file info from path.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
path : str
|
|
137
|
+
Target path of the object.
|
|
138
|
+
src_path : str
|
|
139
|
+
Local path of some source.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
dict
|
|
144
|
+
File info.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
name = get_path_name(path)
|
|
148
|
+
content_type = get_file_mime_type(path)
|
|
149
|
+
size = get_file_size(path)
|
|
150
|
+
hash = calculate_blob_hash(path)
|
|
151
|
+
last_modified = get_last_modified(path)
|
|
152
|
+
|
|
153
|
+
return FileInfo(
|
|
154
|
+
path=src_path,
|
|
155
|
+
name=name,
|
|
156
|
+
content_type=content_type,
|
|
157
|
+
size=size,
|
|
158
|
+
hash=hash,
|
|
159
|
+
last_modified=last_modified,
|
|
160
|
+
).dict()
|
|
161
|
+
except Exception:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_file_info_from_s3(path: str, metadata: dict) -> None | dict:
|
|
166
|
+
"""
|
|
167
|
+
Get file info from path.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
path : str
|
|
172
|
+
Object source path.
|
|
173
|
+
metadata : dict
|
|
174
|
+
Metadata of the object from S3.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
dict
|
|
179
|
+
File info.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
size = metadata["ContentLength"]
|
|
183
|
+
file_hash = metadata["ETag"][1:-1]
|
|
184
|
+
|
|
185
|
+
file_size_limit_multipart = 20 * 1024 * 1024
|
|
186
|
+
if size < file_size_limit_multipart:
|
|
187
|
+
file_hash = "md5:" + file_hash
|
|
188
|
+
else:
|
|
189
|
+
file_hash = "LiteralETag:" + file_hash
|
|
190
|
+
|
|
191
|
+
name = get_path_name(path)
|
|
192
|
+
content_type = metadata["ContentType"]
|
|
193
|
+
last_modified = metadata["LastModified"].isoformat()
|
|
194
|
+
|
|
195
|
+
return FileInfo(
|
|
196
|
+
path=path,
|
|
197
|
+
name=name,
|
|
198
|
+
content_type=content_type,
|
|
199
|
+
size=size,
|
|
200
|
+
hash=file_hash,
|
|
201
|
+
last_modified=last_modified,
|
|
202
|
+
).dict()
|
|
203
|
+
except Exception:
|
|
204
|
+
return None
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
from zipfile import ZipFile
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from boto3 import client as boto3_client
|
|
14
|
+
from requests import get as requests_get
|
|
15
|
+
|
|
16
|
+
from digitalhub.utils.io_utils import read_text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_timestamp() -> str:
|
|
20
|
+
"""
|
|
21
|
+
Get the current timestamp timezoned.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
str
|
|
26
|
+
The current timestamp.
|
|
27
|
+
"""
|
|
28
|
+
return datetime.now().astimezone().isoformat()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def decode_string(string: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Decode a string from base64.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
string : str
|
|
38
|
+
The string to decode.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
str
|
|
43
|
+
The string decoded from base64.
|
|
44
|
+
"""
|
|
45
|
+
return base64.b64decode(string).decode()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def encode_string(string: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Encode a string in base64.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
string : str
|
|
55
|
+
The string to encode.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
str
|
|
60
|
+
The string encoded in base64.
|
|
61
|
+
"""
|
|
62
|
+
return base64.b64encode(string.encode()).decode()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def encode_source(path: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Read a file and encode in base64 the content.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
path : str
|
|
72
|
+
The file path to read.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
The file content encoded in base64.
|
|
78
|
+
"""
|
|
79
|
+
return encode_string(read_text(path))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def requests_chunk_download(source: str, filename: Path) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Download a file in chunks.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
source : str
|
|
89
|
+
URL to download the file.
|
|
90
|
+
filename : Path
|
|
91
|
+
Path where to save the file.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
None
|
|
96
|
+
"""
|
|
97
|
+
with requests_get(source, stream=True) as r:
|
|
98
|
+
r.raise_for_status()
|
|
99
|
+
with filename.open("wb") as f:
|
|
100
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
101
|
+
f.write(chunk)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def extract_archive(path: Path, filename: Path) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Extract a zip archive.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
path : Path
|
|
111
|
+
Path where to extract the archive.
|
|
112
|
+
filename : Path
|
|
113
|
+
Path to the archive.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
None
|
|
118
|
+
"""
|
|
119
|
+
with ZipFile(filename, "r") as zip_file:
|
|
120
|
+
zip_file.extractall(path)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_s3_source(bucket: str, key: str, filename: Path) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Get S3 source.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
bucket : str
|
|
130
|
+
S3 bucket name.
|
|
131
|
+
key : str
|
|
132
|
+
S3 object key.
|
|
133
|
+
filename : Path
|
|
134
|
+
Path where to save the function source.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
None
|
|
139
|
+
"""
|
|
140
|
+
s3 = boto3_client("s3", endpoint_url=os.getenv("S3_ENDPOINT_URL"))
|
|
141
|
+
s3.download_file(bucket, key, filename)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_bucket_and_key(path: str) -> tuple[str, str]:
|
|
145
|
+
"""
|
|
146
|
+
Get bucket and key from path.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
path : str
|
|
151
|
+
The source path to get the key from.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
tuple[str, str]
|
|
156
|
+
The bucket and key.
|
|
157
|
+
"""
|
|
158
|
+
parsed = urlparse(path)
|
|
159
|
+
return parsed.netloc, parsed.path
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MyEncoder(json.JSONEncoder):
|
|
163
|
+
"""
|
|
164
|
+
Custom JSON encoder to handle numpy types.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def default(self, obj: Any) -> Any:
|
|
168
|
+
"""
|
|
169
|
+
Convert numpy types to json.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
obj : Any
|
|
174
|
+
The object to convert.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
Any
|
|
179
|
+
The object converted to json.
|
|
180
|
+
"""
|
|
181
|
+
if isinstance(obj, (int, str, float, list, dict)):
|
|
182
|
+
return obj
|
|
183
|
+
elif isinstance(obj, (np.integer, np.int64)):
|
|
184
|
+
return int(obj)
|
|
185
|
+
elif isinstance(obj, (np.floating, np.float64)):
|
|
186
|
+
return float(obj)
|
|
187
|
+
elif isinstance(obj, np.ndarray):
|
|
188
|
+
return obj.tolist()
|
|
189
|
+
else:
|
|
190
|
+
return str(obj)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def dict_to_json(struct: dict) -> str:
|
|
194
|
+
"""
|
|
195
|
+
Convert a dict to json.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
struct : dict
|
|
200
|
+
The dict to convert.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
str
|
|
205
|
+
The json string.
|
|
206
|
+
"""
|
|
207
|
+
return json.dumps(struct, cls=MyEncoder)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import warnings
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from git import Repo
|
|
11
|
+
except ImportError as e:
|
|
12
|
+
if "Bad git executable." in e.args[0]:
|
|
13
|
+
warnings.warn("git is not installed. Please install git and try again.", RuntimeWarning)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def clone_repository(url: str, path: Path) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Clone git repository.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
url : str
|
|
23
|
+
URL of the repository.
|
|
24
|
+
path : Path
|
|
25
|
+
Path where to save the repository.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
None
|
|
30
|
+
"""
|
|
31
|
+
clean_path(path)
|
|
32
|
+
checkout_object = get_checkout_object(url)
|
|
33
|
+
url = add_credentials_git_remote_url(url)
|
|
34
|
+
repo = clone_from_url(url, path)
|
|
35
|
+
if checkout_object != "":
|
|
36
|
+
repo.git.checkout(checkout_object)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_checkout_object(url: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Get checkout object from url fragment.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
url : str
|
|
46
|
+
URL of the repository.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
str
|
|
51
|
+
Checkout object (branch, tag, commit).
|
|
52
|
+
"""
|
|
53
|
+
return urlparse(url).fragment
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def clean_path(path: Path) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Clean path from any files.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
path : Path
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
None
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_git_username_password_from_token(token: str) -> tuple[str, str]:
|
|
73
|
+
"""
|
|
74
|
+
Parse token to get username and password. The token
|
|
75
|
+
can be one of the following:
|
|
76
|
+
|
|
77
|
+
- GitHub/GitLab personal access token (github_pat_.../glpat...)
|
|
78
|
+
- GitHub/GitLab access token
|
|
79
|
+
- Other generic token
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
token : str
|
|
84
|
+
Token to parse.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
tuple[str, str]
|
|
89
|
+
Username and password.
|
|
90
|
+
"""
|
|
91
|
+
# Mutued from mlrun
|
|
92
|
+
if token.startswith("github_pat_") or token.startswith("glpat"):
|
|
93
|
+
username = "oauth2"
|
|
94
|
+
password = token
|
|
95
|
+
else:
|
|
96
|
+
username = token
|
|
97
|
+
password = "x-oauth-basic"
|
|
98
|
+
return username, password
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_credentials_git_remote_url(url: str) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Add credentials to git remote url.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
url : str
|
|
108
|
+
URL of the repository.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
str
|
|
113
|
+
URL with credentials.
|
|
114
|
+
"""
|
|
115
|
+
url_obj = urlparse(url)
|
|
116
|
+
|
|
117
|
+
# Get credentials from environment variables
|
|
118
|
+
username = os.getenv("GIT_USERNAME")
|
|
119
|
+
password = os.getenv("GIT_PASSWORD")
|
|
120
|
+
token = os.getenv("GIT_TOKEN")
|
|
121
|
+
|
|
122
|
+
# Get credentials from token. Override username and password
|
|
123
|
+
if token is not None:
|
|
124
|
+
username, password = get_git_username_password_from_token(token)
|
|
125
|
+
|
|
126
|
+
# Add credentials to url if needed
|
|
127
|
+
if username is not None and password is not None:
|
|
128
|
+
return f"https://{username}:{password}@{url_obj.hostname}{url_obj.path}"
|
|
129
|
+
return f"https://{url_obj.hostname}{url_obj.path}"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def clone_from_url(url: str, path: Path) -> Repo:
|
|
133
|
+
"""
|
|
134
|
+
Clone repository from url. Wraps git.Repo.clone_from.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
url : str
|
|
139
|
+
HTTP(S) URL of the repository.
|
|
140
|
+
path : Path
|
|
141
|
+
Path where to save the repository.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
Repo
|
|
146
|
+
Cloned repository.
|
|
147
|
+
"""
|
|
148
|
+
return Repo.clone_from(url=url, to_path=path)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
##############################
|
|
8
|
+
# Writers
|
|
9
|
+
##############################
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def write_yaml(filepath: str | Path, obj: dict | list[dict]) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Write a dict or a list of dict to a yaml file.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
filepath : str | Path
|
|
19
|
+
The yaml file path to write.
|
|
20
|
+
obj : dict
|
|
21
|
+
The dict to write.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
None
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(obj, list):
|
|
28
|
+
with open(filepath, "w", encoding="utf-8") as out_file:
|
|
29
|
+
yaml.dump_all(obj, out_file, sort_keys=False, default_flow_style=False)
|
|
30
|
+
else:
|
|
31
|
+
with open(filepath, "w", encoding="utf-8") as out_file:
|
|
32
|
+
yaml.dump(obj, out_file, sort_keys=False)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
##############################
|
|
36
|
+
# Readers
|
|
37
|
+
##############################
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_yaml(filepath: str | Path) -> dict | list[dict]:
|
|
41
|
+
"""
|
|
42
|
+
Read a yaml file and return a dict or a list of dict.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
filepath : str | Path
|
|
47
|
+
The yaml file path to read.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
dict | list[dict]
|
|
52
|
+
The yaml file content.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
with open(filepath, "r", encoding="utf-8") as in_file:
|
|
56
|
+
data = yaml.load(in_file, Loader=yaml.SafeLoader)
|
|
57
|
+
|
|
58
|
+
# If yaml contains multiple documents
|
|
59
|
+
except yaml.composer.ComposerError:
|
|
60
|
+
with open(filepath, "r", encoding="utf-8") as in_file:
|
|
61
|
+
data = list(yaml.load_all(in_file, Loader=yaml.SafeLoader))
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_text(filepath: str | Path) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Read a file and return the text.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
filepath : str | Path
|
|
72
|
+
The file path to read.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
The file content.
|
|
78
|
+
"""
|
|
79
|
+
return Path(filepath).read_text(encoding="utf-8")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
# Create logger
|
|
6
|
+
LOGGER = logging.getLogger("digitalhub-core")
|
|
7
|
+
LOGGER.setLevel(logging.INFO)
|
|
8
|
+
|
|
9
|
+
# Create formatter
|
|
10
|
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
|
11
|
+
|
|
12
|
+
# Create console handler and set formatter
|
|
13
|
+
console_handler = logging.StreamHandler()
|
|
14
|
+
console_handler.setFormatter(formatter)
|
|
15
|
+
|
|
16
|
+
# Set console handler to the logger
|
|
17
|
+
LOGGER.addHandler(console_handler)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def map_uri_scheme(uri: str) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Map an URI scheme to a common scheme.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
uri : str
|
|
13
|
+
URI.
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
str
|
|
18
|
+
Mapped scheme type.
|
|
19
|
+
|
|
20
|
+
Raises
|
|
21
|
+
------
|
|
22
|
+
ValueError
|
|
23
|
+
If the scheme is unknown.
|
|
24
|
+
"""
|
|
25
|
+
scheme = urlparse(uri).scheme
|
|
26
|
+
if scheme in [""]:
|
|
27
|
+
return "local"
|
|
28
|
+
if scheme in ["file", "local"]:
|
|
29
|
+
raise ValueError("For local path, do not use any scheme")
|
|
30
|
+
if scheme in ["http", "https"]:
|
|
31
|
+
return "remote"
|
|
32
|
+
if scheme in ["s3", "s3a", "s3n", "zip+s3"]:
|
|
33
|
+
return "s3"
|
|
34
|
+
if scheme in ["sql", "postgresql"]:
|
|
35
|
+
return "sql"
|
|
36
|
+
if scheme in ["git", "git+http", "git+https"]:
|
|
37
|
+
return "git"
|
|
38
|
+
raise ValueError(f"Unknown scheme '{scheme}'!")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_local_path(path: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if path is local.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
path : str
|
|
48
|
+
Path of some source.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
bool
|
|
53
|
+
True if path is local.
|
|
54
|
+
"""
|
|
55
|
+
scheme = map_uri_scheme(path)
|
|
56
|
+
return scheme == "local"
|