truefoundry 0.4.0rc2__py3-none-any.whl → 0.4.0rc4__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 truefoundry might be problematic. Click here for more details.
- truefoundry/__init__.py +2 -0
- truefoundry/autodeploy/agents/developer.py +1 -1
- truefoundry/autodeploy/agents/project_identifier.py +2 -2
- truefoundry/autodeploy/agents/tester.py +1 -1
- truefoundry/autodeploy/cli.py +1 -1
- truefoundry/autodeploy/tools/list_files.py +1 -1
- truefoundry/{deploy/lib/auth → common}/auth_service_client.py +50 -40
- truefoundry/common/constants.py +12 -0
- truefoundry/{deploy/lib/auth → common}/credential_file_manager.py +7 -7
- truefoundry/{deploy/lib/auth → common}/credential_provider.py +9 -12
- truefoundry/{ml/services → common}/entities.py +59 -43
- truefoundry/common/exceptions.py +12 -0
- truefoundry/common/request_utils.py +36 -8
- truefoundry/common/servicefoundry_client.py +91 -0
- truefoundry/common/utils.py +56 -0
- truefoundry/deploy/auto_gen/models.py +4 -6
- truefoundry/deploy/cli/cli.py +2 -0
- truefoundry/deploy/cli/commands/apply_command.py +1 -1
- truefoundry/deploy/cli/commands/build_command.py +1 -1
- truefoundry/deploy/cli/commands/deploy_command.py +1 -1
- truefoundry/deploy/cli/commands/login_command.py +2 -2
- truefoundry/deploy/cli/commands/patch_application_command.py +1 -1
- truefoundry/deploy/cli/commands/patch_command.py +1 -1
- truefoundry/deploy/cli/commands/terminate_comand.py +1 -1
- truefoundry/deploy/cli/util.py +1 -1
- truefoundry/deploy/function_service/remote/remote.py +1 -1
- truefoundry/deploy/lib/auth/servicefoundry_session.py +2 -2
- truefoundry/deploy/lib/clients/servicefoundry_client.py +120 -150
- truefoundry/deploy/lib/const.py +1 -35
- truefoundry/deploy/lib/exceptions.py +0 -11
- truefoundry/deploy/lib/model/entity.py +1 -112
- truefoundry/deploy/lib/session.py +13 -26
- truefoundry/deploy/lib/util.py +0 -37
- truefoundry/deploy/python_deploy_codegen.py +2 -2
- truefoundry/deploy/v2/lib/deploy.py +3 -3
- truefoundry/ml/__init__.py +0 -2
- truefoundry/ml/artifact/truefoundry_artifact_repo.py +63 -22
- truefoundry/ml/autogen/client/__init__.py +0 -3
- truefoundry/ml/autogen/client/api/experiments_api.py +0 -165
- truefoundry/ml/autogen/client/models/__init__.py +0 -3
- truefoundry/ml/autogen/client/models/artifact_dto.py +6 -6
- truefoundry/ml/autogen/client/models/artifact_version_dto.py +8 -8
- truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +2 -3
- truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +2 -3
- truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +2 -2
- truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +2 -3
- truefoundry/ml/autogen/client/models/create_run_request_dto.py +5 -5
- truefoundry/ml/autogen/client/models/create_run_response_dto.py +2 -3
- truefoundry/ml/autogen/client/models/dataset_dto.py +10 -10
- truefoundry/ml/autogen/client/models/experiment_dto.py +18 -18
- truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +2 -3
- truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +4 -5
- truefoundry/ml/autogen/client/models/model_dto.py +6 -6
- truefoundry/ml/autogen/client/models/model_version_dto.py +15 -8
- truefoundry/ml/autogen/client/models/run_info_dto.py +10 -10
- truefoundry/ml/autogen/client/models/update_run_response_dto.py +2 -3
- truefoundry/ml/autogen/client_README.md +0 -2
- truefoundry/ml/clients/entities.py +8 -0
- truefoundry/ml/{services/servicefoundry_service.py → clients/servicefoundry_client.py} +20 -10
- truefoundry/ml/{services → clients}/utils.py +2 -2
- truefoundry/ml/env_vars.py +1 -5
- truefoundry/ml/internal_namespace.py +8 -8
- truefoundry/ml/log_types/artifacts/artifact.py +7 -3
- truefoundry/ml/log_types/artifacts/dataset.py +1 -1
- truefoundry/ml/log_types/artifacts/model.py +7 -8
- truefoundry/ml/log_types/image/image.py +7 -8
- truefoundry/ml/log_types/image/image_normalizer.py +7 -6
- truefoundry/ml/mlfoundry_api.py +5 -17
- truefoundry/ml/mlfoundry_run.py +0 -5
- truefoundry/ml/run_utils.py +1 -10
- truefoundry/ml/session.py +14 -117
- truefoundry/pydantic_v1.py +1 -1
- truefoundry/workflow/__init__.py +16 -1
- {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/METADATA +2 -2
- {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/RECORD +78 -77
- truefoundry/deploy/lib/clients/utils.py +0 -41
- truefoundry/ml/autogen/client/models/backfill_default_storage_integration_id_request_dto.py +0 -67
- truefoundry/ml/login.py +0 -241
- truefoundry/ml/services/auth_service.py +0 -109
- /truefoundry/ml/{services → clients}/__init__.py +0 -0
- {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/WHEEL +0 -0
- {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/entry_points.txt +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
|
-
import time
|
|
5
4
|
from enum import Enum
|
|
6
5
|
from typing import Any, Dict, List, Optional, Union
|
|
7
6
|
|
|
8
|
-
import jwt
|
|
9
|
-
|
|
10
7
|
from truefoundry.deploy.lib.util import get_application_fqn_from_deployment_fqn
|
|
11
|
-
from truefoundry.pydantic_v1 import BaseModel, Extra, Field
|
|
8
|
+
from truefoundry.pydantic_v1 import BaseModel, Extra, Field
|
|
12
9
|
|
|
13
10
|
# TODO: switch to Enums for str literals
|
|
14
11
|
# TODO: Need a better approach to keep fields in sync with server
|
|
@@ -215,114 +212,6 @@ class Application(Entity):
|
|
|
215
212
|
}
|
|
216
213
|
|
|
217
214
|
|
|
218
|
-
class UserType(Enum):
|
|
219
|
-
user = "user"
|
|
220
|
-
serviceaccount = "serviceaccount"
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
class UserInfo(BaseModel):
|
|
224
|
-
user_id: constr(min_length=1)
|
|
225
|
-
user_type: UserType = UserType.user
|
|
226
|
-
email: Optional[str] = None
|
|
227
|
-
tenant_name: constr(min_length=1) = Field(alias="tenantName")
|
|
228
|
-
|
|
229
|
-
class Config:
|
|
230
|
-
allow_population_by_field_name = True
|
|
231
|
-
allow_mutation = False
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class TenantInfo(BaseModel):
|
|
235
|
-
tenant_name: constr(min_length=1) = Field(alias="tenantName")
|
|
236
|
-
auth_server_url: str
|
|
237
|
-
|
|
238
|
-
class Config:
|
|
239
|
-
allow_population_by_field_name = True
|
|
240
|
-
allow_mutation = False
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class Token(BaseModel):
|
|
244
|
-
access_token: constr(min_length=1) = Field(alias="accessToken", repr=False)
|
|
245
|
-
refresh_token: Optional[constr(min_length=1)] = Field(
|
|
246
|
-
alias="refreshToken", repr=False
|
|
247
|
-
)
|
|
248
|
-
decoded_value: Optional[Dict] = Field(exclude=True, repr=False)
|
|
249
|
-
|
|
250
|
-
class Config:
|
|
251
|
-
allow_population_by_field_name = True
|
|
252
|
-
allow_mutation = False
|
|
253
|
-
|
|
254
|
-
@validator("decoded_value", always=True, pre=True)
|
|
255
|
-
def _decode_jwt(cls, v, values, **kwargs):
|
|
256
|
-
access_token = values["access_token"]
|
|
257
|
-
return jwt.decode(
|
|
258
|
-
access_token,
|
|
259
|
-
options={
|
|
260
|
-
"verify_signature": False,
|
|
261
|
-
"verify_aud": False,
|
|
262
|
-
"verify_exp": False,
|
|
263
|
-
},
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def tenant_name(self) -> str:
|
|
268
|
-
return self.decoded_value["tenantName"]
|
|
269
|
-
|
|
270
|
-
def is_going_to_be_expired(self, buffer_in_seconds: int = 120) -> bool:
|
|
271
|
-
exp = int(self.decoded_value["exp"])
|
|
272
|
-
return (exp - time.time()) < buffer_in_seconds
|
|
273
|
-
|
|
274
|
-
def to_user_info(self) -> UserInfo:
|
|
275
|
-
return UserInfo(
|
|
276
|
-
user_id=self.decoded_value["username"],
|
|
277
|
-
email=self.decoded_value["email"]
|
|
278
|
-
if "email" in self.decoded_value
|
|
279
|
-
else None,
|
|
280
|
-
user_type=UserType(self.decoded_value.get("userType", UserType.user.value)),
|
|
281
|
-
tenant_name=self.tenant_name,
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
class CredentialsFileContent(BaseModel):
|
|
286
|
-
access_token: constr(min_length=1) = Field(repr=False)
|
|
287
|
-
refresh_token: Optional[constr(min_length=1)] = Field(repr=False)
|
|
288
|
-
host: constr(min_length=1)
|
|
289
|
-
|
|
290
|
-
class Config:
|
|
291
|
-
allow_mutation = False
|
|
292
|
-
|
|
293
|
-
def to_token(self) -> Token:
|
|
294
|
-
return Token(access_token=self.access_token, refresh_token=self.refresh_token)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
class DeviceCode(BaseModel):
|
|
298
|
-
user_code: str = Field(alias="userCode")
|
|
299
|
-
device_code: str = Field(alias="deviceCode")
|
|
300
|
-
verification_url: Optional[str] = Field(alias="verificationURI")
|
|
301
|
-
complete_verification_url: Optional[str] = Field(alias="verificationURIComplete")
|
|
302
|
-
expires_in_seconds: int = Field(alias="expiresInSeconds", default=60)
|
|
303
|
-
interval_in_seconds: int = Field(alias="intervalInSeconds", default=1)
|
|
304
|
-
message: Optional[str] = Field(alias="message")
|
|
305
|
-
|
|
306
|
-
class Config:
|
|
307
|
-
allow_population_by_field_name = True
|
|
308
|
-
allow_mutation = False
|
|
309
|
-
|
|
310
|
-
def get_user_clickable_url(self, auth_host: str) -> str:
|
|
311
|
-
return f"{auth_host}/authorize/device?userCode={self.user_code}"
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
class PythonSDKConfig(BaseModel):
|
|
315
|
-
min_version: str = Field(alias="minVersion")
|
|
316
|
-
truefoundry_cli_min_version: str = Field(alias="truefoundryCliMinVersion")
|
|
317
|
-
use_sfy_server_auth_apis: Optional[bool] = Field(
|
|
318
|
-
alias="useSFYServerAuthAPIs", default=False
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
class Config:
|
|
322
|
-
allow_population_by_field_name = True
|
|
323
|
-
allow_mutation = False
|
|
324
|
-
|
|
325
|
-
|
|
326
215
|
class JobRun(Base):
|
|
327
216
|
name: str
|
|
328
217
|
applicationName: str
|
|
@@ -3,38 +3,22 @@ from typing import Optional
|
|
|
3
3
|
|
|
4
4
|
import rich_click as click
|
|
5
5
|
|
|
6
|
+
from truefoundry.common.auth_service_client import AuthServiceClient
|
|
7
|
+
from truefoundry.common.constants import TFY_API_KEY_ENV_KEY, TFY_HOST_ENV_KEY
|
|
8
|
+
from truefoundry.common.credential_file_manager import CredentialsFileManager
|
|
9
|
+
from truefoundry.common.credential_provider import EnvCredentialProvider
|
|
10
|
+
from truefoundry.common.entities import CredentialsFileContent, Token
|
|
11
|
+
from truefoundry.common.utils import resolve_base_url
|
|
6
12
|
from truefoundry.deploy.io.output_callback import OutputCallBack
|
|
7
|
-
from truefoundry.deploy.lib.auth.auth_service_client import AuthServiceClient
|
|
8
|
-
from truefoundry.deploy.lib.auth.credential_file_manager import CredentialsFileManager
|
|
9
|
-
from truefoundry.deploy.lib.auth.credential_provider import EnvCredentialProvider
|
|
10
|
-
from truefoundry.deploy.lib.clients.utils import resolve_base_url
|
|
11
13
|
from truefoundry.deploy.lib.const import (
|
|
12
|
-
API_KEY_ENV_NAME,
|
|
13
|
-
HOST_ENV_NAME,
|
|
14
|
-
OLD_SFY_PROFILES_FILEPATH,
|
|
15
|
-
OLD_SFY_SESSIONS_FILEPATH,
|
|
16
14
|
RICH_OUTPUT_CALLBACK,
|
|
17
15
|
)
|
|
18
16
|
from truefoundry.deploy.lib.messages import (
|
|
19
17
|
PROMPT_ALREADY_LOGGED_OUT,
|
|
20
18
|
PROMPT_LOGOUT_SUCCESSFUL,
|
|
21
19
|
)
|
|
22
|
-
from truefoundry.deploy.lib.model.entity import CredentialsFileContent, Token
|
|
23
20
|
from truefoundry.logger import logger
|
|
24
21
|
|
|
25
|
-
if OLD_SFY_PROFILES_FILEPATH.exists():
|
|
26
|
-
logger.warning(
|
|
27
|
-
"%s file is deprecated. You can delete this file now.",
|
|
28
|
-
OLD_SFY_PROFILES_FILEPATH,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if OLD_SFY_SESSIONS_FILEPATH.exists():
|
|
33
|
-
logger.warning(
|
|
34
|
-
"%s file is deprecated. You can delete this file now.",
|
|
35
|
-
OLD_SFY_SESSIONS_FILEPATH,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
22
|
|
|
39
23
|
def login(
|
|
40
24
|
api_key: Optional[str] = None,
|
|
@@ -42,13 +26,13 @@ def login(
|
|
|
42
26
|
relogin: bool = False,
|
|
43
27
|
output_hook: OutputCallBack = RICH_OUTPUT_CALLBACK,
|
|
44
28
|
) -> bool:
|
|
45
|
-
if
|
|
29
|
+
if TFY_API_KEY_ENV_KEY in os.environ and TFY_HOST_ENV_KEY in os.environ:
|
|
46
30
|
logger.warning(
|
|
47
31
|
"Skipping login because environment variables %s and "
|
|
48
32
|
"%s are set and will be used when running truefoundry. "
|
|
49
33
|
"If you want to relogin then unset these environment keys.",
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
TFY_HOST_ENV_KEY,
|
|
35
|
+
TFY_API_KEY_ENV_KEY,
|
|
52
36
|
)
|
|
53
37
|
return False
|
|
54
38
|
|
|
@@ -129,7 +113,10 @@ def _login_with_device_code(
|
|
|
129
113
|
if device_code.message:
|
|
130
114
|
message = device_code.message
|
|
131
115
|
else:
|
|
132
|
-
message =
|
|
116
|
+
message = (
|
|
117
|
+
f"Please open the following URL in a browser and enter the code {device_code.user_code} "
|
|
118
|
+
f"when prompted: {device_code.verification_url}"
|
|
119
|
+
)
|
|
133
120
|
else:
|
|
134
121
|
auto_open_url = device_code.get_user_clickable_url(auth_host=base_url)
|
|
135
122
|
if auto_open_url:
|
truefoundry/deploy/lib/util.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
-
from functools import lru_cache, wraps
|
|
4
|
-
from time import monotonic_ns
|
|
5
3
|
from typing import Union
|
|
6
4
|
|
|
7
5
|
from truefoundry.deploy.lib.const import (
|
|
@@ -41,23 +39,6 @@ def get_deployment_fqn_from_application_fqn(
|
|
|
41
39
|
return f"{application_fqn}:{version}"
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def is_notebook():
|
|
45
|
-
# https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook
|
|
46
|
-
try:
|
|
47
|
-
# noinspection PyUnresolvedReferences
|
|
48
|
-
shell = get_ipython().__class__
|
|
49
|
-
if shell.__name__ == "ZMQInteractiveShell":
|
|
50
|
-
return True # Jupyter notebook or qtconsole
|
|
51
|
-
elif shell.__name__ == "TerminalInteractiveShell":
|
|
52
|
-
return False # Terminal running IPython
|
|
53
|
-
elif "google.colab" in str(shell):
|
|
54
|
-
return True # google colab notebook
|
|
55
|
-
else:
|
|
56
|
-
return False # Other type (?)
|
|
57
|
-
except NameError:
|
|
58
|
-
return False # Probably standard Python interpreter
|
|
59
|
-
|
|
60
|
-
|
|
61
42
|
def find_list_paths(data, parent_key="", sep="."):
|
|
62
43
|
list_paths = []
|
|
63
44
|
if isinstance(data, dict):
|
|
@@ -70,21 +51,3 @@ def find_list_paths(data, parent_key="", sep="."):
|
|
|
70
51
|
new_key = f"{parent_key}[{i}]"
|
|
71
52
|
list_paths.extend(find_list_paths(value, new_key, sep))
|
|
72
53
|
return list_paths
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def timed_lru_cache(seconds: int = 300, maxsize: int = None):
|
|
76
|
-
def wrapper_cache(func):
|
|
77
|
-
func = lru_cache(maxsize=maxsize)(func)
|
|
78
|
-
func.delta = seconds * 10**9
|
|
79
|
-
func.expiration = monotonic_ns() + func.delta
|
|
80
|
-
|
|
81
|
-
@wraps(func)
|
|
82
|
-
def wrapped_func(*args, **kwargs):
|
|
83
|
-
if monotonic_ns() >= func.expiration:
|
|
84
|
-
func.cache_clear()
|
|
85
|
-
func.expiration = monotonic_ns() + func.delta
|
|
86
|
-
return func(*args, **kwargs)
|
|
87
|
-
|
|
88
|
-
return wrapped_func
|
|
89
|
-
|
|
90
|
-
return wrapper_cache
|
|
@@ -101,11 +101,11 @@ def add_local_source_comment(code):
|
|
|
101
101
|
return "\n".join(new_lines)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
def convert_deployment_config_to_python(workspace_fqn: str,
|
|
104
|
+
def convert_deployment_config_to_python(workspace_fqn: str, application_spec: dict):
|
|
105
105
|
"""
|
|
106
106
|
Convert a deployment config to a python file that can be used to deploy to a workspace
|
|
107
107
|
"""
|
|
108
|
-
application = Application.parse_obj(
|
|
108
|
+
application = Application.parse_obj(application_spec)
|
|
109
109
|
application_type = application.__root__.type
|
|
110
110
|
|
|
111
111
|
spec_repr = get_python_repr(application.__root__)
|
|
@@ -4,12 +4,12 @@ from typing import List, Optional, TypeVar
|
|
|
4
4
|
|
|
5
5
|
from rich.status import Status
|
|
6
6
|
|
|
7
|
+
from truefoundry.common.utils import poll_for_function
|
|
7
8
|
from truefoundry.deploy.auto_gen import models as auto_gen_models
|
|
8
9
|
from truefoundry.deploy.builder.docker_service import env_has_docker
|
|
9
10
|
from truefoundry.deploy.lib.clients.servicefoundry_client import (
|
|
10
11
|
ServiceFoundryServiceClient,
|
|
11
12
|
)
|
|
12
|
-
from truefoundry.deploy.lib.clients.utils import poll_for_function
|
|
13
13
|
from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
|
|
14
14
|
from truefoundry.deploy.lib.model.entity import Deployment, DeploymentTransitionStatus
|
|
15
15
|
from truefoundry.deploy.lib.util import get_application_fqn_from_deployment_fqn
|
|
@@ -45,7 +45,7 @@ def _handle_if_local_source(component: Component, workspace_fqn: str) -> Compone
|
|
|
45
45
|
local_build = False
|
|
46
46
|
else:
|
|
47
47
|
logger.info(
|
|
48
|
-
"Found locally installed docker, image will be built locally and then pushed
|
|
48
|
+
"Found locally installed docker, image will be built locally and then pushed.\n"
|
|
49
49
|
"If you want to always build remotely instead of locally, "
|
|
50
50
|
"please set `image.build_source.local_build` to `false` in your YAML spec or equivalently set "
|
|
51
51
|
"`image=Build(build_source=LocalSource(local_build=False, ...))` in your "
|
|
@@ -72,7 +72,7 @@ def _handle_if_local_source(component: Component, workspace_fqn: str) -> Compone
|
|
|
72
72
|
component_name=component.name,
|
|
73
73
|
)
|
|
74
74
|
else:
|
|
75
|
-
# We'll build image on
|
|
75
|
+
# We'll build image on TrueFoundry servers, upload the source and update image.build_source
|
|
76
76
|
logger.info("Uploading code for %s '%s'", component.type, component.name)
|
|
77
77
|
new_component.image.build_source = local_source_to_remote_source(
|
|
78
78
|
local_source=component.image.build_source,
|
truefoundry/ml/__init__.py
CHANGED
|
@@ -13,7 +13,6 @@ from truefoundry.ml.log_types.artifacts.model import (
|
|
|
13
13
|
ModelVersion,
|
|
14
14
|
)
|
|
15
15
|
from truefoundry.ml.logger import init_logger
|
|
16
|
-
from truefoundry.ml.login import login
|
|
17
16
|
from truefoundry.ml.mlfoundry_api import get_client
|
|
18
17
|
from truefoundry.ml.mlfoundry_run import MlFoundryRun
|
|
19
18
|
|
|
@@ -33,7 +32,6 @@ __all__ = [
|
|
|
33
32
|
"Plot",
|
|
34
33
|
"ViewType",
|
|
35
34
|
"get_client",
|
|
36
|
-
"login",
|
|
37
35
|
]
|
|
38
36
|
|
|
39
37
|
init_logger()
|
|
@@ -23,6 +23,7 @@ from urllib.parse import unquote
|
|
|
23
23
|
from urllib.request import pathname2url
|
|
24
24
|
|
|
25
25
|
import requests
|
|
26
|
+
from rich.console import _is_jupyter
|
|
26
27
|
from rich.progress import (
|
|
27
28
|
BarColumn,
|
|
28
29
|
DownloadColumn,
|
|
@@ -53,13 +54,13 @@ from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
|
|
|
53
54
|
RunArtifactsApi,
|
|
54
55
|
SignedURLDto,
|
|
55
56
|
)
|
|
56
|
-
from truefoundry.ml.
|
|
57
|
-
from truefoundry.ml.exceptions import MlFoundryException
|
|
58
|
-
from truefoundry.ml.logger import logger
|
|
59
|
-
from truefoundry.ml.services.utils import (
|
|
57
|
+
from truefoundry.ml.clients.utils import (
|
|
60
58
|
augmented_raise_for_status,
|
|
61
59
|
cloud_storage_http_request,
|
|
62
60
|
)
|
|
61
|
+
from truefoundry.ml.env_vars import DISABLE_MULTIPART_UPLOAD
|
|
62
|
+
from truefoundry.ml.exceptions import MlFoundryException
|
|
63
|
+
from truefoundry.ml.logger import logger
|
|
63
64
|
from truefoundry.ml.session import _get_api_client
|
|
64
65
|
from truefoundry.pydantic_v1 import BaseModel, root_validator
|
|
65
66
|
|
|
@@ -91,6 +92,39 @@ _GENERATE_SIGNED_URL_BATCH_SIZE = 50
|
|
|
91
92
|
DEFAULT_PRESIGNED_URL_EXPIRY_TIME = 3600
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
def _get_relpath_if_in_tempdir(path: str) -> str:
|
|
96
|
+
tempdir = tempfile.gettempdir()
|
|
97
|
+
if path.startswith(tempdir):
|
|
98
|
+
return os.path.relpath(path, tempdir)
|
|
99
|
+
return path
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _can_display_progress(user_choice: Optional[bool] = None) -> bool:
|
|
103
|
+
if user_choice is False:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
if sys.stdout.isatty():
|
|
107
|
+
return True
|
|
108
|
+
elif _is_jupyter():
|
|
109
|
+
try:
|
|
110
|
+
from IPython.display import display # noqa: F401
|
|
111
|
+
from ipywidgets import Output # noqa: F401
|
|
112
|
+
|
|
113
|
+
return True
|
|
114
|
+
except ImportError:
|
|
115
|
+
logger.warning(
|
|
116
|
+
"Detected Jupyter Environment. Install `ipywidgets` to display live progress bars.",
|
|
117
|
+
)
|
|
118
|
+
if user_choice is True:
|
|
119
|
+
logger.warning(
|
|
120
|
+
"`progress` argument is set to True but did not detect tty "
|
|
121
|
+
"or jupyter environment with ipywidgets installed. "
|
|
122
|
+
"Progress bars may not be displayed. "
|
|
123
|
+
)
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
94
128
|
def relative_path_to_artifact_path(path):
|
|
95
129
|
if os.path == posixpath:
|
|
96
130
|
return path
|
|
@@ -206,7 +240,7 @@ def _signed_url_upload_file(
|
|
|
206
240
|
return
|
|
207
241
|
|
|
208
242
|
task_progress_bar = progress_bar.add_task(
|
|
209
|
-
f"[green]Uploading {local_file}:", start=True
|
|
243
|
+
f"[green]Uploading {_get_relpath_if_in_tempdir(local_file)}:", start=True
|
|
210
244
|
)
|
|
211
245
|
|
|
212
246
|
def callback(length):
|
|
@@ -304,7 +338,7 @@ def _s3_compatible_multipart_upload(
|
|
|
304
338
|
parts = []
|
|
305
339
|
|
|
306
340
|
multi_part_upload_progress = progress_bar.add_task(
|
|
307
|
-
f"[green]Uploading {local_file}:", start=True
|
|
341
|
+
f"[green]Uploading {_get_relpath_if_in_tempdir(local_file)}:", start=True
|
|
308
342
|
)
|
|
309
343
|
|
|
310
344
|
def upload(part_number: int, seek: int) -> None:
|
|
@@ -315,7 +349,7 @@ def _s3_compatible_multipart_upload(
|
|
|
315
349
|
local_file,
|
|
316
350
|
)
|
|
317
351
|
response = _file_part_upload(
|
|
318
|
-
url=multipart_upload.part_signed_urls[part_number].
|
|
352
|
+
url=multipart_upload.part_signed_urls[part_number].signed_url,
|
|
319
353
|
file_path=local_file,
|
|
320
354
|
seek=seek,
|
|
321
355
|
length=multipart_info.part_size,
|
|
@@ -374,7 +408,7 @@ def _azure_multi_part_upload(
|
|
|
374
408
|
abort_event = abort_event or Event()
|
|
375
409
|
|
|
376
410
|
multi_part_upload_progress = progress_bar.add_task(
|
|
377
|
-
f"[green]Uploading {local_file}:", start=True
|
|
411
|
+
f"[green]Uploading {_get_relpath_if_in_tempdir(local_file)}:", start=True
|
|
378
412
|
)
|
|
379
413
|
|
|
380
414
|
def upload(part_number: int, seek: int):
|
|
@@ -385,7 +419,7 @@ def _azure_multi_part_upload(
|
|
|
385
419
|
local_file,
|
|
386
420
|
)
|
|
387
421
|
_file_part_upload(
|
|
388
|
-
url=multipart_upload.part_signed_urls[part_number].
|
|
422
|
+
url=multipart_upload.part_signed_urls[part_number].signed_url,
|
|
389
423
|
file_path=local_file,
|
|
390
424
|
seek=seek,
|
|
391
425
|
length=multipart_info.part_size,
|
|
@@ -527,8 +561,7 @@ class MlFoundryArtifactsRepository:
|
|
|
527
561
|
def log_artifacts( # noqa: C901
|
|
528
562
|
self, local_dir, artifact_path=None, progress=None
|
|
529
563
|
):
|
|
530
|
-
|
|
531
|
-
progress = sys.stdout.isatty()
|
|
564
|
+
show_progress = _can_display_progress(progress)
|
|
532
565
|
|
|
533
566
|
dest_path = artifact_path or ""
|
|
534
567
|
dest_path = dest_path.lstrip(posixpath.sep)
|
|
@@ -565,14 +598,15 @@ class MlFoundryArtifactsRepository:
|
|
|
565
598
|
|
|
566
599
|
with Progress(
|
|
567
600
|
"[progress.description]{task.description}",
|
|
568
|
-
BarColumn(),
|
|
601
|
+
BarColumn(bar_width=None),
|
|
569
602
|
"[progress.percentage]{task.percentage:>3.0f}%",
|
|
570
603
|
DownloadColumn(),
|
|
571
604
|
TransferSpeedColumn(),
|
|
572
605
|
TimeRemainingColumn(),
|
|
573
606
|
TimeElapsedColumn(),
|
|
574
607
|
refresh_per_second=1,
|
|
575
|
-
disable=not
|
|
608
|
+
disable=not show_progress,
|
|
609
|
+
expand=True,
|
|
576
610
|
) as progress_bar, ThreadPoolExecutor(
|
|
577
611
|
max_workers=_MAX_WORKERS_FOR_UPLOAD
|
|
578
612
|
) as executor:
|
|
@@ -647,7 +681,11 @@ class MlFoundryArtifactsRepository:
|
|
|
647
681
|
)[0]
|
|
648
682
|
|
|
649
683
|
if progress_bar.disable:
|
|
650
|
-
logger.info(
|
|
684
|
+
logger.info(
|
|
685
|
+
"Uploading %s to %s",
|
|
686
|
+
_get_relpath_if_in_tempdir(local_file),
|
|
687
|
+
artifact_path,
|
|
688
|
+
)
|
|
651
689
|
|
|
652
690
|
_signed_url_upload_file(
|
|
653
691
|
signed_url=signed_url,
|
|
@@ -668,7 +706,9 @@ class MlFoundryArtifactsRepository:
|
|
|
668
706
|
):
|
|
669
707
|
if progress_bar.disable:
|
|
670
708
|
logger.info(
|
|
671
|
-
"Uploading %s to %s using multipart upload",
|
|
709
|
+
"Uploading %s to %s using multipart upload",
|
|
710
|
+
_get_relpath_if_in_tempdir(local_file),
|
|
711
|
+
artifact_path,
|
|
672
712
|
)
|
|
673
713
|
|
|
674
714
|
multipart_upload = self.create_multipart_upload_for_identifier(
|
|
@@ -746,7 +786,7 @@ class MlFoundryArtifactsRepository:
|
|
|
746
786
|
upload_path = posixpath.join(upload_path, os.path.basename(local_file))
|
|
747
787
|
with Progress(
|
|
748
788
|
"[progress.description]{task.description}",
|
|
749
|
-
BarColumn(),
|
|
789
|
+
BarColumn(bar_width=None),
|
|
750
790
|
"[progress.percentage]{task.percentage:>3.0f}%",
|
|
751
791
|
DownloadColumn(),
|
|
752
792
|
TransferSpeedColumn(),
|
|
@@ -754,6 +794,7 @@ class MlFoundryArtifactsRepository:
|
|
|
754
794
|
TimeElapsedColumn(),
|
|
755
795
|
refresh_per_second=1,
|
|
756
796
|
disable=True,
|
|
797
|
+
expand=True,
|
|
757
798
|
) as progress_bar:
|
|
758
799
|
self._log_artifact(
|
|
759
800
|
local_file=local_file,
|
|
@@ -769,11 +810,11 @@ class MlFoundryArtifactsRepository:
|
|
|
769
810
|
|
|
770
811
|
def download_artifacts( # noqa: C901
|
|
771
812
|
self,
|
|
772
|
-
artifact_path,
|
|
773
|
-
dst_path=None,
|
|
813
|
+
artifact_path: str,
|
|
814
|
+
dst_path: Optional[str] = None,
|
|
774
815
|
overwrite: bool = False,
|
|
775
816
|
progress: Optional[bool] = None,
|
|
776
|
-
):
|
|
817
|
+
) -> str:
|
|
777
818
|
"""
|
|
778
819
|
Download an artifact file or directory to a local directory if applicable, and return a
|
|
779
820
|
local path for it. The caller is responsible for managing the lifecycle of the downloaded artifacts.
|
|
@@ -791,8 +832,7 @@ class MlFoundryArtifactsRepository:
|
|
|
791
832
|
str: Absolute path of the local filesystem location containing the desired artifacts.
|
|
792
833
|
"""
|
|
793
834
|
|
|
794
|
-
|
|
795
|
-
progress = sys.stdout.isatty()
|
|
835
|
+
show_progress = _can_display_progress()
|
|
796
836
|
|
|
797
837
|
is_dir_temp = False
|
|
798
838
|
if dst_path is None:
|
|
@@ -829,7 +869,8 @@ class MlFoundryArtifactsRepository:
|
|
|
829
869
|
TimeRemainingColumn(),
|
|
830
870
|
TimeElapsedColumn(),
|
|
831
871
|
refresh_per_second=1,
|
|
832
|
-
disable=not
|
|
872
|
+
disable=not show_progress,
|
|
873
|
+
expand=True,
|
|
833
874
|
)
|
|
834
875
|
|
|
835
876
|
try:
|
|
@@ -74,9 +74,6 @@ from truefoundry.ml.autogen.client.models.authorize_user_for_model_request_dto i
|
|
|
74
74
|
from truefoundry.ml.autogen.client.models.authorize_user_for_model_version_request_dto import (
|
|
75
75
|
AuthorizeUserForModelVersionRequestDto,
|
|
76
76
|
)
|
|
77
|
-
from truefoundry.ml.autogen.client.models.backfill_default_storage_integration_id_request_dto import (
|
|
78
|
-
BackfillDefaultStorageIntegrationIdRequestDto,
|
|
79
|
-
)
|
|
80
77
|
from truefoundry.ml.autogen.client.models.blob_storage_reference import (
|
|
81
78
|
BlobStorageReference,
|
|
82
79
|
)
|