truefoundry 0.2.10__py3-none-any.whl → 0.3.0__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 (112) hide show
  1. truefoundry/__init__.py +1 -0
  2. truefoundry/autodeploy/cli.py +31 -18
  3. truefoundry/deploy/__init__.py +112 -1
  4. truefoundry/deploy/auto_gen/models.py +1714 -0
  5. truefoundry/deploy/builder/__init__.py +134 -0
  6. truefoundry/deploy/builder/builders/__init__.py +22 -0
  7. truefoundry/deploy/builder/builders/dockerfile.py +57 -0
  8. truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py +46 -0
  9. truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +66 -0
  10. truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +44 -0
  11. truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +158 -0
  12. truefoundry/deploy/builder/docker_service.py +168 -0
  13. truefoundry/deploy/cli/cli.py +21 -26
  14. truefoundry/deploy/cli/commands/__init__.py +18 -0
  15. truefoundry/deploy/cli/commands/apply_command.py +52 -0
  16. truefoundry/deploy/cli/commands/build_command.py +45 -0
  17. truefoundry/deploy/cli/commands/build_logs_command.py +89 -0
  18. truefoundry/deploy/cli/commands/create_command.py +75 -0
  19. truefoundry/deploy/cli/commands/delete_command.py +77 -0
  20. truefoundry/deploy/cli/commands/deploy_command.py +102 -0
  21. truefoundry/deploy/cli/commands/get_command.py +216 -0
  22. truefoundry/deploy/cli/commands/list_command.py +171 -0
  23. truefoundry/deploy/cli/commands/login_command.py +33 -0
  24. truefoundry/deploy/cli/commands/logout_command.py +20 -0
  25. truefoundry/deploy/cli/commands/logs_command.py +134 -0
  26. truefoundry/deploy/cli/commands/patch_application_command.py +81 -0
  27. truefoundry/deploy/cli/commands/patch_command.py +70 -0
  28. truefoundry/deploy/cli/commands/redeploy_command.py +41 -0
  29. truefoundry/deploy/cli/commands/terminate_comand.py +44 -0
  30. truefoundry/deploy/cli/commands/trigger_command.py +145 -0
  31. truefoundry/deploy/cli/config.py +10 -0
  32. truefoundry/deploy/cli/console.py +5 -0
  33. truefoundry/deploy/cli/const.py +12 -0
  34. truefoundry/deploy/cli/display_util.py +118 -0
  35. truefoundry/deploy/cli/util.py +129 -0
  36. truefoundry/deploy/core/__init__.py +7 -0
  37. truefoundry/deploy/core/login.py +9 -0
  38. truefoundry/deploy/core/logout.py +5 -0
  39. truefoundry/deploy/function_service/__init__.py +3 -0
  40. truefoundry/deploy/function_service/__main__.py +27 -0
  41. truefoundry/deploy/function_service/app.py +92 -0
  42. truefoundry/deploy/function_service/build.py +45 -0
  43. truefoundry/deploy/function_service/remote/__init__.py +6 -0
  44. truefoundry/deploy/function_service/remote/context.py +3 -0
  45. truefoundry/deploy/function_service/remote/method.py +67 -0
  46. truefoundry/deploy/function_service/remote/remote.py +144 -0
  47. truefoundry/deploy/function_service/route.py +137 -0
  48. truefoundry/deploy/function_service/service.py +113 -0
  49. truefoundry/deploy/function_service/utils.py +53 -0
  50. truefoundry/deploy/io/__init__.py +0 -0
  51. truefoundry/deploy/io/output_callback.py +23 -0
  52. truefoundry/deploy/io/rich_output_callback.py +27 -0
  53. truefoundry/deploy/json_util.py +7 -0
  54. truefoundry/deploy/lib/__init__.py +0 -0
  55. truefoundry/deploy/lib/auth/auth_service_client.py +181 -0
  56. truefoundry/deploy/lib/auth/credential_file_manager.py +115 -0
  57. truefoundry/deploy/lib/auth/credential_provider.py +131 -0
  58. truefoundry/deploy/lib/auth/servicefoundry_session.py +59 -0
  59. truefoundry/deploy/lib/clients/__init__.py +0 -0
  60. truefoundry/deploy/lib/clients/servicefoundry_client.py +746 -0
  61. truefoundry/deploy/lib/clients/shell_client.py +13 -0
  62. truefoundry/deploy/lib/clients/utils.py +41 -0
  63. truefoundry/deploy/lib/const.py +43 -0
  64. truefoundry/deploy/lib/dao/__init__.py +0 -0
  65. truefoundry/deploy/lib/dao/application.py +263 -0
  66. truefoundry/deploy/lib/dao/apply.py +80 -0
  67. truefoundry/deploy/lib/dao/version.py +33 -0
  68. truefoundry/deploy/lib/dao/workspace.py +71 -0
  69. truefoundry/deploy/lib/exceptions.py +26 -0
  70. truefoundry/deploy/lib/logs_utils.py +43 -0
  71. truefoundry/deploy/lib/messages.py +12 -0
  72. truefoundry/deploy/lib/model/__init__.py +0 -0
  73. truefoundry/deploy/lib/model/entity.py +400 -0
  74. truefoundry/deploy/lib/session.py +158 -0
  75. truefoundry/deploy/lib/util.py +90 -0
  76. truefoundry/deploy/lib/win32.py +129 -0
  77. truefoundry/deploy/v2/__init__.py +0 -0
  78. truefoundry/deploy/v2/lib/__init__.py +3 -0
  79. truefoundry/deploy/v2/lib/deploy.py +283 -0
  80. truefoundry/deploy/v2/lib/deploy_workflow.py +295 -0
  81. truefoundry/deploy/v2/lib/deployable_patched_models.py +86 -0
  82. truefoundry/deploy/v2/lib/models.py +53 -0
  83. truefoundry/deploy/v2/lib/patched_models.py +479 -0
  84. truefoundry/deploy/v2/lib/source.py +267 -0
  85. truefoundry/langchain/__init__.py +12 -1
  86. truefoundry/langchain/deprecated.py +302 -0
  87. truefoundry/langchain/truefoundry_chat.py +130 -0
  88. truefoundry/langchain/truefoundry_embeddings.py +171 -0
  89. truefoundry/langchain/truefoundry_llm.py +106 -0
  90. truefoundry/langchain/utils.py +85 -0
  91. truefoundry/logger.py +17 -0
  92. truefoundry/pydantic_v1.py +5 -0
  93. truefoundry/python_deploy_codegen.py +132 -0
  94. truefoundry/version.py +6 -0
  95. truefoundry/workflow/__init__.py +19 -0
  96. truefoundry/workflow/container_task.py +12 -0
  97. truefoundry/workflow/example/deploy.sh +1 -0
  98. truefoundry/workflow/example/hello_world_package/workflow.py +20 -0
  99. truefoundry/workflow/example/package/test_workflow.py +152 -0
  100. truefoundry/workflow/example/truefoundry.yaml +9 -0
  101. truefoundry/workflow/example/workflow.yaml +116 -0
  102. truefoundry/workflow/map_task.py +45 -0
  103. truefoundry/workflow/python_task.py +32 -0
  104. truefoundry/workflow/task.py +50 -0
  105. truefoundry/workflow/workflow.py +114 -0
  106. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/METADATA +27 -7
  107. truefoundry-0.3.0.dist-info/RECORD +136 -0
  108. truefoundry/deploy/cli/deploy.py +0 -165
  109. truefoundry/deploy/cli/version.py +0 -6
  110. truefoundry-0.2.10.dist-info/RECORD +0 -38
  111. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/WHEEL +0 -0
  112. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,27 @@
1
+ from rich.console import Console
2
+ from rich.highlighter import ReprHighlighter
3
+ from rich.panel import Panel
4
+ from rich.text import Text
5
+
6
+ from truefoundry.deploy.io.output_callback import OutputCallBack
7
+
8
+
9
+ def _text(line):
10
+ return Text.from_ansi(str(line)) if "\x1b" in line else str(line)
11
+
12
+
13
+ class RichOutputCallBack(OutputCallBack):
14
+ console = Console(soft_wrap=True)
15
+ highlighter = ReprHighlighter()
16
+
17
+ def print_header(self, line):
18
+ self.console.rule(_text(line), style="cyan")
19
+
20
+ def print_line(self, line):
21
+ self.console.print(_text(line))
22
+
23
+ def print_lines_in_panel(self, lines, header=None):
24
+ self.console.print(Panel(self.highlighter("\n".join(lines)), title=header))
25
+
26
+ def print_code_in_panel(self, lines, header=None, lang="python"):
27
+ self.print_lines_in_panel(lines, header)
@@ -0,0 +1,7 @@
1
+ import datetime
2
+
3
+
4
+ def json_default_encoder(o):
5
+ if isinstance(o, datetime.datetime):
6
+ return o.isoformat()
7
+ raise TypeError(f"Cannot json encode {type(o)}: {o}")
File without changes
@@ -0,0 +1,181 @@
1
+ import time
2
+ from abc import ABC, abstractmethod
3
+
4
+ import requests
5
+
6
+ from truefoundry.deploy.lib.clients.utils import poll_for_function, request_handling
7
+ from truefoundry.deploy.lib.const import VERSION_PREFIX
8
+ from truefoundry.deploy.lib.exceptions import BadRequestException
9
+ from truefoundry.deploy.lib.model.entity import DeviceCode, Token
10
+ from truefoundry.logger import logger
11
+
12
+
13
+ class AuthServiceClient(ABC):
14
+ def __init__(self, base_url):
15
+ from truefoundry.deploy.lib.clients.servicefoundry_client import (
16
+ ServiceFoundryServiceClient,
17
+ )
18
+
19
+ client = ServiceFoundryServiceClient(init_session=False, base_url=base_url)
20
+
21
+ self._api_server_url = client._api_server_url
22
+ self._auth_server_url = client.tenant_info.auth_server_url
23
+ self._tenant_name = client.tenant_info.tenant_name
24
+
25
+ @classmethod
26
+ def from_base_url(cls, base_url: str) -> "AuthServiceClient":
27
+ from truefoundry.deploy.lib.clients.servicefoundry_client import (
28
+ ServiceFoundryServiceClient,
29
+ )
30
+
31
+ client = ServiceFoundryServiceClient(init_session=False, base_url=base_url)
32
+ if client.python_sdk_config.use_sfy_server_auth_apis:
33
+ return ServiceFoundryServerAuthServiceClient(base_url)
34
+ return AuthServerServiceClient(base_url)
35
+
36
+ @abstractmethod
37
+ def refresh_token(self, token: Token, host: str = None) -> Token: ...
38
+
39
+ @abstractmethod
40
+ def get_device_code(self) -> DeviceCode: ...
41
+
42
+ @abstractmethod
43
+ def get_token_from_device_code(
44
+ self, device_code: str, timeout: float = 60, poll_interval_seconds: int = 1
45
+ ) -> Token: ...
46
+
47
+
48
+ class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
49
+ def __init__(self, base_url):
50
+ super().__init__(base_url)
51
+
52
+ def refresh_token(self, token: Token, host: str = None) -> Token:
53
+ host_arg_str = f"--host {host}" if host else "--host HOST"
54
+ if not token.refresh_token:
55
+ # TODO: Add a way to propagate error messages without traceback to the output interface side
56
+ raise Exception(
57
+ f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
58
+ )
59
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/oauth2/token"
60
+ data = {
61
+ "tenantName": token.tenant_name,
62
+ "refreshToken": token.refresh_token,
63
+ "grantType": "refresh_token",
64
+ "returnJWT": True,
65
+ }
66
+ res = requests.post(url, json=data)
67
+ try:
68
+ res = request_handling(res)
69
+ return Token.parse_obj(res)
70
+ except BadRequestException as ex:
71
+ raise Exception(
72
+ f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
73
+ ) from ex
74
+
75
+ def get_device_code(self) -> DeviceCode:
76
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/oauth2/device-authorize"
77
+ data = {"tenantName": self._tenant_name}
78
+ res = requests.post(url, json=data)
79
+ res = request_handling(res)
80
+ return DeviceCode.parse_obj(res)
81
+
82
+ def get_token_from_device_code(
83
+ self, device_code: str, timeout: float = 60, poll_interval_seconds: int = 1
84
+ ) -> Token:
85
+ timeout = timeout or 60
86
+ poll_interval_seconds = poll_interval_seconds or 1
87
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/oauth2/token"
88
+ data = {
89
+ "tenantName": self._tenant_name,
90
+ "deviceCode": device_code,
91
+ "grantType": "device_code",
92
+ "returnJWT": True,
93
+ }
94
+ response = requests.post(url=url, json=data)
95
+ start_time = time.monotonic()
96
+
97
+ for response in poll_for_function(
98
+ requests.post, poll_after_secs=poll_interval_seconds, url=url, json=data
99
+ ):
100
+ if response.status_code == 201:
101
+ response = response.json()
102
+ return Token.parse_obj(response)
103
+ elif response.status_code == 202:
104
+ logger.debug("User has not authorized yet. Checking again.")
105
+ else:
106
+ raise Exception(
107
+ "Failed to get token using device code. "
108
+ f"status_code {response.status_code},\n {response.text}"
109
+ )
110
+ time_elapsed = time.monotonic() - start_time
111
+ if time_elapsed > timeout:
112
+ logger.warning("Polled server for %s secs.", int(time_elapsed))
113
+ break
114
+
115
+ raise Exception(f"Did not get authorized within {timeout} seconds.")
116
+
117
+
118
+ class AuthServerServiceClient(AuthServiceClient):
119
+ def __init__(self, base_url):
120
+ super().__init__(base_url)
121
+
122
+ def refresh_token(self, token: Token, host: str = None) -> Token:
123
+ host_arg_str = f"--host {host}" if host else "--host HOST"
124
+ if not token.refresh_token:
125
+ # TODO: Add a way to propagate error messages without traceback to the output interface side
126
+ raise Exception(
127
+ f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
128
+ )
129
+ url = f"{self._auth_server_url}/api/{VERSION_PREFIX}/oauth/token/refresh"
130
+ data = {
131
+ "tenantName": token.tenant_name,
132
+ "refreshToken": token.refresh_token,
133
+ }
134
+ res = requests.post(url, json=data)
135
+ try:
136
+ res = request_handling(res)
137
+ return Token.parse_obj(res)
138
+ except BadRequestException as ex:
139
+ raise Exception(
140
+ f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
141
+ ) from ex
142
+
143
+ def get_device_code(self) -> DeviceCode:
144
+ url = f"{self._auth_server_url}/api/{VERSION_PREFIX}/oauth/device"
145
+ data = {"tenantName": self._tenant_name}
146
+ res = requests.post(url, json=data)
147
+ res = request_handling(res)
148
+ # TODO: temporary cleanup of incorrect attributes
149
+ res = {"userCode": res.get("userCode"), "deviceCode": res.get("deviceCode")}
150
+ return DeviceCode.parse_obj(res)
151
+
152
+ def get_token_from_device_code(
153
+ self, device_code: str, timeout: float = 60, poll_interval_seconds: int = 1
154
+ ) -> Token:
155
+ url = f"{self._auth_server_url}/api/{VERSION_PREFIX}/oauth/device/token"
156
+ data = {
157
+ "tenantName": self._tenant_name,
158
+ "deviceCode": device_code,
159
+ }
160
+ start_time = time.monotonic()
161
+ poll_interval_seconds = 1
162
+
163
+ for response in poll_for_function(
164
+ requests.post, poll_after_secs=poll_interval_seconds, url=url, json=data
165
+ ):
166
+ if response.status_code == 201:
167
+ response = response.json()
168
+ return Token.parse_obj(response)
169
+ elif response.status_code == 202:
170
+ logger.debug("User has not authorized yet. Checking again.")
171
+ else:
172
+ raise Exception(
173
+ "Failed to get token using device code. "
174
+ f"status_code {response.status_code},\n {response.text}"
175
+ )
176
+ time_elapsed = time.monotonic() - start_time
177
+ if time_elapsed > timeout:
178
+ logger.warning("Polled server for %s secs.", int(time_elapsed))
179
+ break
180
+
181
+ raise Exception(f"Did not get authorized within {timeout} seconds.")
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import threading
5
+ from functools import lru_cache, wraps
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from filelock import FileLock, Timeout
10
+
11
+ from truefoundry.deploy.lib.const import CREDENTIAL_FILEPATH
12
+ from truefoundry.deploy.lib.model.entity import CredentialsFileContent
13
+ from truefoundry.logger import logger
14
+
15
+
16
+ def _ensure_lock_taken(method):
17
+ @wraps(method)
18
+ def lock_guard(self, *method_args, **method_kwargs):
19
+ if not self.lock_taken():
20
+ raise Exception(
21
+ "Trying to write to credential file without using with block"
22
+ )
23
+ return method(self, *method_args, **method_kwargs)
24
+
25
+ return lock_guard
26
+
27
+
28
+ CRED_FILE_THREAD_LOCK = threading.RLock()
29
+
30
+
31
+ @lru_cache(maxsize=None)
32
+ def get_file_lock(lock_file_path: str) -> FileLock:
33
+ return FileLock(lock_file_path)
34
+
35
+
36
+ class CredentialsFileManager:
37
+ def __init__(
38
+ self,
39
+ credentials_file_path: Path = CREDENTIAL_FILEPATH,
40
+ lock_timeout: float = 60.0,
41
+ ) -> None:
42
+ credentials_file_path = credentials_file_path.absolute()
43
+ logger.debug("credential file path %r", credentials_file_path)
44
+
45
+ credentials_lock_file_path = f"{credentials_file_path}.lock"
46
+ logger.debug("credential lock file path %r", credentials_lock_file_path)
47
+
48
+ self._credentials_file_path = credentials_file_path
49
+ cred_file_dir = credentials_file_path.parent
50
+ cred_file_dir.mkdir(exist_ok=True, parents=True)
51
+
52
+ self._file_lock = get_file_lock(credentials_lock_file_path)
53
+ self._lock_timeout = lock_timeout
54
+ self._lock_owner: Optional[int] = None
55
+
56
+ def __enter__(self) -> CredentialsFileManager:
57
+ # The lock objects are recursive locks, which means that once acquired, they will not block on successive lock requests:
58
+ lock_aquired = CRED_FILE_THREAD_LOCK.acquire(timeout=self._lock_timeout)
59
+ if not lock_aquired:
60
+ raise Exception(
61
+ "Could not aquire CRED_FILE_THREAD_LOCK"
62
+ f" in {self._lock_timeout} seconds"
63
+ )
64
+ try:
65
+ self._file_lock.acquire(timeout=self._lock_timeout)
66
+ except Timeout as ex:
67
+ raise Exception(
68
+ f"Failed to aquire lock on credential file within {self._lock_timeout} seconds.\n"
69
+ "Is any other process trying to login?"
70
+ ) from ex
71
+ logger.debug("Acquired file and thread lock to access credential file")
72
+ self._lock_owner = threading.get_ident()
73
+ return self
74
+
75
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
76
+ self._file_lock.release()
77
+ CRED_FILE_THREAD_LOCK.release()
78
+ logger.debug("Released file and thread lock to access credential file")
79
+ self._lock_owner = None
80
+
81
+ def lock_taken(self) -> bool:
82
+ return self._lock_owner == threading.get_ident()
83
+
84
+ @_ensure_lock_taken
85
+ def read(self) -> CredentialsFileContent:
86
+ try:
87
+ return CredentialsFileContent.parse_file(self._credentials_file_path)
88
+ except Exception as ex:
89
+ raise Exception(
90
+ "Error while reading the credentials file "
91
+ f"{self._credentials_file_path}. Please login again "
92
+ "using `tfy login --relogin` or `tfy.login(relogin=True)` function"
93
+ ) from ex
94
+
95
+ @_ensure_lock_taken
96
+ def write(self, credentials_file_content: CredentialsFileContent) -> None:
97
+ if not isinstance(credentials_file_content, CredentialsFileContent):
98
+ raise Exception(
99
+ "Only object of type `CredentialsFileContent` is allowed. "
100
+ f"Got {type(credentials_file_content)}"
101
+ )
102
+ logger.debug("Updating the credential file content")
103
+ with open(self._credentials_file_path, "w", encoding="utf8") as file:
104
+ file.write(credentials_file_content.json())
105
+
106
+ @_ensure_lock_taken
107
+ def delete(self) -> bool:
108
+ if not os.path.exists(self._credentials_file_path):
109
+ return False
110
+ os.remove(self._credentials_file_path)
111
+ return True
112
+
113
+ @_ensure_lock_taken
114
+ def exists(self) -> bool:
115
+ return self._credentials_file_path.exists()
@@ -0,0 +1,131 @@
1
+ import os
2
+ import threading
3
+ from abc import ABC, abstractmethod
4
+
5
+ from truefoundry.deploy.lib.auth.auth_service_client import AuthServiceClient
6
+ from truefoundry.deploy.lib.auth.credential_file_manager import (
7
+ CredentialsFileContent,
8
+ CredentialsFileManager,
9
+ )
10
+ from truefoundry.deploy.lib.clients.utils import resolve_base_url
11
+ from truefoundry.deploy.lib.const import API_KEY_ENV_NAME
12
+ from truefoundry.deploy.lib.model.entity import Token
13
+ from truefoundry.logger import logger
14
+
15
+ TOKEN_REFRESH_LOCK = threading.RLock()
16
+
17
+
18
+ class CredentialProvider(ABC):
19
+ @property
20
+ @abstractmethod
21
+ def token(self) -> Token: ...
22
+
23
+ @property
24
+ @abstractmethod
25
+ def base_url(self) -> str: ...
26
+
27
+ @staticmethod
28
+ @abstractmethod
29
+ def can_provide() -> bool: ...
30
+
31
+
32
+ class EnvCredentialProvider(CredentialProvider):
33
+ def __init__(self) -> None:
34
+ from truefoundry.deploy.lib.clients.servicefoundry_client import (
35
+ ServiceFoundryServiceClient,
36
+ )
37
+
38
+ logger.debug("Using env var credential provider")
39
+ api_key = os.getenv(API_KEY_ENV_NAME)
40
+ if not api_key:
41
+ raise Exception(
42
+ f"Value of {API_KEY_ENV_NAME} env var should be non-empty string"
43
+ )
44
+ # TODO: Read host from cred file as well.
45
+ base_url = resolve_base_url().strip("/")
46
+ self._host = base_url
47
+ self._auth_service = AuthServiceClient.from_base_url(base_url=base_url)
48
+
49
+ servicefoundry_client = ServiceFoundryServiceClient(
50
+ init_session=False, base_url=base_url
51
+ )
52
+ self._token: Token = servicefoundry_client.get_token_from_api_key(
53
+ api_key=api_key
54
+ )
55
+
56
+ @staticmethod
57
+ def can_provide() -> bool:
58
+ return API_KEY_ENV_NAME in os.environ
59
+
60
+ @property
61
+ def token(self) -> Token:
62
+ with TOKEN_REFRESH_LOCK:
63
+ if self._token.is_going_to_be_expired():
64
+ logger.info("Refreshing access token")
65
+ self._token = self._auth_service.refresh_token(
66
+ self._token, self.base_url
67
+ )
68
+ return self._token
69
+
70
+ @property
71
+ def base_url(self) -> str:
72
+ return self._host
73
+
74
+
75
+ class FileCredentialProvider(CredentialProvider):
76
+ def __init__(self) -> None:
77
+ logger.debug("Using file credential provider")
78
+ self._cred_file = CredentialsFileManager()
79
+
80
+ with self._cred_file:
81
+ self._last_cred_file_content = self._cred_file.read()
82
+ self._token = self._last_cred_file_content.to_token()
83
+ self._host = self._last_cred_file_content.host
84
+
85
+ self._auth_service = AuthServiceClient.from_base_url(base_url=self._host)
86
+
87
+ @staticmethod
88
+ def can_provide() -> bool:
89
+ with CredentialsFileManager() as cred_file:
90
+ return cred_file.exists()
91
+
92
+ @property
93
+ def token(self) -> Token:
94
+ with TOKEN_REFRESH_LOCK:
95
+ if not self._token.is_going_to_be_expired():
96
+ return self._token
97
+
98
+ logger.info("Refreshing access token")
99
+ with self._cred_file:
100
+ new_cred_file_content = self._cred_file.read()
101
+ new_token = new_cred_file_content.to_token()
102
+ new_host = new_cred_file_content.host
103
+
104
+ if new_cred_file_content == self._last_cred_file_content:
105
+ self._token = self._auth_service.refresh_token(
106
+ self._token, self.base_url
107
+ )
108
+ self._last_cred_file_content = CredentialsFileContent(
109
+ host=self._host,
110
+ access_token=self._token.access_token,
111
+ refresh_token=self._token.refresh_token,
112
+ )
113
+ self._cred_file.write(self._last_cred_file_content)
114
+ return self._token
115
+
116
+ if (
117
+ new_host == self._host
118
+ and new_token.to_user_info() == self._token.to_user_info()
119
+ ):
120
+ self._last_cred_file_content = new_cred_file_content
121
+ self._token = new_token
122
+ # recursive
123
+ return self.token
124
+
125
+ raise Exception(
126
+ "Credentials on disk changed while mlfoundry was running."
127
+ )
128
+
129
+ @property
130
+ def base_url(self) -> str:
131
+ return self._host
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from truefoundry.deploy.lib.auth.credential_provider import (
6
+ CredentialProvider,
7
+ EnvCredentialProvider,
8
+ FileCredentialProvider,
9
+ )
10
+ from truefoundry.deploy.lib.model.entity import UserInfo
11
+ from truefoundry.logger import logger
12
+
13
+ ACTIVE_SESSION: Optional[ServiceFoundrySession] = None
14
+
15
+
16
+ class ServiceFoundrySession:
17
+ def __init__(self) -> None:
18
+ self._cred_provider = self._get_cred_provider()
19
+ self._user_info: UserInfo = self._cred_provider.token.to_user_info()
20
+
21
+ global ACTIVE_SESSION
22
+ if (ACTIVE_SESSION is None) or (
23
+ ACTIVE_SESSION
24
+ and ACTIVE_SESSION.base_url != self.base_url
25
+ and ACTIVE_SESSION.user_info != self.user_info
26
+ ):
27
+ logger.info(
28
+ "Logged in to %r as %r (%s)",
29
+ self.base_url,
30
+ self.user_info.user_id,
31
+ self.user_info.email or self.user_info.user_type.value,
32
+ )
33
+ ACTIVE_SESSION = self
34
+
35
+ @staticmethod
36
+ def _get_cred_provider() -> CredentialProvider:
37
+ final_cred_provider = None
38
+ for cred_provider in [EnvCredentialProvider, FileCredentialProvider]:
39
+ if cred_provider.can_provide():
40
+ final_cred_provider = cred_provider()
41
+ break
42
+ if final_cred_provider is None:
43
+ raise Exception(
44
+ "Please login again using `tfy login --relogin`"
45
+ "or `tfy.login(relogin=True)` function"
46
+ )
47
+ return final_cred_provider
48
+
49
+ @property
50
+ def access_token(self) -> str:
51
+ return self._cred_provider.token.access_token
52
+
53
+ @property
54
+ def base_url(self) -> str:
55
+ return self._cred_provider.base_url
56
+
57
+ @property
58
+ def user_info(self) -> UserInfo:
59
+ return self._user_info
File without changes