qwak-core 0.4.265__py3-none-any.whl → 0.4.266__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.
Files changed (52) hide show
  1. frogml_storage/__init__.py +0 -0
  2. frogml_storage/_artifactory_api.py +315 -0
  3. frogml_storage/_environment.py +22 -0
  4. frogml_storage/_log_config.py +45 -0
  5. frogml_storage/_storage_utils.py +15 -0
  6. frogml_storage/_utils.py +69 -0
  7. frogml_storage/authentication/_authentication_utils.py +259 -0
  8. frogml_storage/authentication/models/_auth_config.py +70 -0
  9. frogml_storage/cli/_frogml_cli.py +40 -0
  10. frogml_storage/cli/_login_cli.py +240 -0
  11. frogml_storage/cli/commands/_login_command.py +74 -0
  12. frogml_storage/cli/models/_cli_login_arguments.py +22 -0
  13. frogml_storage/cli/utils/_cli_utils.py +19 -0
  14. frogml_storage/cli/utils/_login_checks_utility.py +114 -0
  15. frogml_storage/constants.py +56 -0
  16. frogml_storage/dataset_manifest.py +13 -0
  17. frogml_storage/entity_manifest.py +93 -0
  18. frogml_storage/exceptions/checksum_verification_error.py +3 -0
  19. frogml_storage/exceptions/validation_error.py +4 -0
  20. frogml_storage/frog_ml.py +668 -0
  21. frogml_storage/frogml_entity_type_info.py +46 -0
  22. frogml_storage/http/__init__.py +0 -0
  23. frogml_storage/http/http_client.py +83 -0
  24. frogml_storage/model_manifest.py +60 -0
  25. frogml_storage/models/_download_context.py +54 -0
  26. frogml_storage/models/frogml_dataset_version.py +21 -0
  27. frogml_storage/models/frogml_entity_version.py +34 -0
  28. frogml_storage/models/frogml_model_version.py +21 -0
  29. frogml_storage/serialization_metadata.py +15 -0
  30. frogml_storage/storage.py +140 -0
  31. frogml_storage/utils/_input_checks_utility.py +104 -0
  32. qwak/__init__.py +1 -1
  33. qwak/clients/instance_template/client.py +6 -4
  34. qwak/clients/prompt_manager/model_descriptor_mapper.py +21 -19
  35. qwak/feature_store/_common/artifact_utils.py +3 -3
  36. qwak/feature_store/data_sources/base.py +4 -4
  37. qwak/feature_store/data_sources/batch/athena.py +3 -3
  38. qwak/feature_store/feature_sets/streaming.py +3 -3
  39. qwak/feature_store/feature_sets/streaming_backfill.py +1 -1
  40. qwak/feature_store/online/client.py +6 -6
  41. qwak/feature_store/sinks/streaming/factory.py +1 -1
  42. qwak/inner/build_logic/phases/phase_010_fetch_model/fetch_strategy_manager/strategy/git/git_strategy.py +3 -3
  43. qwak/llmops/provider/openai/provider.py +3 -3
  44. qwak/model/tools/adapters/output.py +1 -1
  45. qwak/model/utils/feature_utils.py +12 -8
  46. qwak/model_loggers/artifact_logger.py +7 -7
  47. qwak/tools/logger/logger.py +1 -1
  48. qwak_core-0.4.266.dist-info/METADATA +419 -0
  49. {qwak_core-0.4.265.dist-info → qwak_core-0.4.266.dist-info}/RECORD +51 -19
  50. qwak_core-0.4.266.dist-info/entry_points.txt +3 -0
  51. qwak_core-0.4.265.dist-info/METADATA +0 -53
  52. {qwak_core-0.4.265.dist-info → qwak_core-0.4.266.dist-info}/WHEEL +0 -0
File without changes
@@ -0,0 +1,315 @@
1
+ import json
2
+ import os
3
+ from typing import Optional
4
+
5
+ from requests import Response
6
+ from tqdm.auto import tqdm
7
+ from tqdm.utils import CallbackIOWrapper
8
+ from urllib3 import Retry
9
+
10
+ from frogml_storage._log_config import logger
11
+ from frogml_storage._utils import join_url
12
+ from frogml_storage.constants import CHECKSUM_SHA2_HEADER
13
+ from frogml_storage.entity_manifest import Checksums, EntityManifest
14
+ from frogml_storage.frogml_entity_type_info import FrogMLEntityTypeInfo
15
+ from frogml_storage.http.http_client import HTTPClient
16
+ from frogml_storage.models._download_context import DownloadContext
17
+
18
+
19
+ class StartTransactionResponse:
20
+ files_upload_path: str
21
+ lead_upload_path: str
22
+ dependencies_upload_path: str
23
+ code_upload_path: str
24
+ transaction_id: str
25
+
26
+ def __init__(
27
+ self,
28
+ files_upload_path,
29
+ lead_upload_path,
30
+ dependencies_upload_path,
31
+ code_upload_path,
32
+ transaction_id,
33
+ ):
34
+ self.files_upload_path = files_upload_path
35
+ self.lead_upload_path = lead_upload_path
36
+ self.dependencies_upload_path = dependencies_upload_path
37
+ self.code_upload_path = code_upload_path
38
+ self.transaction_id = transaction_id
39
+
40
+
41
+ class ArtifactoryApi:
42
+ def __init__(self, uri, auth=None, http_client=None):
43
+ self.uri = uri
44
+ if http_client is not None:
45
+ self.http_client = http_client
46
+ else:
47
+ self.auth = auth
48
+ self.http_client = HTTPClient(auth=auth)
49
+
50
+ def start_transaction(
51
+ self,
52
+ entity_type_info: FrogMLEntityTypeInfo,
53
+ repository: str,
54
+ entity_name: str,
55
+ version: Optional[str],
56
+ ) -> StartTransactionResponse:
57
+ """
58
+ Initializes an upload. Returns transaction ID and upload path
59
+ """
60
+ if version is None:
61
+ start_transaction_url = (
62
+ f"{self.uri}/api/machinelearning/{repository}/"
63
+ f"{entity_type_info.entity_type}/{entity_name}/start-transaction"
64
+ )
65
+ else:
66
+ start_transaction_url = (
67
+ f"{self.uri}/api/machinelearning/{repository}/{entity_type_info.entity_type}"
68
+ f"/{entity_name}/start-transaction/{version}"
69
+ )
70
+ try:
71
+ response = self.http_client.post(start_transaction_url)
72
+ response.raise_for_status()
73
+ files_upload_path = response.json()["filesUploadPath"]
74
+ lead_upload_path = response.json()["leadUploadPath"]
75
+ dependencies_upload_path = response.json()["dependenciesUploadPath"]
76
+ code_upload_path = response.json()["codeUploadPath"]
77
+ transaction_id = response.json()["transactionId"]
78
+ except Exception as exception:
79
+ err = (
80
+ f"Error occurred while trying to start an upload transaction for "
81
+ f"{entity_type_info.entity_type}: '{entity_name}'"
82
+ f" Error: '{exception}'"
83
+ )
84
+ logger.error(err, exc_info=False)
85
+ raise exception
86
+ return StartTransactionResponse(
87
+ files_upload_path=files_upload_path,
88
+ lead_upload_path=lead_upload_path,
89
+ dependencies_upload_path=dependencies_upload_path,
90
+ code_upload_path=code_upload_path,
91
+ transaction_id=transaction_id,
92
+ )
93
+
94
+ def end_transaction(
95
+ self,
96
+ entity_type: FrogMLEntityTypeInfo,
97
+ repository: str,
98
+ entity_name: str,
99
+ entity_manifest: EntityManifest,
100
+ transaction_id: str,
101
+ version: str,
102
+ properties: Optional[dict[str, str]],
103
+ ) -> None:
104
+ """
105
+ Upload model-manifest.json | dataset-manifest.json file, makes the model | dataset available in the repository
106
+ """
107
+ filename = entity_type.metadata_file_name
108
+
109
+ url = join_url(
110
+ self.uri,
111
+ "api",
112
+ "machinelearning",
113
+ repository,
114
+ entity_type.entity_type,
115
+ "entity-manifest",
116
+ entity_name,
117
+ version,
118
+ transaction_id,
119
+ )
120
+
121
+ json_entity_manifest = entity_manifest.to_json()
122
+ self.upload_entity_manifest(
123
+ entity_type=entity_type,
124
+ filename=filename,
125
+ payload=json_entity_manifest,
126
+ url=url,
127
+ properties=properties,
128
+ )
129
+
130
+ def download_file(self, args: DownloadContext) -> None:
131
+ filename = os.path.basename(args.target_path)
132
+ try:
133
+ url = f"{self.uri}/{args.repo_key}/{args.source_url}"
134
+ with self.http_client.get(url=url, stream=True) as response:
135
+ response.raise_for_status()
136
+ total_size = int(response.headers.get("content-length", 0))
137
+ with open(args.target_path, "wb") as file:
138
+ with self.__initialize_progress_bar(total_size, filename) as pbar:
139
+ for chunk in response.iter_content(chunk_size=8192):
140
+ if chunk:
141
+ file.write(chunk)
142
+ pbar.update(len(chunk))
143
+
144
+ except Exception as exception:
145
+ self.__handle_download_exception(exception, args.target_path, filename)
146
+
147
+ def get_entity_manifest(
148
+ self,
149
+ entity_type_info: FrogMLEntityTypeInfo,
150
+ repository: str,
151
+ entity_name: str,
152
+ namespace: Optional[str],
153
+ version: Optional[str],
154
+ ) -> dict:
155
+ url = join_url(
156
+ self.uri,
157
+ "api",
158
+ "machinelearning",
159
+ repository,
160
+ entity_type_info.entity_type,
161
+ "entity-manifest",
162
+ namespace,
163
+ entity_name,
164
+ version,
165
+ )
166
+ try:
167
+ with self.http_client.get(url=url) as r:
168
+ r.raise_for_status()
169
+ return r.json()
170
+ except Exception as exception:
171
+ err = f"Error occurred while trying to get {entity_type_info.entity_type} info file. Error: '{exception}'"
172
+ logger.error(err, exc_info=False)
173
+ raise exception
174
+
175
+ @staticmethod
176
+ def __handle_download_exception(
177
+ exception: Exception, target_path: str, filename: str
178
+ ) -> None:
179
+ if os.path.exists(target_path):
180
+ os.remove(target_path)
181
+ err = f"Error occurred while trying to download file: '{filename}' Error: '{exception}'"
182
+ logger.error(err, exc_info=False)
183
+ raise exception
184
+
185
+ def get_artifact_checksum(self, download_context: DownloadContext) -> str:
186
+ url = f"{self.uri}/{download_context.repo_key}/{download_context.source_url}"
187
+ try:
188
+ with self.http_client.head(url=url, stream=True) as response:
189
+ response.raise_for_status()
190
+ return response.headers.get(CHECKSUM_SHA2_HEADER)
191
+
192
+ except Exception as exception:
193
+ logger.error(exception.__cause__, exc_info=False)
194
+ raise exception
195
+
196
+ def upload_entity_manifest(
197
+ self,
198
+ entity_type: FrogMLEntityTypeInfo,
199
+ filename: str,
200
+ payload: str,
201
+ url: str,
202
+ properties: Optional[dict[str, str]],
203
+ stream: bool = False,
204
+ ) -> None:
205
+ body_part_name = f"{entity_type.body_part_stream}"
206
+
207
+ try:
208
+ files = {
209
+ f"{body_part_name}": (
210
+ f"{body_part_name}",
211
+ payload,
212
+ "application/octet-stream",
213
+ ), # Include the InputStream
214
+ "additionalData": (
215
+ "additionalData",
216
+ json.dumps(properties),
217
+ "application/octet-stream",
218
+ ), # Include the object
219
+ }
220
+ with self.http_client.put(url=url, files=files, stream=stream) as response:
221
+ response.raise_for_status()
222
+ except Exception as exception:
223
+ err = f"Error occurred while trying to upload file: '{filename}' Error: '{exception}'"
224
+ logger.error(err, exc_info=False)
225
+ raise exception
226
+
227
+ def upload_file(self, file_path: str, url: str) -> None:
228
+ wrapped_file = None
229
+ try:
230
+ file_size = os.stat(file_path).st_size
231
+ with (
232
+ self.__initialize_progress_bar(file_size, file_path) as pbar,
233
+ open(file_path, "rb") as file,
234
+ ):
235
+ wrapped_file = CallbackIOWrapper(pbar.update, file, "read")
236
+ with self.http_client.put(url=url, payload=wrapped_file) as response:
237
+ response.raise_for_status()
238
+ except Exception as exception:
239
+ err = f"Error occurred while trying to upload file: '{file_path}' Error: '{exception}'"
240
+ logger.error(err, exc_info=False)
241
+ raise type(exception)(f"{err} File: {file_path}") from exception
242
+ finally:
243
+ if wrapped_file is not None:
244
+ wrapped_file.close()
245
+
246
+ def checksum_deployment(
247
+ self,
248
+ checksum: Checksums,
249
+ url: str,
250
+ full_path: str,
251
+ stream: bool = False,
252
+ ) -> bool:
253
+ response = self.http_client.put(
254
+ url=url,
255
+ headers={"X-Checksum-Sha256": checksum.sha2, "X-Checksum-Deploy": "true"},
256
+ stream=stream,
257
+ )
258
+ if response.status_code != 200 and response.status_code != 201:
259
+ return False
260
+ else:
261
+ file_size = os.path.getsize(full_path)
262
+ pbar = self.__initialize_progress_bar(file_size, full_path)
263
+ pbar.update(file_size)
264
+ pbar.close()
265
+ return True
266
+
267
+ @staticmethod
268
+ def __initialize_progress_bar(total_size: int, filename: str) -> tqdm:
269
+ return tqdm(
270
+ total=total_size, unit="B", unit_scale=True, desc=filename, initial=0
271
+ )
272
+
273
+ def encrypt_password(self) -> Response:
274
+ """
275
+ returns encrypted password as text
276
+ """
277
+ return self.http_client.get(
278
+ url=join_url(self.uri, "/api/security/encryptedPassword")
279
+ )
280
+
281
+ def ping(self) -> Response:
282
+ """
283
+ Sends a ping to Artifactory to validate login status
284
+ """
285
+ url = join_url(self.uri, "api/system/ping")
286
+ return self.http_client.get(url=url)
287
+
288
+ def get_artifactory_version(self) -> Response:
289
+ return self.http_client.get(url=join_url(self.uri, "/api/system/version"))
290
+
291
+ def create_machinelearning_local_repo(self, repo_name: str) -> Response:
292
+ data = {
293
+ "rclass": "local",
294
+ "packageType": "machinelearning",
295
+ }
296
+ return self.http_client.put(
297
+ url=join_url(self.uri, "/api/repositories/" + repo_name), json=data
298
+ )
299
+
300
+ def delete_frogml_local_repo(self, repo_name: str) -> Response:
301
+ return self.http_client.delete(
302
+ url=join_url(self.uri, "/api/repositories/" + repo_name)
303
+ )
304
+
305
+
306
+ class RetryWithLog(Retry):
307
+ """
308
+ Adding extra logs before making a retry request
309
+ """
310
+
311
+ def __init__(self, *args, **kwargs):
312
+ history = kwargs.get("history")
313
+ if history is not None:
314
+ logger.debug(f"Error: ${history[-1].error}\nretrying...")
315
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,22 @@
1
+ import importlib.metadata
2
+ import platform
3
+
4
+
5
+ class Recorder:
6
+ @staticmethod
7
+ def get_environment_dependencies():
8
+ distributions = importlib.metadata.distributions()
9
+ return sorted(
10
+ [f"{dist.metadata['Name']}=={dist.version}" for dist in distributions]
11
+ )
12
+
13
+ @staticmethod
14
+ def get_environment_details():
15
+ return [
16
+ f"arch={platform.architecture()[0]}",
17
+ f"cpu={platform.processor()}",
18
+ f"platform={platform.platform()}",
19
+ f"python_version={platform.python_version()}",
20
+ f"python_implementation={platform.python_implementation()}",
21
+ f"python_compiler={platform.python_compiler()}",
22
+ ]
@@ -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,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()
@@ -0,0 +1,69 @@
1
+ from typing import Optional
2
+ from urllib.parse import urlparse
3
+
4
+ from requests.auth import AuthBase
5
+
6
+ from frogml_storage.frogml_entity_type_info import FrogMLEntityTypeInfo
7
+
8
+
9
+ class BearerAuth(AuthBase):
10
+ def __init__(self, token):
11
+ self.token = token
12
+
13
+ def __call__(self, r):
14
+ r.headers["Authorization"] = f"Bearer {self.token}"
15
+ return r
16
+
17
+ def __eq__(self, other):
18
+ if not isinstance(other, BearerAuth):
19
+ return False
20
+ return self.token == other.token
21
+
22
+
23
+ class EmptyAuth(AuthBase):
24
+ def __call__(self, r):
25
+ return r
26
+
27
+
28
+ def join_url(base_uri: str, *parts: Optional[str]) -> str:
29
+ if base_uri.endswith("/"):
30
+ base_uri = base_uri[:-1]
31
+
32
+ cleaned_parts = [
33
+ part.strip("/") for part in parts if part is not None and part.strip("/")
34
+ ]
35
+ return f"{base_uri}/{'/'.join(cleaned_parts)}"
36
+
37
+
38
+ def assemble_artifact_url(uri: Optional[str]) -> str:
39
+ if uri is None:
40
+ raise Exception("Artifactory URI is required")
41
+
42
+ parsed_url = urlparse(uri)
43
+ if parsed_url.scheme not in ["http", "https"]:
44
+ raise Exception(
45
+ f"Not a valid Artifactory URI: {uri}. "
46
+ f"Artifactory URI example: `https://frogger.jfrog.io/artifactory/ml-local`"
47
+ )
48
+
49
+ return f"{parsed_url.scheme}://{parsed_url.netloc}/artifactory"
50
+
51
+
52
+ # The following method affect e2e tests.
53
+ def build_download_success_log(
54
+ entity_type_info: FrogMLEntityTypeInfo, entity_name: str, version: str
55
+ ) -> str:
56
+ return (
57
+ f'{entity_type_info.entity_type.capitalize()}: "{entity_name}", version: "{version}"'
58
+ f" has been downloaded successfully"
59
+ )
60
+
61
+
62
+ # The following method affect e2e tests.
63
+ def build_upload_success_log(
64
+ entity_type_info: FrogMLEntityTypeInfo, entity_name: str, version: str
65
+ ) -> str:
66
+ return (
67
+ f'{entity_type_info.entity_type.capitalize()}: "{entity_name}", version: "{version}"'
68
+ f" has been uploaded successfully"
69
+ )