qwak-core 0.4.272__py3-none-any.whl → 0.4.274__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.
- frogml_storage/__init__.py +1 -0
- frogml_storage/artifactory/__init__.py +1 -0
- frogml_storage/artifactory/_artifactory_api.py +315 -0
- frogml_storage/authentication/login/__init__.py +1 -0
- frogml_storage/authentication/login/_login_cli.py +239 -0
- frogml_storage/authentication/login/_login_command.py +74 -0
- frogml_storage/authentication/models/__init__.py +3 -0
- frogml_storage/authentication/models/_auth.py +24 -0
- frogml_storage/authentication/models/_auth_config.py +70 -0
- frogml_storage/authentication/models/_login.py +22 -0
- frogml_storage/authentication/utils/__init__.py +17 -0
- frogml_storage/authentication/utils/_authentication_utils.py +281 -0
- frogml_storage/authentication/utils/_login_checks_utils.py +114 -0
- frogml_storage/base_storage.py +140 -0
- frogml_storage/constants.py +56 -0
- frogml_storage/exceptions/checksum_verification_error.py +3 -0
- frogml_storage/exceptions/validation_error.py +4 -0
- frogml_storage/frog_ml.py +668 -0
- frogml_storage/http/__init__.py +1 -0
- frogml_storage/http/http_client.py +83 -0
- frogml_storage/logging/__init__.py +1 -0
- frogml_storage/logging/_log_config.py +45 -0
- frogml_storage/logging/log_utils.py +21 -0
- frogml_storage/models/__init__.py +1 -0
- frogml_storage/models/_download_context.py +54 -0
- frogml_storage/models/dataset_manifest.py +13 -0
- frogml_storage/models/entity_manifest.py +93 -0
- frogml_storage/models/frogml_dataset_version.py +21 -0
- frogml_storage/models/frogml_entity_type_info.py +50 -0
- frogml_storage/models/frogml_entity_version.py +34 -0
- frogml_storage/models/frogml_model_version.py +21 -0
- frogml_storage/models/model_manifest.py +60 -0
- frogml_storage/models/serialization_metadata.py +15 -0
- frogml_storage/utils/__init__.py +12 -0
- frogml_storage/utils/_environment.py +21 -0
- frogml_storage/utils/_input_checks_utility.py +104 -0
- frogml_storage/utils/_storage_utils.py +15 -0
- frogml_storage/utils/_url_utils.py +27 -0
- qwak/__init__.py +1 -1
- qwak/clients/batch_job_management/client.py +39 -0
- qwak/clients/instance_template/client.py +6 -4
- qwak/clients/prompt_manager/model_descriptor_mapper.py +21 -19
- qwak/feature_store/_common/artifact_utils.py +3 -3
- qwak/feature_store/data_sources/base.py +4 -4
- qwak/feature_store/data_sources/batch/athena.py +3 -3
- qwak/feature_store/feature_sets/streaming.py +3 -3
- qwak/feature_store/feature_sets/streaming_backfill.py +1 -1
- qwak/feature_store/online/client.py +6 -6
- qwak/feature_store/sinks/streaming/factory.py +1 -1
- qwak/inner/build_logic/phases/phase_010_fetch_model/fetch_strategy_manager/strategy/git/git_strategy.py +3 -3
- qwak/inner/di_configuration/account.py +23 -24
- qwak/inner/tool/auth.py +2 -2
- qwak/llmops/provider/openai/provider.py +3 -3
- qwak/model/tools/adapters/output.py +1 -1
- qwak/model/utils/feature_utils.py +12 -8
- qwak/model_loggers/artifact_logger.py +7 -7
- qwak/tools/logger/logger.py +1 -1
- qwak_core-0.4.274.dist-info/METADATA +415 -0
- {qwak_core-0.4.272.dist-info → qwak_core-0.4.274.dist-info}/RECORD +61 -25
- qwak_services_mock/mocks/batch_job_manager_service.py +24 -1
- _qwak_proto/__init__.py +0 -0
- _qwak_proto/qwak/__init__.py +0 -0
- qwak_core-0.4.272.dist-info/METADATA +0 -53
- {qwak_core-0.4.272.dist-info → qwak_core-0.4.274.dist-info}/WHEEL +0 -0
@@ -0,0 +1,668 @@
|
|
1
|
+
import concurrent.futures
|
2
|
+
import os
|
3
|
+
from datetime import datetime, timezone
|
4
|
+
from enum import Enum
|
5
|
+
from typing import Dict, List, Optional, Union
|
6
|
+
|
7
|
+
from frogml_storage.artifactory import ArtifactoryApi, StartTransactionResponse
|
8
|
+
from frogml_storage.authentication.models import AuthConfig
|
9
|
+
from frogml_storage.authentication.utils import get_credentials
|
10
|
+
from frogml_storage.base_storage import BaseStorage
|
11
|
+
from frogml_storage.constants import (
|
12
|
+
DATASET,
|
13
|
+
FROG_ML_DEFAULT_HTTP_THREADS_COUNT,
|
14
|
+
FROG_ML_IGNORED_FILES,
|
15
|
+
JFML_THREAD_COUNT,
|
16
|
+
MODEL,
|
17
|
+
)
|
18
|
+
from frogml_storage.exceptions.checksum_verification_error import (
|
19
|
+
ChecksumVerificationError,
|
20
|
+
)
|
21
|
+
from frogml_storage.exceptions.validation_error import FrogMLValidationError
|
22
|
+
from frogml_storage.logging import logger
|
23
|
+
from frogml_storage.models import DownloadContext
|
24
|
+
from frogml_storage.models.dataset_manifest import DatasetManifest
|
25
|
+
from frogml_storage.models.entity_manifest import Artifact, Checksums, EntityManifest
|
26
|
+
from frogml_storage.models.frogml_dataset_version import FrogMLDatasetVersion
|
27
|
+
from frogml_storage.models.frogml_entity_type_info import FrogMLEntityTypeInfo
|
28
|
+
from frogml_storage.models.frogml_entity_version import FrogMLEntityVersion
|
29
|
+
from frogml_storage.models.frogml_model_version import FrogMLModelVersion
|
30
|
+
from frogml_storage.models.model_manifest import ModelManifest
|
31
|
+
from frogml_storage.models.serialization_metadata import SerializationMetadata
|
32
|
+
from frogml_storage.utils import (
|
33
|
+
calculate_sha2,
|
34
|
+
assemble_artifact_url,
|
35
|
+
join_url,
|
36
|
+
is_not_none,
|
37
|
+
is_valid_thread_number,
|
38
|
+
user_input_validation,
|
39
|
+
validate_not_folder_paths,
|
40
|
+
validate_path_exists,
|
41
|
+
)
|
42
|
+
from frogml_storage.logging.log_utils import (
|
43
|
+
build_download_success_log,
|
44
|
+
build_upload_success_log,
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
class FileType(Enum):
|
49
|
+
REGULAR = 1 # can represent either model or dataset file
|
50
|
+
DEPENDENCY = 2
|
51
|
+
ARCHIVE = 3
|
52
|
+
|
53
|
+
|
54
|
+
class FrogMLStorage(BaseStorage):
|
55
|
+
"""
|
56
|
+
Repository implementation to download or store model|dataset artifacts, and metrics in Artifactory repository.
|
57
|
+
"""
|
58
|
+
|
59
|
+
def __init__(self, login_config: Optional[AuthConfig] = None):
|
60
|
+
uri, auth = get_credentials(login_config)
|
61
|
+
self.uri = assemble_artifact_url(uri)
|
62
|
+
self.auth = auth
|
63
|
+
self.artifactory_api = ArtifactoryApi(self.uri, self.auth)
|
64
|
+
self.__initialize_executor()
|
65
|
+
|
66
|
+
def __initialize_executor(self) -> None:
|
67
|
+
thread_count = os.getenv(JFML_THREAD_COUNT)
|
68
|
+
if thread_count is None or not is_valid_thread_number(thread_count):
|
69
|
+
self.http_threads_count = FROG_ML_DEFAULT_HTTP_THREADS_COUNT
|
70
|
+
else:
|
71
|
+
self.http_threads_count = int(thread_count)
|
72
|
+
|
73
|
+
self.executor = concurrent.futures.ThreadPoolExecutor(
|
74
|
+
max_workers=self.http_threads_count
|
75
|
+
)
|
76
|
+
|
77
|
+
def upload_dataset_version(
|
78
|
+
self,
|
79
|
+
repository: str,
|
80
|
+
dataset_name: str,
|
81
|
+
dataset_path: str,
|
82
|
+
namespace: Optional[str] = None,
|
83
|
+
version: Optional[str] = None,
|
84
|
+
properties: Optional[dict[str, str]] = None,
|
85
|
+
) -> FrogMLDatasetVersion:
|
86
|
+
return FrogMLDatasetVersion.from_entity_version(
|
87
|
+
self.__upload_entity_version(
|
88
|
+
repository=repository,
|
89
|
+
entity_name=dataset_name,
|
90
|
+
source_path=dataset_path,
|
91
|
+
entity_type=DATASET,
|
92
|
+
namespace=namespace,
|
93
|
+
version=version,
|
94
|
+
properties=properties,
|
95
|
+
)
|
96
|
+
)
|
97
|
+
|
98
|
+
def upload_model_version(
|
99
|
+
self,
|
100
|
+
repository: str,
|
101
|
+
model_name: str,
|
102
|
+
model_path: str,
|
103
|
+
model_type: Union[SerializationMetadata, Dict],
|
104
|
+
namespace: Optional[str] = None,
|
105
|
+
version: Optional[str] = None,
|
106
|
+
properties: Optional[dict[str, str]] = None,
|
107
|
+
dependencies_files_paths: Optional[List[str]] = None,
|
108
|
+
code_archive_file_path: Optional[str] = None,
|
109
|
+
) -> FrogMLModelVersion:
|
110
|
+
|
111
|
+
is_not_none("model_type", model_type)
|
112
|
+
if isinstance(model_type, Dict):
|
113
|
+
logger.debug(
|
114
|
+
"received dictionary model_type, converting to SerializationMetadata"
|
115
|
+
)
|
116
|
+
model_type = SerializationMetadata.from_json(model_type)
|
117
|
+
validate_not_folder_paths(dependencies_files_paths)
|
118
|
+
validate_not_folder_paths(code_archive_file_path)
|
119
|
+
validate_path_exists(model_path)
|
120
|
+
|
121
|
+
return FrogMLModelVersion.from_entity_version(
|
122
|
+
self.__upload_entity_version(
|
123
|
+
repository=repository,
|
124
|
+
entity_name=model_name,
|
125
|
+
source_path=model_path,
|
126
|
+
entity_type=MODEL,
|
127
|
+
model_type=model_type,
|
128
|
+
namespace=namespace,
|
129
|
+
version=version,
|
130
|
+
properties=properties,
|
131
|
+
dependencies_files_paths=dependencies_files_paths,
|
132
|
+
code_archive_file_path=code_archive_file_path,
|
133
|
+
)
|
134
|
+
)
|
135
|
+
|
136
|
+
def __upload_entity_version(
|
137
|
+
self,
|
138
|
+
repository: str,
|
139
|
+
entity_name: str,
|
140
|
+
source_path: str,
|
141
|
+
entity_type: str = "model",
|
142
|
+
model_type: Optional[SerializationMetadata] = None,
|
143
|
+
namespace: Optional[str] = None,
|
144
|
+
version: Optional[str] = None,
|
145
|
+
properties: Optional[dict[str, str]] = None,
|
146
|
+
dependencies_files_paths: Optional[List[str]] = None,
|
147
|
+
code_archive_file_path: Optional[str] = None,
|
148
|
+
) -> FrogMLEntityVersion:
|
149
|
+
try:
|
150
|
+
self.__verify_upload_required_args_not_none(
|
151
|
+
repository=repository, entity_name=entity_name, source_path=source_path
|
152
|
+
)
|
153
|
+
|
154
|
+
user_input_validation(
|
155
|
+
entity_name=entity_name,
|
156
|
+
namespace=namespace,
|
157
|
+
version=version,
|
158
|
+
properties=properties,
|
159
|
+
)
|
160
|
+
|
161
|
+
validate_path_exists(source_path)
|
162
|
+
|
163
|
+
namespace_and_name = self.__union_entity_name_with_namespace(
|
164
|
+
namespace, entity_name
|
165
|
+
)
|
166
|
+
|
167
|
+
entity_type_info = FrogMLEntityTypeInfo.from_string(entity_type)
|
168
|
+
response: StartTransactionResponse = self.artifactory_api.start_transaction(
|
169
|
+
entity_type_info=entity_type_info,
|
170
|
+
repository=repository,
|
171
|
+
entity_name=namespace_and_name,
|
172
|
+
version=version,
|
173
|
+
)
|
174
|
+
|
175
|
+
if version is None:
|
176
|
+
version = response.files_upload_path.replace(
|
177
|
+
f"{entity_type_info.folder_name}/{namespace_and_name}/tmp/", # nosec B108
|
178
|
+
"",
|
179
|
+
).split("/")[0]
|
180
|
+
logger.debug(
|
181
|
+
"Version was not specified. Setting version to {}".format(version)
|
182
|
+
)
|
183
|
+
|
184
|
+
transaction_date = self.__milliseconds_to_iso_instant(
|
185
|
+
response.transaction_id
|
186
|
+
)
|
187
|
+
|
188
|
+
entity_manifest: Union[ModelManifest, DatasetManifest]
|
189
|
+
if entity_type_info == FrogMLEntityTypeInfo.MODEL:
|
190
|
+
if not isinstance(model_type, SerializationMetadata):
|
191
|
+
raise AssertionError(
|
192
|
+
f"model_type must be {SerializationMetadata.__name__}"
|
193
|
+
)
|
194
|
+
|
195
|
+
entity_manifest = ModelManifest(
|
196
|
+
created_date=transaction_date,
|
197
|
+
artifacts=[],
|
198
|
+
model_format=model_type,
|
199
|
+
)
|
200
|
+
self.__upload_model(
|
201
|
+
source_path=source_path,
|
202
|
+
repository=repository,
|
203
|
+
files_upload_path=response.files_upload_path,
|
204
|
+
dependencies_upload_path=response.dependencies_upload_path,
|
205
|
+
code_upload_path=response.code_upload_path,
|
206
|
+
entity_name=entity_name,
|
207
|
+
version=version,
|
208
|
+
entity_type_info=entity_type_info,
|
209
|
+
model_manifest=entity_manifest,
|
210
|
+
dependency_files=dependencies_files_paths,
|
211
|
+
code_file=code_archive_file_path,
|
212
|
+
)
|
213
|
+
else:
|
214
|
+
entity_manifest = DatasetManifest(
|
215
|
+
created_date=transaction_date,
|
216
|
+
artifacts=[],
|
217
|
+
)
|
218
|
+
self.__upload_dataset(
|
219
|
+
source_path=source_path,
|
220
|
+
repository=repository,
|
221
|
+
location_to_upload=response.files_upload_path,
|
222
|
+
entity_name=entity_name,
|
223
|
+
version=version,
|
224
|
+
entity_type_info=entity_type_info,
|
225
|
+
dataset_manifest=entity_manifest,
|
226
|
+
)
|
227
|
+
|
228
|
+
self.artifactory_api.end_transaction(
|
229
|
+
entity_type=entity_type_info,
|
230
|
+
repository=repository,
|
231
|
+
entity_name=namespace_and_name,
|
232
|
+
entity_manifest=entity_manifest,
|
233
|
+
transaction_id=response.transaction_id,
|
234
|
+
version=version,
|
235
|
+
properties=properties,
|
236
|
+
)
|
237
|
+
|
238
|
+
return FrogMLEntityVersion(
|
239
|
+
entity_name=entity_name,
|
240
|
+
namespace=namespace,
|
241
|
+
version=version,
|
242
|
+
entity_manifest=entity_manifest,
|
243
|
+
)
|
244
|
+
|
245
|
+
except FrogMLValidationError as error:
|
246
|
+
logger.error(error.args[0], exc_info=False)
|
247
|
+
|
248
|
+
return FrogMLEntityVersion(
|
249
|
+
entity_name="", namespace="", version="", entity_manifest=None
|
250
|
+
)
|
251
|
+
|
252
|
+
def download_dataset_version(
|
253
|
+
self,
|
254
|
+
repository: str,
|
255
|
+
dataset_name: str,
|
256
|
+
version: str,
|
257
|
+
target_path: str,
|
258
|
+
namespace: Optional[str] = None,
|
259
|
+
) -> None:
|
260
|
+
return self.__download_entity_version(
|
261
|
+
repository=repository,
|
262
|
+
entity_name=dataset_name,
|
263
|
+
version=version,
|
264
|
+
target_path=target_path,
|
265
|
+
entity_type=DATASET,
|
266
|
+
namespace=namespace,
|
267
|
+
)
|
268
|
+
|
269
|
+
def download_model_version(
|
270
|
+
self,
|
271
|
+
repository: str,
|
272
|
+
model_name: str,
|
273
|
+
version: str,
|
274
|
+
target_path: str,
|
275
|
+
namespace: Optional[str] = None,
|
276
|
+
) -> None:
|
277
|
+
return self.__download_entity_version(
|
278
|
+
repository=repository,
|
279
|
+
entity_name=model_name,
|
280
|
+
version=version,
|
281
|
+
target_path=target_path,
|
282
|
+
entity_type=MODEL,
|
283
|
+
namespace=namespace,
|
284
|
+
)
|
285
|
+
|
286
|
+
def __download_entity_version(
|
287
|
+
self,
|
288
|
+
repository: str,
|
289
|
+
entity_name: str,
|
290
|
+
version: str,
|
291
|
+
target_path: str,
|
292
|
+
entity_type: str,
|
293
|
+
namespace: Optional[str] = None,
|
294
|
+
) -> None:
|
295
|
+
try:
|
296
|
+
self.__verify_download_required_args_not_none(
|
297
|
+
repository=repository,
|
298
|
+
entity_name=entity_name,
|
299
|
+
version=version,
|
300
|
+
target_path=target_path,
|
301
|
+
)
|
302
|
+
user_input_validation(
|
303
|
+
entity_name=entity_name, version=version, namespace=namespace
|
304
|
+
)
|
305
|
+
|
306
|
+
entity_type_info = FrogMLEntityTypeInfo.from_string(entity_type)
|
307
|
+
|
308
|
+
download_context_list = self._get_download_context_list(
|
309
|
+
entity_type_info=entity_type_info,
|
310
|
+
repository=repository,
|
311
|
+
entity_name=entity_name,
|
312
|
+
version=version,
|
313
|
+
target_dir_path=target_path,
|
314
|
+
namespace=namespace,
|
315
|
+
)
|
316
|
+
|
317
|
+
if len(download_context_list) == 0:
|
318
|
+
logger.info(
|
319
|
+
f"No files to download for {entity_type_info.entity_type}: '{entity_name}' version: '{version}'"
|
320
|
+
)
|
321
|
+
return
|
322
|
+
for download_context in download_context_list[:]:
|
323
|
+
if download_context.exists_locally:
|
324
|
+
logger.info(
|
325
|
+
f"File '{download_context.target_path}' already exists locally, will not download it."
|
326
|
+
)
|
327
|
+
download_context_list.remove(download_context)
|
328
|
+
elif not self.__is_checksum_valid(download_context):
|
329
|
+
raise ChecksumVerificationError(
|
330
|
+
os.path.basename(download_context.target_path)
|
331
|
+
)
|
332
|
+
|
333
|
+
successfully_downloaded = self._execute_parallel_download(
|
334
|
+
download_context_list
|
335
|
+
)
|
336
|
+
|
337
|
+
if successfully_downloaded:
|
338
|
+
successful_log = build_download_success_log(
|
339
|
+
entity_type_info, entity_name, version
|
340
|
+
)
|
341
|
+
logger.info(successful_log)
|
342
|
+
|
343
|
+
except FrogMLValidationError as error:
|
344
|
+
logger.error(error.args[0], exc_info=False)
|
345
|
+
except Exception as e:
|
346
|
+
logger.error(
|
347
|
+
f"An error occurred during download_entity_version: {e}", exc_info=False
|
348
|
+
)
|
349
|
+
raise e
|
350
|
+
|
351
|
+
def __is_checksum_valid(self, download_arg: DownloadContext) -> bool:
|
352
|
+
if not download_arg.artifact_checksum:
|
353
|
+
return False
|
354
|
+
return (
|
355
|
+
download_arg.artifact_checksum.sha2
|
356
|
+
== self.artifactory_api.get_artifact_checksum(download_arg)
|
357
|
+
)
|
358
|
+
|
359
|
+
def _get_download_context_list(
|
360
|
+
self,
|
361
|
+
entity_type_info: FrogMLEntityTypeInfo,
|
362
|
+
repository: str,
|
363
|
+
entity_name: str,
|
364
|
+
version: str,
|
365
|
+
target_dir_path: str,
|
366
|
+
namespace: Optional[str] = None,
|
367
|
+
) -> List[DownloadContext]:
|
368
|
+
download_context_list = []
|
369
|
+
entity_manifest = self.artifactory_api.get_entity_manifest(
|
370
|
+
entity_type_info,
|
371
|
+
repository,
|
372
|
+
entity_name,
|
373
|
+
namespace,
|
374
|
+
version,
|
375
|
+
)
|
376
|
+
search_under_repo = f"/{repository}/"
|
377
|
+
|
378
|
+
for artifact in entity_manifest[
|
379
|
+
(
|
380
|
+
"model_artifacts"
|
381
|
+
if entity_type_info == FrogMLEntityTypeInfo.MODEL
|
382
|
+
else "dataset_artifacts"
|
383
|
+
)
|
384
|
+
]:
|
385
|
+
download_url = artifact["download_url"]
|
386
|
+
artifact_path = artifact["artifact_path"]
|
387
|
+
artifact_checksum = Checksums(**artifact["checksums"])
|
388
|
+
target_path = os.path.join(target_dir_path, artifact_path)
|
389
|
+
position_under_repo = download_url.find(search_under_repo)
|
390
|
+
if position_under_repo != -1:
|
391
|
+
repo_rel_path = download_url[
|
392
|
+
position_under_repo + len(search_under_repo) :
|
393
|
+
]
|
394
|
+
|
395
|
+
args = DownloadContext(
|
396
|
+
repo_key=repository,
|
397
|
+
source_url=repo_rel_path,
|
398
|
+
target_path=target_path,
|
399
|
+
artifact_checksum=artifact_checksum,
|
400
|
+
)
|
401
|
+
|
402
|
+
if not os.path.exists(target_path):
|
403
|
+
self.__create_dirs_if_needed(target_dir_path, artifact_path)
|
404
|
+
else:
|
405
|
+
args.exists_locally = True
|
406
|
+
download_context_list.append(args)
|
407
|
+
return download_context_list
|
408
|
+
|
409
|
+
def _execute_parallel_download(
|
410
|
+
self, download_context_list: List[DownloadContext]
|
411
|
+
) -> int:
|
412
|
+
if len(download_context_list) == 0:
|
413
|
+
logger.debug("No files to download.")
|
414
|
+
return 0
|
415
|
+
logger.debug(f"Fetching: {len(download_context_list)} files.")
|
416
|
+
futures = []
|
417
|
+
for download_arg in download_context_list:
|
418
|
+
future = self.executor.submit(
|
419
|
+
self.artifactory_api.download_file, download_arg
|
420
|
+
)
|
421
|
+
futures.append(future)
|
422
|
+
return self.__submit_and_handle_futures(futures)
|
423
|
+
|
424
|
+
def get_entity_manifest(
|
425
|
+
self,
|
426
|
+
entity_type: str,
|
427
|
+
repository: str,
|
428
|
+
namespace: Optional[str],
|
429
|
+
entity_name: str,
|
430
|
+
version: Optional[str],
|
431
|
+
) -> dict:
|
432
|
+
entity_type_info = FrogMLEntityTypeInfo.from_string(entity_type)
|
433
|
+
return self.artifactory_api.get_entity_manifest(
|
434
|
+
entity_type_info=entity_type_info,
|
435
|
+
repository=repository,
|
436
|
+
entity_name=entity_name,
|
437
|
+
namespace=namespace,
|
438
|
+
version=version,
|
439
|
+
)
|
440
|
+
|
441
|
+
def __upload_by_source(
|
442
|
+
self,
|
443
|
+
source_path: str,
|
444
|
+
repository: str,
|
445
|
+
location_to_upload: str,
|
446
|
+
file_type: FileType,
|
447
|
+
entity_manifest: Optional[EntityManifest] = None,
|
448
|
+
) -> bool:
|
449
|
+
if os.path.isfile(source_path):
|
450
|
+
rel_path = os.path.basename(source_path)
|
451
|
+
self.__upload_single_file(
|
452
|
+
(
|
453
|
+
repository,
|
454
|
+
source_path,
|
455
|
+
rel_path,
|
456
|
+
location_to_upload,
|
457
|
+
file_type,
|
458
|
+
entity_manifest,
|
459
|
+
)
|
460
|
+
)
|
461
|
+
return True
|
462
|
+
|
463
|
+
else:
|
464
|
+
futures = []
|
465
|
+
for dir_path, dir_names, file_names in os.walk(source_path):
|
466
|
+
for filename in file_names:
|
467
|
+
full_path = os.path.join(dir_path, filename)
|
468
|
+
rel_path = os.path.relpath(full_path, source_path)
|
469
|
+
future = self.executor.submit(
|
470
|
+
self.__upload_single_file,
|
471
|
+
(
|
472
|
+
repository,
|
473
|
+
full_path,
|
474
|
+
rel_path,
|
475
|
+
location_to_upload,
|
476
|
+
file_type,
|
477
|
+
entity_manifest,
|
478
|
+
),
|
479
|
+
)
|
480
|
+
futures.append(future)
|
481
|
+
|
482
|
+
successfully_uploaded = self.__submit_and_handle_futures(futures)
|
483
|
+
|
484
|
+
if successfully_uploaded > 0:
|
485
|
+
return True
|
486
|
+
return False
|
487
|
+
|
488
|
+
def __upload_model(
|
489
|
+
self,
|
490
|
+
source_path: str,
|
491
|
+
repository: str,
|
492
|
+
files_upload_path: str,
|
493
|
+
dependencies_upload_path: str,
|
494
|
+
code_upload_path: str,
|
495
|
+
entity_name: str,
|
496
|
+
version: str,
|
497
|
+
entity_type_info: FrogMLEntityTypeInfo,
|
498
|
+
model_manifest: ModelManifest,
|
499
|
+
dependency_files: Optional[List[str]] = None,
|
500
|
+
code_file: Optional[str] = None,
|
501
|
+
) -> None:
|
502
|
+
logger.debug("Uploading model files...")
|
503
|
+
is_successful_upload: bool = self.__upload_by_source(
|
504
|
+
source_path,
|
505
|
+
repository,
|
506
|
+
files_upload_path,
|
507
|
+
FileType.REGULAR,
|
508
|
+
model_manifest,
|
509
|
+
)
|
510
|
+
logger.debug("Uploading model requirement files...")
|
511
|
+
if dependency_files is not None:
|
512
|
+
for dependency_file in dependency_files:
|
513
|
+
is_successful_upload &= self.__upload_by_source(
|
514
|
+
dependency_file,
|
515
|
+
repository,
|
516
|
+
dependencies_upload_path,
|
517
|
+
FileType.DEPENDENCY,
|
518
|
+
model_manifest,
|
519
|
+
)
|
520
|
+
logger.debug("Uploading model code archive file...")
|
521
|
+
if code_file is not None:
|
522
|
+
is_successful_upload &= self.__upload_by_source(
|
523
|
+
code_file,
|
524
|
+
repository,
|
525
|
+
code_upload_path,
|
526
|
+
FileType.ARCHIVE,
|
527
|
+
model_manifest,
|
528
|
+
)
|
529
|
+
if is_successful_upload:
|
530
|
+
successful_log = build_upload_success_log(
|
531
|
+
entity_type_info, entity_name, version
|
532
|
+
)
|
533
|
+
logger.info(successful_log)
|
534
|
+
|
535
|
+
def __upload_dataset(
|
536
|
+
self,
|
537
|
+
source_path: str,
|
538
|
+
repository: str,
|
539
|
+
location_to_upload: str,
|
540
|
+
entity_name: str,
|
541
|
+
version: str,
|
542
|
+
entity_type_info: FrogMLEntityTypeInfo,
|
543
|
+
dataset_manifest: DatasetManifest,
|
544
|
+
) -> None:
|
545
|
+
logger.debug("Uploading dataset files...")
|
546
|
+
is_successful_upload: bool = self.__upload_by_source(
|
547
|
+
source_path=source_path,
|
548
|
+
repository=repository,
|
549
|
+
location_to_upload=location_to_upload,
|
550
|
+
file_type=FileType.REGULAR,
|
551
|
+
entity_manifest=dataset_manifest,
|
552
|
+
)
|
553
|
+
if is_successful_upload:
|
554
|
+
successful_log = build_upload_success_log(
|
555
|
+
entity_type_info, entity_name, version
|
556
|
+
)
|
557
|
+
logger.info(successful_log)
|
558
|
+
|
559
|
+
@staticmethod
|
560
|
+
def __submit_and_handle_futures(futures: list) -> int:
|
561
|
+
failed_futures = []
|
562
|
+
for future in futures:
|
563
|
+
try:
|
564
|
+
future.result()
|
565
|
+
except Exception as e:
|
566
|
+
failed_futures.append(e.args)
|
567
|
+
|
568
|
+
if len(failed_futures) > 0:
|
569
|
+
for failed_future in failed_futures:
|
570
|
+
logger.error(f"{failed_future}")
|
571
|
+
|
572
|
+
return len(futures) - len(failed_futures)
|
573
|
+
|
574
|
+
def __upload_single_file(self, args):
|
575
|
+
(
|
576
|
+
repository,
|
577
|
+
full_path,
|
578
|
+
rel_path,
|
579
|
+
location_to_upload,
|
580
|
+
file_type,
|
581
|
+
entity_manifest,
|
582
|
+
) = args
|
583
|
+
if not FrogMLStorage.__is_ignored_file(full_path=full_path):
|
584
|
+
checksums = Checksums.calc_checksums(full_path)
|
585
|
+
if entity_manifest is not None:
|
586
|
+
if file_type == FileType.REGULAR:
|
587
|
+
entity_manifest.add_file(full_path, checksums, rel_path)
|
588
|
+
elif file_type == FileType.DEPENDENCY:
|
589
|
+
entity_manifest.add_dependency_file(full_path, checksums, rel_path)
|
590
|
+
elif file_type == FileType.ARCHIVE:
|
591
|
+
entity_manifest.code_artifacts = Artifact(
|
592
|
+
artifact_path=rel_path,
|
593
|
+
size=os.path.getsize(full_path),
|
594
|
+
checksums=checksums,
|
595
|
+
)
|
596
|
+
url = join_url(self.uri, repository, location_to_upload, rel_path)
|
597
|
+
is_checksum_deploy_success = self.artifactory_api.checksum_deployment(
|
598
|
+
url=url, checksum=checksums, full_path=full_path, stream=True
|
599
|
+
)
|
600
|
+
if is_checksum_deploy_success is False:
|
601
|
+
self.artifactory_api.upload_file(url=url, file_path=full_path)
|
602
|
+
|
603
|
+
@staticmethod
|
604
|
+
def __calculate_sha2_no_error(file_path: str) -> Optional[str]:
|
605
|
+
# Used to calculate sha2 without raising an exception
|
606
|
+
if os.path.exists(file_path) is False:
|
607
|
+
logger.debug(f"File {file_path} does not exist, can't calculate sha256")
|
608
|
+
return None
|
609
|
+
try:
|
610
|
+
return calculate_sha2(file_path)
|
611
|
+
except Exception as e:
|
612
|
+
logger.debug(f"Failed to calculate sha256 for file {file_path}. Error: {e}")
|
613
|
+
return None
|
614
|
+
|
615
|
+
@staticmethod
|
616
|
+
def __verify_download_required_args_not_none(
|
617
|
+
repository: str, entity_name: str, version: str, target_path: str
|
618
|
+
) -> bool:
|
619
|
+
is_not_none("repository", repository)
|
620
|
+
is_not_none("entity_name", entity_name)
|
621
|
+
is_not_none("version", version)
|
622
|
+
is_not_none("target_path", target_path)
|
623
|
+
return True
|
624
|
+
|
625
|
+
@staticmethod
|
626
|
+
def __verify_upload_required_args_not_none(
|
627
|
+
repository: str, entity_name: str, source_path: str
|
628
|
+
) -> bool:
|
629
|
+
is_not_none("repository", repository)
|
630
|
+
is_not_none("entity_name", entity_name)
|
631
|
+
is_not_none("source_path", source_path)
|
632
|
+
return True
|
633
|
+
|
634
|
+
@staticmethod
|
635
|
+
def __is_ignored_file(full_path: str) -> bool:
|
636
|
+
normalized_path = os.path.normpath(full_path).lower()
|
637
|
+
parts = normalized_path.split(os.sep)
|
638
|
+
ignored_files_lower = [item.lower() for item in FROG_ML_IGNORED_FILES]
|
639
|
+
|
640
|
+
for part in parts:
|
641
|
+
if part in ignored_files_lower:
|
642
|
+
return True
|
643
|
+
return False
|
644
|
+
|
645
|
+
@staticmethod
|
646
|
+
def __union_entity_name_with_namespace(
|
647
|
+
namespace: Optional[str], entity_name: str
|
648
|
+
) -> str:
|
649
|
+
if namespace is None:
|
650
|
+
return entity_name
|
651
|
+
else:
|
652
|
+
return namespace + "/" + entity_name
|
653
|
+
|
654
|
+
@staticmethod
|
655
|
+
def __create_dirs_if_needed(base_dir: str, file_uri: str) -> str:
|
656
|
+
full_path = os.path.join(base_dir, file_uri.strip("/"))
|
657
|
+
dest_path = os.path.dirname(full_path)
|
658
|
+
if not os.path.exists(dest_path):
|
659
|
+
os.makedirs(dest_path)
|
660
|
+
return dest_path
|
661
|
+
|
662
|
+
@staticmethod
|
663
|
+
def __milliseconds_to_iso_instant(milliseconds: str) -> str:
|
664
|
+
instant: datetime = datetime.fromtimestamp(
|
665
|
+
int(milliseconds) / 1000.0, tz=timezone.utc
|
666
|
+
)
|
667
|
+
x = instant.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4]
|
668
|
+
return x
|
@@ -0,0 +1 @@
|
|
1
|
+
from .http_client import HTTPClient
|