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.

Files changed (82) hide show
  1. truefoundry/__init__.py +2 -0
  2. truefoundry/autodeploy/agents/developer.py +1 -1
  3. truefoundry/autodeploy/agents/project_identifier.py +2 -2
  4. truefoundry/autodeploy/agents/tester.py +1 -1
  5. truefoundry/autodeploy/cli.py +1 -1
  6. truefoundry/autodeploy/tools/list_files.py +1 -1
  7. truefoundry/{deploy/lib/auth → common}/auth_service_client.py +50 -40
  8. truefoundry/common/constants.py +12 -0
  9. truefoundry/{deploy/lib/auth → common}/credential_file_manager.py +7 -7
  10. truefoundry/{deploy/lib/auth → common}/credential_provider.py +9 -12
  11. truefoundry/{ml/services → common}/entities.py +59 -43
  12. truefoundry/common/exceptions.py +12 -0
  13. truefoundry/common/request_utils.py +36 -8
  14. truefoundry/common/servicefoundry_client.py +91 -0
  15. truefoundry/common/utils.py +56 -0
  16. truefoundry/deploy/auto_gen/models.py +4 -6
  17. truefoundry/deploy/cli/cli.py +2 -0
  18. truefoundry/deploy/cli/commands/apply_command.py +1 -1
  19. truefoundry/deploy/cli/commands/build_command.py +1 -1
  20. truefoundry/deploy/cli/commands/deploy_command.py +1 -1
  21. truefoundry/deploy/cli/commands/login_command.py +2 -2
  22. truefoundry/deploy/cli/commands/patch_application_command.py +1 -1
  23. truefoundry/deploy/cli/commands/patch_command.py +1 -1
  24. truefoundry/deploy/cli/commands/terminate_comand.py +1 -1
  25. truefoundry/deploy/cli/util.py +1 -1
  26. truefoundry/deploy/function_service/remote/remote.py +1 -1
  27. truefoundry/deploy/lib/auth/servicefoundry_session.py +2 -2
  28. truefoundry/deploy/lib/clients/servicefoundry_client.py +120 -150
  29. truefoundry/deploy/lib/const.py +1 -35
  30. truefoundry/deploy/lib/exceptions.py +0 -11
  31. truefoundry/deploy/lib/model/entity.py +1 -112
  32. truefoundry/deploy/lib/session.py +13 -26
  33. truefoundry/deploy/lib/util.py +0 -37
  34. truefoundry/deploy/python_deploy_codegen.py +2 -2
  35. truefoundry/deploy/v2/lib/deploy.py +3 -3
  36. truefoundry/ml/__init__.py +0 -2
  37. truefoundry/ml/artifact/truefoundry_artifact_repo.py +63 -22
  38. truefoundry/ml/autogen/client/__init__.py +0 -3
  39. truefoundry/ml/autogen/client/api/experiments_api.py +0 -165
  40. truefoundry/ml/autogen/client/models/__init__.py +0 -3
  41. truefoundry/ml/autogen/client/models/artifact_dto.py +6 -6
  42. truefoundry/ml/autogen/client/models/artifact_version_dto.py +8 -8
  43. truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +2 -3
  44. truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +2 -3
  45. truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +2 -2
  46. truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +2 -3
  47. truefoundry/ml/autogen/client/models/create_run_request_dto.py +5 -5
  48. truefoundry/ml/autogen/client/models/create_run_response_dto.py +2 -3
  49. truefoundry/ml/autogen/client/models/dataset_dto.py +10 -10
  50. truefoundry/ml/autogen/client/models/experiment_dto.py +18 -18
  51. truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +2 -3
  52. truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +4 -5
  53. truefoundry/ml/autogen/client/models/model_dto.py +6 -6
  54. truefoundry/ml/autogen/client/models/model_version_dto.py +15 -8
  55. truefoundry/ml/autogen/client/models/run_info_dto.py +10 -10
  56. truefoundry/ml/autogen/client/models/update_run_response_dto.py +2 -3
  57. truefoundry/ml/autogen/client_README.md +0 -2
  58. truefoundry/ml/clients/entities.py +8 -0
  59. truefoundry/ml/{services/servicefoundry_service.py → clients/servicefoundry_client.py} +20 -10
  60. truefoundry/ml/{services → clients}/utils.py +2 -2
  61. truefoundry/ml/env_vars.py +1 -5
  62. truefoundry/ml/internal_namespace.py +8 -8
  63. truefoundry/ml/log_types/artifacts/artifact.py +7 -3
  64. truefoundry/ml/log_types/artifacts/dataset.py +1 -1
  65. truefoundry/ml/log_types/artifacts/model.py +7 -8
  66. truefoundry/ml/log_types/image/image.py +7 -8
  67. truefoundry/ml/log_types/image/image_normalizer.py +7 -6
  68. truefoundry/ml/mlfoundry_api.py +5 -17
  69. truefoundry/ml/mlfoundry_run.py +0 -5
  70. truefoundry/ml/run_utils.py +1 -10
  71. truefoundry/ml/session.py +14 -117
  72. truefoundry/pydantic_v1.py +1 -1
  73. truefoundry/workflow/__init__.py +16 -1
  74. {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/METADATA +2 -2
  75. {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/RECORD +78 -77
  76. truefoundry/deploy/lib/clients/utils.py +0 -41
  77. truefoundry/ml/autogen/client/models/backfill_default_storage_integration_id_request_dto.py +0 -67
  78. truefoundry/ml/login.py +0 -241
  79. truefoundry/ml/services/auth_service.py +0 -109
  80. /truefoundry/ml/{services → clients}/__init__.py +0 -0
  81. {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/WHEEL +0 -0
  82. {truefoundry-0.4.0rc2.dist-info → truefoundry-0.4.0rc4.dist-info}/entry_points.txt +0 -0
@@ -1,41 +0,0 @@
1
- import os
2
- import time
3
- from typing import Any, Callable, Generator, Optional
4
-
5
- from truefoundry.deploy.lib.const import HOST_ENV_NAME
6
- from truefoundry.deploy.lib.exceptions import BadRequestException
7
-
8
-
9
- def request_handling(res):
10
- try:
11
- status_code = res.status_code
12
- except Exception as ex:
13
- raise Exception("Unknown error occurred. Couldn't get status code.") from ex
14
- if 200 <= status_code <= 299:
15
- if res.content == b"":
16
- return None
17
- return res.json()
18
- if 400 <= status_code <= 499:
19
- try:
20
- message = str(res.json()["message"])
21
- except Exception:
22
- message = res
23
- raise BadRequestException(res.status_code, message)
24
- if 500 <= status_code <= 599:
25
- raise Exception(res.content)
26
-
27
-
28
- def poll_for_function(
29
- func: Callable, poll_after_secs: int = 5, *args, **kwargs
30
- ) -> Generator[Any, None, None]:
31
- while True:
32
- yield func(*args, **kwargs)
33
- time.sleep(poll_after_secs)
34
-
35
-
36
- def resolve_base_url(host: Optional[str] = None) -> str:
37
- if not host and not os.getenv(HOST_ENV_NAME):
38
- raise ValueError(
39
- f"Either `host` should be provided by --host <value>, or `{HOST_ENV_NAME}` env must be set"
40
- )
41
- return host or os.getenv(HOST_ENV_NAME)
@@ -1,67 +0,0 @@
1
- # coding: utf-8
2
-
3
- """
4
- FastAPI
5
-
6
- No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
-
8
- The version of the OpenAPI document: 0.1.0
9
- Generated by OpenAPI Generator (https://openapi-generator.tech)
10
-
11
- Do not edit the class manually.
12
- """ # noqa: E501
13
-
14
- from __future__ import annotations
15
-
16
- import json
17
- import pprint
18
- import re # noqa: F401
19
-
20
- from truefoundry.pydantic_v1 import BaseModel, Field, StrictStr
21
-
22
-
23
- class BackfillDefaultStorageIntegrationIdRequestDto(BaseModel):
24
- """
25
- BackfillDefaultStorageIntegrationIdRequestDto
26
- """
27
-
28
- storage_integration_id: StrictStr = Field(...)
29
- __properties = ["storage_integration_id"]
30
-
31
- class Config:
32
- """Pydantic configuration"""
33
-
34
- allow_population_by_field_name = True
35
- validate_assignment = True
36
-
37
- def to_str(self) -> str:
38
- """Returns the string representation of the model using alias"""
39
- return pprint.pformat(self.dict(by_alias=True))
40
-
41
- def to_json(self) -> str:
42
- """Returns the JSON representation of the model using alias"""
43
- return json.dumps(self.to_dict())
44
-
45
- @classmethod
46
- def from_json(cls, json_str: str) -> BackfillDefaultStorageIntegrationIdRequestDto:
47
- """Create an instance of BackfillDefaultStorageIntegrationIdRequestDto from a JSON string"""
48
- return cls.from_dict(json.loads(json_str))
49
-
50
- def to_dict(self):
51
- """Returns the dictionary representation of the model using alias"""
52
- _dict = self.dict(by_alias=True, exclude={}, exclude_none=True)
53
- return _dict
54
-
55
- @classmethod
56
- def from_dict(cls, obj: dict) -> BackfillDefaultStorageIntegrationIdRequestDto:
57
- """Create an instance of BackfillDefaultStorageIntegrationIdRequestDto from a dict"""
58
- if obj is None:
59
- return None
60
-
61
- if not isinstance(obj, dict):
62
- return BackfillDefaultStorageIntegrationIdRequestDto.parse_obj(obj)
63
-
64
- _obj = BackfillDefaultStorageIntegrationIdRequestDto.parse_obj(
65
- {"storage_integration_id": obj.get("storage_integration_id")}
66
- )
67
- return _obj
truefoundry/ml/login.py DELETED
@@ -1,241 +0,0 @@
1
- import os
2
- import threading
3
- from functools import lru_cache, wraps
4
- from pathlib import Path
5
- from typing import Optional
6
-
7
- import click
8
- from filelock import FileLock, Timeout
9
-
10
- from truefoundry.ml.env_vars import API_KEY_GLOBAL, TRACKING_HOST_GLOBAL
11
- from truefoundry.ml.exceptions import MlFoundryException
12
- from truefoundry.ml.logger import logger
13
- from truefoundry.ml.run_utils import resolve_tracking_uri
14
- from truefoundry.ml.services.auth_service import get_auth_service
15
- from truefoundry.ml.services.entities import Token
16
- from truefoundry.pydantic_v1 import BaseModel, Field, NonEmptyStr
17
-
18
- CREDENTIALS_DIR = Path.home() / ".truefoundry"
19
- CREDENTIALS_FILE = CREDENTIALS_DIR / "credentials.json"
20
-
21
-
22
- OLD_CREDENTIALS_DIR = Path.home() / ".mlfoundry"
23
- OLD_CREDENTIALS_FILE = OLD_CREDENTIALS_DIR / "credentials.netrc"
24
-
25
- if OLD_CREDENTIALS_FILE.exists():
26
- logger.warning(
27
- "%s file is deprecated. You can delete this file now.", OLD_CREDENTIALS_FILE
28
- )
29
-
30
-
31
- class CredentialsFileContent(BaseModel):
32
- access_token: NonEmptyStr = Field(repr=False)
33
- refresh_token: Optional[NonEmptyStr] = Field(repr=False)
34
- host: NonEmptyStr
35
-
36
- class Config:
37
- allow_mutation = False
38
-
39
- def to_token(self) -> Token:
40
- return Token(
41
- access_token=self.access_token, # type: ignore[call-arg]
42
- refresh_token=self.refresh_token,
43
- )
44
-
45
-
46
- def _ensure_lock_taken(method):
47
- @wraps(method)
48
- def lock_guard(self: "CredentialsFileManager", *method_args, **method_kwargs):
49
- if not self.lock_taken():
50
- raise Exception(
51
- "Trying to write to credential file without using with block"
52
- )
53
- return method(self, *method_args, **method_kwargs)
54
-
55
- return lock_guard
56
-
57
-
58
- CRED_FILE_THREAD_LOCK = threading.RLock()
59
-
60
-
61
- @lru_cache(maxsize=None)
62
- def get_file_lock(lock_file_path: str) -> FileLock:
63
- return FileLock(lock_file_path)
64
-
65
-
66
- class CredentialsFileManager:
67
- def __init__(
68
- self,
69
- credentials_file_path: Path = CREDENTIALS_FILE,
70
- lock_timeout: float = 60.0,
71
- ):
72
- credentials_file_path = credentials_file_path.absolute()
73
-
74
- logger.debug("credential file path %r", credentials_file_path)
75
-
76
- credentials_lock_file_path = f"{credentials_file_path}.lock"
77
-
78
- logger.debug("credential lock file path %r", credentials_lock_file_path)
79
- self._credentials_file_path = credentials_file_path
80
-
81
- cred_file_dir = credentials_file_path.parent
82
- cred_file_dir.mkdir(exist_ok=True, parents=True)
83
-
84
- self._file_lock = get_file_lock(credentials_lock_file_path)
85
- self._lock_timeout = lock_timeout
86
- self._lock_owner: Optional[int] = None
87
-
88
- def __enter__(self) -> "CredentialsFileManager":
89
- # The lock objects are recursive locks, which means that once acquired,
90
- # they will not block on successive lock requests:
91
- lock_aquired = CRED_FILE_THREAD_LOCK.acquire(timeout=self._lock_timeout)
92
- if not lock_aquired:
93
- raise MlFoundryException(
94
- "Could not aquire CRED_FILE_THREAD_LOCK"
95
- f" in {self._lock_timeout} seconds"
96
- )
97
- try:
98
- self._file_lock.acquire(timeout=self._lock_timeout)
99
- except Timeout as ex:
100
- raise MlFoundryException(
101
- f"Failed to aquire lock on credential file within {self._lock_timeout} seconds.\n"
102
- "Is any other process trying to login?"
103
- ) from ex
104
- logger.debug("Acquired file and thread lock to access credential file")
105
- self._lock_owner = threading.get_ident()
106
- return self
107
-
108
- def __exit__(self, exc_type, exc_val, exc_tb):
109
- self._file_lock.release()
110
- CRED_FILE_THREAD_LOCK.release()
111
- logger.debug("Released file and thread lock to access credential file")
112
- self._lock_owner = None
113
-
114
- def lock_taken(self) -> bool:
115
- return self._lock_owner == threading.get_ident()
116
-
117
- @_ensure_lock_taken
118
- def read(self) -> CredentialsFileContent:
119
- try:
120
- return CredentialsFileContent.parse_file(self._credentials_file_path)
121
- except Exception as ex:
122
- raise MlFoundryException(
123
- "Error while reading the credentials file "
124
- f"{self._credentials_file_path}. Please login again "
125
- "using `tfy login` command or `truefoundry.login()` function"
126
- ) from ex
127
-
128
- @_ensure_lock_taken
129
- def write(self, credentials_file_content: CredentialsFileContent):
130
- if not isinstance(credentials_file_content, CredentialsFileContent):
131
- raise MlFoundryException(
132
- "Only object of type `CredentialsFileContent` is allowed. "
133
- f"Got {type(credentials_file_content)}"
134
- )
135
- logger.debug("Updating the credential file content")
136
- with open(self._credentials_file_path, "w", encoding="utf8") as file:
137
- file.write(credentials_file_content.json())
138
-
139
- @_ensure_lock_taken
140
- def exists(self) -> bool:
141
- return self._credentials_file_path.exists()
142
-
143
-
144
- def login(
145
- tracking_uri: Optional[str] = None,
146
- relogin: bool = False,
147
- api_key: Optional[str] = None,
148
- ) -> bool:
149
- """Save API key in local file system for a given `tracking_uri`.
150
-
151
- Args:
152
- tracking_uri (Optional[str], optional): tracking_uri for the given API key
153
- relogin (bool, optional): Overwrites the existing API key for the `tracking_uri` if
154
- set to `True`. If set to `False` and an API key is already present for
155
- the given `tracking_uri`, then the existing API key is kept untouched.
156
- Default is `False`.
157
- api_key (Optional[str], optional): The API key for the given `tracking_uri`.
158
- If `api_key` is not passed, this function prompts for the API key.
159
-
160
- Returns:
161
- bool: Returns `True` if any credential was persisted.
162
- """
163
- from truefoundry.ml.session import EnvCredentialProvider
164
-
165
- if API_KEY_GLOBAL in os.environ and TRACKING_HOST_GLOBAL in os.environ:
166
- logger.warning(
167
- "Skipping login because environment variables %s and "
168
- "%s are set and will be used when running truefoundry. "
169
- "If you want to relogin then unset these environment keys.",
170
- TRACKING_HOST_GLOBAL,
171
- API_KEY_GLOBAL,
172
- )
173
- return False
174
-
175
- if EnvCredentialProvider.can_provide():
176
- logger.warning(
177
- "TFY_API_KEY env var is already set. "
178
- "When running mlfoundry, it will use the api key to authorize.\n"
179
- "Login will just save the credentials on disk."
180
- )
181
-
182
- tracking_uri = resolve_tracking_uri(tracking_uri).strip("/")
183
- auth_service = get_auth_service(tracking_uri=tracking_uri)
184
-
185
- cred_file = CredentialsFileManager()
186
-
187
- with cred_file:
188
- if not relogin and cred_file.exists():
189
- cred_file_content = cred_file.read()
190
- if tracking_uri != cred_file_content.host:
191
- if click.confirm(
192
- f"Already logged in to {cred_file_content.host!r}\n"
193
- f"Do you want to relogin to {tracking_uri!r}?"
194
- ):
195
- return login(
196
- tracking_uri=tracking_uri, relogin=True, api_key=api_key
197
- )
198
- user_info = cred_file_content.to_token().to_user_info()
199
- user_name_display_info = user_info.email or user_info.user_type.value
200
- print(
201
- f"Already logged in to {cred_file_content.host!r} as "
202
- f"{user_info.user_id!r} ({user_name_display_info!r})\n"
203
- "Please use `tfy login --relogin` or `truefoundry.login(relogin=True)` "
204
- "to force relogin"
205
- )
206
- return False
207
-
208
- if api_key:
209
- logger.debug("Logging in with api key")
210
- token = Token(access_token=api_key, refresh_token=None) # type: ignore[call-arg]
211
- cred_file_content = CredentialsFileContent(
212
- access_token=token.access_token,
213
- refresh_token=token.refresh_token,
214
- host=tracking_uri,
215
- )
216
- cred_file.write(cred_file_content)
217
- else:
218
- device_code = auth_service.get_device_code()
219
- url_to_go = device_code.get_user_clickable_url(tracking_uri=tracking_uri)
220
- print(f"Opening:- {url_to_go}")
221
- print(
222
- "Please click on the above link if it is not "
223
- "automatically opened in a browser window."
224
- )
225
- click.launch(url_to_go)
226
- token = auth_service.get_token_from_device_code(
227
- device_code=device_code.device_code, timeout=120
228
- )
229
- cred_file_content = CredentialsFileContent(
230
- access_token=token.access_token,
231
- refresh_token=token.refresh_token,
232
- host=tracking_uri,
233
- )
234
- cred_file.write(cred_file_content)
235
-
236
- user_info = token.to_user_info()
237
- user_name_display_info = user_info.email or user_info.user_type.value
238
- print(
239
- f"Logged in to {cred_file_content.host!r} as {user_info.user_id!r} ({user_name_display_info})"
240
- )
241
- return True
@@ -1,109 +0,0 @@
1
- import time
2
- from urllib.parse import urlparse
3
-
4
- from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
5
- ApiClient,
6
- AuthApi,
7
- Configuration,
8
- )
9
- from truefoundry.ml.exceptions import MlFoundryException
10
- from truefoundry.ml.logger import logger
11
- from truefoundry.ml.run_utils import append_path_to_rest_tracking_uri
12
- from truefoundry.ml.services.entities import (
13
- AuthServerInfo,
14
- DeviceCode,
15
- HostCreds,
16
- Token,
17
- )
18
- from truefoundry.ml.services.utils import http_request, http_request_safe
19
-
20
- # TODO: This will eventually go away, this is duplicate of AuthServiceClient
21
-
22
-
23
- class AuthService:
24
- def __init__(self, url: str, tenant_name: str):
25
- self._host_creds = HostCreds(host=url.rstrip("/"), token=None)
26
- self._tenant_name = tenant_name
27
-
28
- def refresh_token(self, token: Token) -> Token:
29
- if not token.refresh_token:
30
- # TODO: Add a way to propagate error messages without traceback to the output interface side
31
- raise MlFoundryException(
32
- "Unable to resume login session. Please log in again using `tfy login [--host HOST] --relogin`"
33
- )
34
- try:
35
- response = http_request_safe(
36
- method="post",
37
- host_creds=self._host_creds,
38
- endpoint="api/v1/oauth/token/refresh",
39
- json={
40
- "tenantName": token.tenant_name,
41
- "refreshToken": token.refresh_token,
42
- },
43
- timeout=3,
44
- )
45
- except MlFoundryException as e:
46
- if e.status_code and (400 <= e.status_code < 500):
47
- raise MlFoundryException(
48
- "Unable to resume login session. "
49
- "Please log in again using `tfy login [--host HOST] --relogin`"
50
- ) from None
51
- raise
52
- return Token.parse_obj(response)
53
-
54
- def get_device_code(self) -> DeviceCode:
55
- response = http_request_safe(
56
- method="post",
57
- host_creds=self._host_creds,
58
- endpoint="api/v1/oauth/device",
59
- json={"tenantName": self._tenant_name},
60
- timeout=3,
61
- )
62
- return DeviceCode.parse_obj(response)
63
-
64
- def get_token_from_device_code(
65
- self, device_code: str, timeout: float = 60
66
- ) -> Token:
67
- start_time = time.monotonic()
68
- while (time.monotonic() - start_time) <= timeout:
69
- response = http_request(
70
- method="post",
71
- host_creds=self._host_creds,
72
- endpoint="api/v1/oauth/device/token",
73
- json={"tenantName": self._tenant_name, "deviceCode": device_code},
74
- timeout=3,
75
- )
76
- if response.status_code == 202:
77
- logger.debug("User has not authorized yet. Checking again.")
78
- time.sleep(1.0)
79
- continue
80
- if response.status_code == 201:
81
- response = response.json()
82
- return Token.parse_obj(response)
83
- raise MlFoundryException(
84
- "Failed to get token using device code.\n"
85
- f"Status Code: {response.status_code},\nResponse: {response.text}"
86
- )
87
- raise MlFoundryException(f"Did not get authorized within {timeout} seconds.")
88
-
89
-
90
- def get_auth_service(tracking_uri: str) -> AuthService:
91
- tracking_uri = append_path_to_rest_tracking_uri(tracking_uri)
92
- parsed_tracking_uri = urlparse(tracking_uri)
93
- host = parsed_tracking_uri.netloc
94
- # Anonymous api
95
- api_client = ApiClient(
96
- configuration=Configuration(
97
- host=tracking_uri.rstrip("/"),
98
- access_token=None,
99
- )
100
- )
101
- auth_api = AuthApi(api_client=api_client)
102
- auth_server_info = auth_api.get_tenant_id_get(
103
- host_name=host,
104
- _request_timeout=3,
105
- )
106
- tenant_info = AuthServerInfo.parse_obj(auth_server_info.dict())
107
- return AuthService(
108
- url=tenant_info.auth_server_url, tenant_name=tenant_info.tenant_name
109
- )
File without changes