qwak-core 0.4.272__py3-none-any.whl → 0.4.273__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/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.273.dist-info/METADATA +415 -0
- {qwak_core-0.4.272.dist-info → qwak_core-0.4.273.dist-info}/RECORD +59 -23
- _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.273.dist-info}/WHEEL +0 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Optional, Tuple
|
3
|
+
|
4
|
+
import requests
|
5
|
+
from requests.adapters import HTTPAdapter
|
6
|
+
from urllib3 import Retry
|
7
|
+
|
8
|
+
import frogml_storage
|
9
|
+
from frogml_storage.logging import logger
|
10
|
+
|
11
|
+
|
12
|
+
class HTTPClient:
|
13
|
+
|
14
|
+
def __init__(
|
15
|
+
self, auth: Tuple[str, str], session: Optional[requests.Session] = None
|
16
|
+
):
|
17
|
+
self.auth = auth
|
18
|
+
# add default headers
|
19
|
+
if session is None:
|
20
|
+
self.session = self._create_session()
|
21
|
+
self._add_default_headers()
|
22
|
+
self.timeout = os.getenv("JFML_TIMEOUT", default=30)
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def _create_session():
|
26
|
+
session = requests.Session()
|
27
|
+
adapter = HTTPAdapter(
|
28
|
+
max_retries=RetryWithLog(
|
29
|
+
total=5, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504]
|
30
|
+
)
|
31
|
+
)
|
32
|
+
session.mount("http://", adapter)
|
33
|
+
session.mount("https://", adapter)
|
34
|
+
return session
|
35
|
+
|
36
|
+
def post(self, url, data=None, params=None):
|
37
|
+
return self.session.post(
|
38
|
+
url, auth=self.auth, timeout=self.timeout, data=data, params=params
|
39
|
+
)
|
40
|
+
|
41
|
+
def get(self, url, params=None, stream=False):
|
42
|
+
return self.session.get(url, auth=self.auth, params=params, stream=stream)
|
43
|
+
|
44
|
+
def put(self, url, payload=None, files=None, stream=False, headers=None, json=None):
|
45
|
+
return self.session.request(
|
46
|
+
method="PUT",
|
47
|
+
url=url,
|
48
|
+
data=payload,
|
49
|
+
auth=self.auth,
|
50
|
+
files=files,
|
51
|
+
stream=stream,
|
52
|
+
timeout=self.timeout,
|
53
|
+
headers=headers,
|
54
|
+
json=json,
|
55
|
+
)
|
56
|
+
|
57
|
+
def delete(self, url):
|
58
|
+
return self.session.request(
|
59
|
+
method="DELETE",
|
60
|
+
url=url,
|
61
|
+
auth=self.auth,
|
62
|
+
timeout=self.timeout,
|
63
|
+
)
|
64
|
+
|
65
|
+
def head(self, url, params=None, stream=False):
|
66
|
+
return self.session.head(url, auth=self.auth, params=params, stream=stream)
|
67
|
+
|
68
|
+
def _add_default_headers(self):
|
69
|
+
self.session.headers.update(
|
70
|
+
{"User-Agent": "frogml-sdk-python/{}".format(frogml_storage.__version__)}
|
71
|
+
)
|
72
|
+
|
73
|
+
|
74
|
+
class RetryWithLog(Retry):
|
75
|
+
"""
|
76
|
+
Adding extra logs before making a retry request
|
77
|
+
"""
|
78
|
+
|
79
|
+
def __init__(self, *args, **kwargs):
|
80
|
+
history = kwargs.get("history")
|
81
|
+
if history is not None:
|
82
|
+
logger.debug(f"Error: ${history[-1].error}\nretrying...")
|
83
|
+
super().__init__(*args, **kwargs)
|
@@ -0,0 +1 @@
|
|
1
|
+
from ._log_config import logger
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import logging.config
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
|
5
|
+
log_level = (
|
6
|
+
"DEBUG"
|
7
|
+
if os.getenv("JFML_DEBUG", "false").casefold() == "true".casefold()
|
8
|
+
else "INFO"
|
9
|
+
)
|
10
|
+
log_file = f'{os.path.expanduser("~")}/.frogml/frogml-log-history.log'
|
11
|
+
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
12
|
+
|
13
|
+
DEFAULT_LOGGING = {
|
14
|
+
"version": 1,
|
15
|
+
"formatters": {
|
16
|
+
"standard": {
|
17
|
+
"format": "%(asctime)s - %(levelname)s - %(name)s.%(module)s.%(funcName)s:%(lineno)d - %(message)s"
|
18
|
+
},
|
19
|
+
},
|
20
|
+
"handlers": {
|
21
|
+
"console": {
|
22
|
+
"class": "logging.StreamHandler",
|
23
|
+
"formatter": "standard",
|
24
|
+
"stream": sys.stdout,
|
25
|
+
},
|
26
|
+
"file": {
|
27
|
+
"class": "logging.FileHandler",
|
28
|
+
"formatter": "standard",
|
29
|
+
"filename": log_file,
|
30
|
+
},
|
31
|
+
},
|
32
|
+
"loggers": {
|
33
|
+
__name__: {
|
34
|
+
"level": log_level,
|
35
|
+
"handlers": ["console", "file"],
|
36
|
+
"propagate": False,
|
37
|
+
},
|
38
|
+
},
|
39
|
+
}
|
40
|
+
|
41
|
+
if os.getenv("IS_LOGGER_SHADED") is not None:
|
42
|
+
logger = logging.getLogger(__name__)
|
43
|
+
else:
|
44
|
+
logging.config.dictConfig(DEFAULT_LOGGING)
|
45
|
+
logger = logging.getLogger(__name__)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from frogml_storage.models.frogml_entity_type_info import FrogMLEntityTypeInfo
|
2
|
+
|
3
|
+
|
4
|
+
# The following method affect e2e tests.
|
5
|
+
def build_download_success_log(
|
6
|
+
entity_type_info: FrogMLEntityTypeInfo, entity_name: str, version: str
|
7
|
+
) -> str:
|
8
|
+
return (
|
9
|
+
f'{entity_type_info.entity_type.capitalize()}: "{entity_name}", version: "{version}"'
|
10
|
+
f" has been downloaded successfully"
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
# The following method affect e2e tests.
|
15
|
+
def build_upload_success_log(
|
16
|
+
entity_type_info: FrogMLEntityTypeInfo, entity_name: str, version: str
|
17
|
+
) -> str:
|
18
|
+
return (
|
19
|
+
f'{entity_type_info.entity_type.capitalize()}: "{entity_name}", version: "{version}"'
|
20
|
+
f" has been uploaded successfully"
|
21
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
from ._download_context import DownloadContext
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from frogml_storage.models.entity_manifest import Checksums
|
4
|
+
|
5
|
+
|
6
|
+
class DownloadContext(object):
|
7
|
+
"""
|
8
|
+
A class to represent the arguments for a download operation.
|
9
|
+
|
10
|
+
Attributes
|
11
|
+
----------
|
12
|
+
repo_key : str
|
13
|
+
The key of the repository where the artifact is located.
|
14
|
+
source_url : str
|
15
|
+
The source relative URL of the artifact, relative to artifactory url and the repo key.
|
16
|
+
target_path : str
|
17
|
+
The target path where the artifact will be downloaded to.
|
18
|
+
exists_locally : bool
|
19
|
+
A flag indicating whether the artifact already exists locally in the target path.
|
20
|
+
artifact_checksum: Checksums
|
21
|
+
The checksum of the artifact.
|
22
|
+
"""
|
23
|
+
|
24
|
+
repo_key: str
|
25
|
+
source_url: str
|
26
|
+
target_path: str
|
27
|
+
exists_locally: bool = False
|
28
|
+
artifact_checksum: Optional[Checksums]
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
repo_key: str,
|
33
|
+
source_url: str,
|
34
|
+
target_path: str,
|
35
|
+
exists_locally: bool = False,
|
36
|
+
artifact_checksum: Optional[Checksums] = None,
|
37
|
+
):
|
38
|
+
self.repo_key = repo_key
|
39
|
+
self.source_url = source_url
|
40
|
+
self.target_path = target_path
|
41
|
+
self.exists_locally = exists_locally
|
42
|
+
self.artifact_checksum = artifact_checksum
|
43
|
+
|
44
|
+
def __eq__(self, other):
|
45
|
+
if not isinstance(other, DownloadContext):
|
46
|
+
return False
|
47
|
+
|
48
|
+
return (
|
49
|
+
self.repo_key == other.repo_key
|
50
|
+
and self.source_url == other.source_url
|
51
|
+
and self.target_path == other.target_path
|
52
|
+
and self.exists_locally == other.exists_locally
|
53
|
+
and self.artifact_checksum == other.artifact_checksum
|
54
|
+
)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from pydantic import Field
|
4
|
+
|
5
|
+
from frogml_storage.models.entity_manifest import Artifact, EntityManifest
|
6
|
+
|
7
|
+
|
8
|
+
class DatasetManifest(EntityManifest):
|
9
|
+
"""
|
10
|
+
Represent a dataset manifest file
|
11
|
+
"""
|
12
|
+
|
13
|
+
artifacts: List[Artifact] = Field(serialization_alias="dataset_artifacts")
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import os
|
2
|
+
from abc import ABC
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from frogml_storage.utils import calc_content_sha2, calculate_sha2
|
8
|
+
|
9
|
+
|
10
|
+
class Checksums(BaseModel):
|
11
|
+
sha2: str
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def calc_checksums(cls, file_path: str) -> "Checksums":
|
15
|
+
return cls(sha2=calculate_sha2(file_path))
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def calc_content_checksums(cls, content: str) -> "Checksums":
|
19
|
+
return cls(sha2=calc_content_sha2(content))
|
20
|
+
|
21
|
+
|
22
|
+
class Artifact(BaseModel):
|
23
|
+
artifact_path: str
|
24
|
+
size: int
|
25
|
+
checksums: Checksums
|
26
|
+
|
27
|
+
def __eq__(self, other):
|
28
|
+
if not isinstance(other, Artifact):
|
29
|
+
return False
|
30
|
+
return (
|
31
|
+
self.artifact_path == other.artifact_path
|
32
|
+
and self.size == other.size
|
33
|
+
and self.checksums == other.checksums
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class EntityManifest(BaseModel, ABC):
|
38
|
+
"""
|
39
|
+
Represent an entity manifest file
|
40
|
+
|
41
|
+
Attributes:
|
42
|
+
created_date: The date the model | dataset were uploaded to Artifactory
|
43
|
+
artifacts: A list of artifacts that belong to the model | dataset
|
44
|
+
id: <organization>/<entity_name> - exists only for downloaded EntityInfo
|
45
|
+
version: The entity version - exists only for downloaded EntityInfo
|
46
|
+
"""
|
47
|
+
|
48
|
+
created_date: str
|
49
|
+
artifacts: List[Artifact]
|
50
|
+
id: Optional[str] = None
|
51
|
+
version: Optional[str] = None
|
52
|
+
|
53
|
+
def add_file(self, file_path: str, checksums: Checksums, rel_path: str) -> None:
|
54
|
+
self.artifacts.append(
|
55
|
+
Artifact(
|
56
|
+
artifact_path=rel_path,
|
57
|
+
size=os.path.getsize(file_path),
|
58
|
+
checksums=checksums,
|
59
|
+
)
|
60
|
+
)
|
61
|
+
|
62
|
+
def add_content_file(self, rel_path: str, content: str) -> None:
|
63
|
+
checksums = Checksums.calc_content_checksums(content)
|
64
|
+
self.artifacts.append(
|
65
|
+
Artifact(
|
66
|
+
artifact_path=rel_path,
|
67
|
+
size=len((content, "utf-8")),
|
68
|
+
checksums=checksums,
|
69
|
+
)
|
70
|
+
)
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def from_json(cls, json_str: str) -> "EntityManifest":
|
74
|
+
return cls.model_validate_json(json_str)
|
75
|
+
|
76
|
+
def to_json(self) -> str:
|
77
|
+
return self.model_dump_json(by_alias=True, exclude_none=True)
|
78
|
+
|
79
|
+
def __eq__(self, other):
|
80
|
+
if not isinstance(other, EntityManifest):
|
81
|
+
return False
|
82
|
+
if self.id != other.id:
|
83
|
+
return False
|
84
|
+
if self.version != other.version:
|
85
|
+
return False
|
86
|
+
if self.created_date != other.created_date:
|
87
|
+
return False
|
88
|
+
if len(self.artifacts) != len(other.artifacts):
|
89
|
+
return False
|
90
|
+
for self_artifact, other_artifact in zip(self.artifacts, other.artifacts):
|
91
|
+
if self_artifact != other_artifact:
|
92
|
+
return False
|
93
|
+
return True
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from frogml_storage.models.frogml_entity_version import FrogMLEntityVersion
|
2
|
+
|
3
|
+
|
4
|
+
class FrogMLDatasetVersion(FrogMLEntityVersion):
|
5
|
+
"""
|
6
|
+
Represent metadata of an uploaded dataset version.
|
7
|
+
|
8
|
+
Inherits:
|
9
|
+
FrogMLEntityVersion: Base class for entity versions.
|
10
|
+
"""
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
def from_entity_version(
|
14
|
+
cls, entity_version: FrogMLEntityVersion
|
15
|
+
) -> "FrogMLDatasetVersion":
|
16
|
+
return cls(
|
17
|
+
entity_name=entity_version.entity_name,
|
18
|
+
version=entity_version.version,
|
19
|
+
namespace=entity_version.namespace,
|
20
|
+
entity_manifest=entity_version.entity_manifest,
|
21
|
+
)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
from typing_extensions import Self
|
4
|
+
|
5
|
+
from frogml_storage.constants import (
|
6
|
+
BODY_PART_DATASET_MANIFEST_STREAM,
|
7
|
+
BODY_PART_MODEL_MANIFEST_STREAM,
|
8
|
+
DATASET_METADATA_FILE_NAME,
|
9
|
+
DATASET_UI_DIRECTORY,
|
10
|
+
MODEL_METADATA_FILE_NAME,
|
11
|
+
MODEL_UI_DIRECTORY,
|
12
|
+
ROOT_FROGML_DATASET_UI_DIRECTORY,
|
13
|
+
ROOT_FROGML_MODEL_UI_DIRECTORY,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
# noinspection PyEnum
|
18
|
+
class FrogMLEntityTypeInfo(Enum):
|
19
|
+
MODEL = (
|
20
|
+
MODEL_UI_DIRECTORY,
|
21
|
+
ROOT_FROGML_MODEL_UI_DIRECTORY,
|
22
|
+
MODEL_METADATA_FILE_NAME,
|
23
|
+
BODY_PART_MODEL_MANIFEST_STREAM,
|
24
|
+
)
|
25
|
+
DATASET = (
|
26
|
+
DATASET_UI_DIRECTORY,
|
27
|
+
ROOT_FROGML_DATASET_UI_DIRECTORY,
|
28
|
+
DATASET_METADATA_FILE_NAME,
|
29
|
+
BODY_PART_DATASET_MANIFEST_STREAM,
|
30
|
+
)
|
31
|
+
|
32
|
+
def __init__(
|
33
|
+
self: Self,
|
34
|
+
entity_type: str,
|
35
|
+
folder_name: str,
|
36
|
+
metadata_file_name: str,
|
37
|
+
body_part_stream: str,
|
38
|
+
):
|
39
|
+
self.entity_type: str = entity_type
|
40
|
+
self.folder_name: str = folder_name
|
41
|
+
self.metadata_file_name: str = metadata_file_name
|
42
|
+
self.body_part_stream: str = body_part_stream
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def from_string(cls, entity_type_string: str) -> "FrogMLEntityTypeInfo":
|
46
|
+
for entity_type in cls:
|
47
|
+
if entity_type.entity_type.lower() == entity_type_string.lower():
|
48
|
+
return entity_type
|
49
|
+
|
50
|
+
raise ValueError(f"No enum constant found for entityType: {entity_type_string}")
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from frogml_storage.models.entity_manifest import EntityManifest
|
6
|
+
|
7
|
+
|
8
|
+
class FrogMLEntityVersion(BaseModel):
|
9
|
+
"""
|
10
|
+
Represent a metadata of uploaded entity
|
11
|
+
|
12
|
+
Attributes:
|
13
|
+
entity_name: The entity name (model | dataset)
|
14
|
+
namespace: The namespace of the model | dataset
|
15
|
+
version: The version of the model | dataset
|
16
|
+
entity_manifest: The entity manifest file
|
17
|
+
"""
|
18
|
+
|
19
|
+
entity_name: str
|
20
|
+
namespace: Optional[str] = None
|
21
|
+
version: str
|
22
|
+
entity_manifest: Optional[EntityManifest] = None
|
23
|
+
|
24
|
+
def __eq__(self, other):
|
25
|
+
if not isinstance(other, FrogMLEntityVersion):
|
26
|
+
return False
|
27
|
+
if self.version != other.version:
|
28
|
+
return False
|
29
|
+
if self.namespace != other.namespace:
|
30
|
+
return False
|
31
|
+
if self.entity_name != other.entity_name:
|
32
|
+
return False
|
33
|
+
|
34
|
+
return True
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from frogml_storage.models.frogml_entity_version import FrogMLEntityVersion
|
2
|
+
|
3
|
+
|
4
|
+
class FrogMLModelVersion(FrogMLEntityVersion):
|
5
|
+
"""
|
6
|
+
Represent metadata of an uploaded model version.
|
7
|
+
|
8
|
+
Inherits:
|
9
|
+
FrogMLEntityVersion: Base class for entity versions.
|
10
|
+
"""
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
def from_entity_version(
|
14
|
+
cls, entity_version: FrogMLEntityVersion
|
15
|
+
) -> "FrogMLModelVersion":
|
16
|
+
return cls(
|
17
|
+
entity_name=entity_version.entity_name,
|
18
|
+
version=entity_version.version,
|
19
|
+
namespace=entity_version.namespace,
|
20
|
+
entity_manifest=entity_version.entity_manifest,
|
21
|
+
)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import os
|
2
|
+
from typing import List, Optional
|
3
|
+
|
4
|
+
from pydantic import ConfigDict, Field
|
5
|
+
|
6
|
+
from frogml_storage.models.entity_manifest import Artifact, Checksums, EntityManifest
|
7
|
+
from frogml_storage.models.serialization_metadata import SerializationMetadata
|
8
|
+
|
9
|
+
|
10
|
+
class ModelManifest(EntityManifest):
|
11
|
+
"""
|
12
|
+
Represent a model manifest file
|
13
|
+
|
14
|
+
Attributes:
|
15
|
+
model_format: If the entity is model, holds model format information
|
16
|
+
dependency_artifacts: If the entity is model, holds a list of files specifying the model dependencies
|
17
|
+
code_artifacts: If the entity is model, specifies the archive file artifact
|
18
|
+
"""
|
19
|
+
|
20
|
+
artifacts: List[Artifact] = Field(serialization_alias="model_artifacts")
|
21
|
+
|
22
|
+
model_format: SerializationMetadata
|
23
|
+
dependency_artifacts: Optional[List[Artifact]] = None
|
24
|
+
code_artifacts: Optional[Artifact] = None
|
25
|
+
|
26
|
+
# suppress warning on model_format field name.
|
27
|
+
# if one day it collides with pydantic field, it will throw an error.
|
28
|
+
model_config = ConfigDict(protected_namespaces=())
|
29
|
+
|
30
|
+
def add_dependency_file(
|
31
|
+
self, file_path: str, checksums: Checksums, rel_path: str
|
32
|
+
) -> None:
|
33
|
+
if self.dependency_artifacts is None:
|
34
|
+
self.dependency_artifacts = []
|
35
|
+
self.dependency_artifacts.append(
|
36
|
+
Artifact(
|
37
|
+
artifact_path=rel_path,
|
38
|
+
size=os.path.getsize(file_path),
|
39
|
+
checksums=checksums,
|
40
|
+
)
|
41
|
+
)
|
42
|
+
|
43
|
+
def __eq__(self, other):
|
44
|
+
if not super.__eq__(self, other):
|
45
|
+
return False
|
46
|
+
if self.model_format != other.model_format:
|
47
|
+
return False
|
48
|
+
if self.dependency_artifacts != other.dependency_artifacts:
|
49
|
+
return False
|
50
|
+
if self.dependency_artifacts is not None:
|
51
|
+
if len(self.dependency_artifacts) != len(other.dependency_artifacts):
|
52
|
+
return False
|
53
|
+
for self_artifact, other_artifact in zip(
|
54
|
+
self.dependency_artifacts, other.dependency_artifacts
|
55
|
+
):
|
56
|
+
if self_artifact != other_artifact:
|
57
|
+
return False
|
58
|
+
if self.code_artifacts != other.code_artifacts:
|
59
|
+
return False
|
60
|
+
return True
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class SerializationMetadata(BaseModel):
|
7
|
+
framework: str
|
8
|
+
framework_version: str
|
9
|
+
serialization_format: str
|
10
|
+
runtime: str
|
11
|
+
runtime_version: str
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def from_json(cls, json_dict: Dict) -> "SerializationMetadata":
|
15
|
+
return cls.model_validate(json_dict)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from ._input_checks_utility import (
|
2
|
+
is_not_none,
|
3
|
+
is_valid_thread_number,
|
4
|
+
user_input_validation,
|
5
|
+
validate_not_folder_paths,
|
6
|
+
validate_path_exists,
|
7
|
+
)
|
8
|
+
from ._storage_utils import calculate_sha2, calc_content_sha2
|
9
|
+
from ._url_utils import (
|
10
|
+
assemble_artifact_url,
|
11
|
+
join_url,
|
12
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import importlib.metadata
|
2
|
+
import platform
|
3
|
+
from typing import List
|
4
|
+
|
5
|
+
|
6
|
+
def get_environment_dependencies() -> List[str]:
|
7
|
+
distributions = importlib.metadata.distributions()
|
8
|
+
return sorted(
|
9
|
+
[f"{dist.metadata['Name']}=={dist.version}" for dist in distributions]
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
def get_environment_details() -> List[str]:
|
14
|
+
return [
|
15
|
+
f"arch={platform.architecture()[0]}",
|
16
|
+
f"cpu={platform.processor()}",
|
17
|
+
f"platform={platform.platform()}",
|
18
|
+
f"python_version={platform.python_version()}",
|
19
|
+
f"python_implementation={platform.python_implementation()}",
|
20
|
+
f"python_compiler={platform.python_compiler()}",
|
21
|
+
]
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
from typing import List, Optional, Union
|
4
|
+
|
5
|
+
from frogml_storage.logging import logger
|
6
|
+
from frogml_storage.constants import FROG_ML_MAX_CHARS_FOR_NAME
|
7
|
+
from frogml_storage.exceptions.validation_error import FrogMLValidationError
|
8
|
+
|
9
|
+
valid_characters_pattern = re.compile(r"^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$")
|
10
|
+
|
11
|
+
|
12
|
+
def user_input_validation(
|
13
|
+
entity_name: Optional[str],
|
14
|
+
namespace: Optional[str],
|
15
|
+
version: Optional[str],
|
16
|
+
properties: Optional[dict[str, str]] = None,
|
17
|
+
) -> bool:
|
18
|
+
for arg_name, arg_value in {
|
19
|
+
"entity_name": entity_name,
|
20
|
+
"namespace": namespace,
|
21
|
+
"version": version,
|
22
|
+
}.items():
|
23
|
+
if arg_value is not None:
|
24
|
+
__input_validation(arg_value, arg_name)
|
25
|
+
|
26
|
+
if properties is not None:
|
27
|
+
__user_input_dict_validation(properties)
|
28
|
+
|
29
|
+
return True
|
30
|
+
|
31
|
+
|
32
|
+
def __user_input_dict_validation(properties: dict[str, str]) -> bool:
|
33
|
+
if properties is not None:
|
34
|
+
for key, value in properties.items():
|
35
|
+
__input_validation(key, "properties")
|
36
|
+
__input_validation(value, "properties")
|
37
|
+
return True
|
38
|
+
|
39
|
+
|
40
|
+
def is_not_none(arg_name: str, arg_value: Optional[object]) -> bool:
|
41
|
+
if arg_value is None:
|
42
|
+
raise FrogMLValidationError("{} can't be 'None'.".format(arg_name))
|
43
|
+
return True
|
44
|
+
|
45
|
+
|
46
|
+
def __input_validation(field_value: str, field_name: str) -> bool:
|
47
|
+
if len(str(field_value)) > FROG_ML_MAX_CHARS_FOR_NAME:
|
48
|
+
raise FrogMLValidationError(
|
49
|
+
"Max length for {} is 60 characters.".format(field_name.capitalize())
|
50
|
+
)
|
51
|
+
|
52
|
+
if not field_value or not re.match(valid_characters_pattern, str(field_value)):
|
53
|
+
raise FrogMLValidationError(
|
54
|
+
"Invalid characters detected at {}: {}".format(
|
55
|
+
field_name.capitalize(), field_value
|
56
|
+
)
|
57
|
+
)
|
58
|
+
|
59
|
+
return True
|
60
|
+
|
61
|
+
|
62
|
+
def is_valid_thread_number(thread_count: str) -> bool:
|
63
|
+
try:
|
64
|
+
int_thread_count = int(thread_count)
|
65
|
+
cpu_count = os.cpu_count()
|
66
|
+
if int_thread_count <= 0 or (
|
67
|
+
cpu_count is not None and int_thread_count >= cpu_count
|
68
|
+
):
|
69
|
+
raise ValueError(
|
70
|
+
"Invalid thread count: {}. The default value will be used.".format(
|
71
|
+
thread_count
|
72
|
+
)
|
73
|
+
)
|
74
|
+
return True
|
75
|
+
except ValueError as e:
|
76
|
+
logger.warning("Thread count {}: {}".format(thread_count, e))
|
77
|
+
return False
|
78
|
+
except TypeError:
|
79
|
+
logger.debug("Thread count not configured. The default value will be used.")
|
80
|
+
return False
|
81
|
+
|
82
|
+
|
83
|
+
def validate_not_folder_paths(paths: Union[Optional[List[str]], Optional[str]]) -> bool:
|
84
|
+
if paths is not None:
|
85
|
+
if isinstance(paths, List):
|
86
|
+
for path in paths:
|
87
|
+
__validate_not_folder_path(path)
|
88
|
+
else:
|
89
|
+
__validate_not_folder_path(paths)
|
90
|
+
return True
|
91
|
+
|
92
|
+
|
93
|
+
def __validate_not_folder_path(path: str) -> bool:
|
94
|
+
if os.path.isdir(path):
|
95
|
+
raise FrogMLValidationError(
|
96
|
+
"file '{}' must be a file, but is a directory.".format(path)
|
97
|
+
)
|
98
|
+
return True
|
99
|
+
|
100
|
+
|
101
|
+
def validate_path_exists(path: str) -> bool:
|
102
|
+
if not os.path.exists(path):
|
103
|
+
raise ValueError(f"Provided path does not exists : '{path}'")
|
104
|
+
return True
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import hashlib
|
2
|
+
|
3
|
+
|
4
|
+
def calculate_sha2(path: str) -> str:
|
5
|
+
sha256 = hashlib.sha256()
|
6
|
+
with open(path, "rb") as f:
|
7
|
+
while chunk := f.read(8192):
|
8
|
+
sha256.update(chunk)
|
9
|
+
return sha256.hexdigest()
|
10
|
+
|
11
|
+
|
12
|
+
def calc_content_sha2(content: str) -> str:
|
13
|
+
sha256 = hashlib.sha256()
|
14
|
+
sha256.update(content.encode("utf-8"))
|
15
|
+
return sha256.hexdigest()
|