truefoundry 0.2.10__py3-none-any.whl → 0.3.0rc2__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 (100) hide show
  1. truefoundry/__init__.py +1 -0
  2. truefoundry/autodeploy/cli.py +31 -18
  3. truefoundry/deploy/__init__.py +119 -1
  4. truefoundry/deploy/auto_gen/models.py +1791 -0
  5. truefoundry/deploy/builder/__init__.py +138 -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 +44 -0
  9. truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +51 -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 +19 -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 +99 -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 +79 -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 +87 -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 +92 -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 +81 -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 +723 -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 +246 -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 +23 -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 +382 -0
  74. truefoundry/deploy/lib/session.py +146 -0
  75. truefoundry/deploy/lib/util.py +70 -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 +232 -0
  80. truefoundry/deploy/v2/lib/deployable_patched_models.py +72 -0
  81. truefoundry/deploy/v2/lib/models.py +53 -0
  82. truefoundry/deploy/v2/lib/patched_models.py +515 -0
  83. truefoundry/deploy/v2/lib/source.py +267 -0
  84. truefoundry/flyte/__init__.py +6 -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-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/METADATA +25 -6
  95. truefoundry-0.3.0rc2.dist-info/RECORD +125 -0
  96. truefoundry/deploy/cli/deploy.py +0 -165
  97. truefoundry-0.2.10.dist-info/RECORD +0 -38
  98. /truefoundry/{deploy/cli/version.py → version.py} +0 -0
  99. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/WHEEL +0 -0
  100. {truefoundry-0.2.10.dist-info → truefoundry-0.3.0rc2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,382 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import time
5
+ from enum import Enum
6
+ from typing import Any, Dict, List, Optional, Union
7
+
8
+ import jwt
9
+
10
+ from truefoundry.deploy.lib.util import get_application_fqn_from_deployment_fqn
11
+ from truefoundry.pydantic_v1 import BaseModel, Extra, Field, constr, validator
12
+
13
+ # TODO: switch to Enums for str literals
14
+ # TODO: Need a better approach to keep fields in sync with server
15
+ # most fields should have a default in case server adds/removes a field
16
+ # TODO: Implement NotImplementedError sections
17
+ UNDEFINED_STRING = "<Undefined>"
18
+
19
+
20
+ class Base(BaseModel):
21
+ class Config:
22
+ validate_assignment = True
23
+ use_enum_values = True
24
+ extra = Extra.allow
25
+
26
+ def __repr_args__(self):
27
+ return [
28
+ (key, value)
29
+ for key, value in self.__dict__.items()
30
+ if key in self.__fields__
31
+ and self.__fields__[key].field_info.extra.get("repr", True)
32
+ ]
33
+
34
+
35
+ class Entity(Base):
36
+ createdAt: datetime.datetime = Field(repr=False)
37
+ updatedAt: datetime.datetime = Field(repr=False)
38
+ createdBy: str = Field(repr=False)
39
+
40
+
41
+ class Workspace(Entity):
42
+ id: str = Field(repr=False)
43
+ name: str
44
+ fqn: str
45
+ clusterId: str = Field(repr=False)
46
+
47
+ def list_row_data(self) -> Dict[str, Any]:
48
+ return {
49
+ "name": self.name,
50
+ "fqn": self.fqn,
51
+ "cluster_fqn": self.clusterId,
52
+ "created_at": self.createdAt,
53
+ }
54
+
55
+ def get_data(self) -> Dict[str, Any]:
56
+ return {
57
+ "name": self.name,
58
+ "fqn": self.fqn,
59
+ "cluster_fqn": self.clusterId,
60
+ "created_at": self.createdAt,
61
+ "updated_at": self.updatedAt,
62
+ "created_by": self.createdBy,
63
+ }
64
+
65
+
66
+ class WorkspaceResources(BaseModel):
67
+ cpu_limit: Optional[float]
68
+ memory_limit: Optional[int]
69
+ ephemeral_storage_limit: Optional[int]
70
+
71
+
72
+ class PortMetadata(BaseModel):
73
+ port: int
74
+ # TODO: done because of a bug, needs to reverted to be mandatory
75
+ host: Optional[str]
76
+
77
+
78
+ class DeploymentTransitionStatus(str, Enum):
79
+ INITIALIZED: str = "INITIALIZED"
80
+ BUILDING: str = "BUILDING"
81
+ DEPLOYING: str = "DEPLOYING"
82
+ BUILD_SUCCESS: str = "BUILD_SUCCESS"
83
+ DEPLOY_SUCCESS: str = "DEPLOY_SUCCESS"
84
+ DEPLOY_FAILED: str = "DEPLOY_FAILED"
85
+ BUILD_FAILED: str = "BUILD_FAILED"
86
+ CANCELLED: str = "CANCELLED"
87
+ FAILED: str = "FAILED"
88
+ REUSING_EXISTING_BUILD: str = "REUSING_EXISTING_BUILD"
89
+ REDEPLOY_STARTED: str = "REDEPLOY_STARTED"
90
+ PAUSED: str = "PAUSED"
91
+ ROLLOUT_STARTED: str = "ROLLOUT_STARTED"
92
+ SET_TRAFFIC: str = "SET_TRAFFIC"
93
+ DEPLOY_FAILED_WITH_RETRY: str = "DEPLOY_FAILED_WITH_RETRY"
94
+ WAITING: str = "WAITING"
95
+
96
+ _: str = ""
97
+
98
+ @classmethod
99
+ def is_failure_state(cls, state: DeploymentTransitionStatus) -> bool:
100
+ return state in (cls.DEPLOY_FAILED, cls.BUILD_FAILED, cls.FAILED, cls.CANCELLED)
101
+
102
+
103
+ class DeploymentState(BaseModel):
104
+ isTerminalState: bool
105
+
106
+
107
+ class DeploymentStatus(BaseModel):
108
+ state: DeploymentState
109
+ status: DeploymentTransitionStatus
110
+ transition: Optional[DeploymentTransitionStatus]
111
+
112
+
113
+ class DeploymentManifest(Base):
114
+ name: Optional[str] = Field(default=UNDEFINED_STRING)
115
+ type: str
116
+
117
+
118
+ class Deployment(Entity):
119
+ id: str = Field(repr=False)
120
+ fqn: str
121
+ version: int
122
+ currentStatusId: str = Field(repr=False)
123
+ applicationId: str = Field(repr=False)
124
+ manifest: DeploymentManifest = Field(repr=False)
125
+ failureReason: Optional[str]
126
+ deploymentStatuses: Optional[List[DeploymentStatus]]
127
+ metadata: Optional[
128
+ Union[Dict[str, Any], List[Dict[str, Any]]]
129
+ ] # TODO (chiragjn): revisit the type of this field
130
+ currentStatus: Optional[DeploymentStatus] # Server was not returning CurrentStatus
131
+ application_fqn: str
132
+
133
+ def __init__(self, **kwargs) -> None:
134
+ deployment_fqn = kwargs.get("fqn")
135
+ if not deployment_fqn:
136
+ raise ValueError("'fqn' field is required")
137
+ application_fqn = ":".join(deployment_fqn.split(":")[:3])
138
+ kwargs["application_fqn"] = application_fqn
139
+ super().__init__(**kwargs)
140
+
141
+ @property
142
+ def application_fqn(self) -> str:
143
+ return get_application_fqn_from_deployment_fqn(self.fqn)
144
+
145
+ def list_row_data(self) -> Dict[str, Any]:
146
+ return {
147
+ "fqn": self.fqn,
148
+ "application_name": self.manifest.name,
149
+ "version": self.version,
150
+ "created_at": self.createdAt,
151
+ }
152
+
153
+ def get_data(self) -> Dict[str, Any]:
154
+ # TODO: Remove this splitting logic
155
+ cluster_fqn, workspace_name, *_ = self.fqn.split(":")
156
+ workspace_fqn = ":".join([cluster_fqn, workspace_name])
157
+ return {
158
+ "fqn": self.fqn,
159
+ "application_name": self.manifest.name,
160
+ "application_type": self.manifest.type,
161
+ "version": self.version,
162
+ "workspace_fqn": workspace_fqn,
163
+ "cluster_fqn": cluster_fqn,
164
+ "created_at": self.createdAt,
165
+ "updated_at": self.updatedAt,
166
+ "created_by": self.createdBy,
167
+ }
168
+
169
+ class Config:
170
+ extra = Extra.allow
171
+
172
+
173
+ class ApplicationWorkspace(Base):
174
+ name: str
175
+ fqn: str
176
+ clusterId: str
177
+
178
+
179
+ class Application(Entity):
180
+ id: str = Field(repr=False)
181
+ name: str
182
+ fqn: str
183
+ tenantName: str
184
+ workspaceId: str = Field(repr=False)
185
+ lastVersion: int
186
+ activeVersion: int
187
+ workspace: ApplicationWorkspace
188
+ deployment: Deployment
189
+ activeDeploymentId: Optional[str] = Field(repr=False)
190
+ lastDeploymentId: str = Field(repr=False)
191
+
192
+ def list_row_data(self) -> Dict[str, Any]:
193
+ return {
194
+ "name": self.name,
195
+ "type": self.deployment.manifest.type,
196
+ "fqn": self.fqn,
197
+ "active_version": self.activeVersion,
198
+ "workspace_name": self.workspace.name,
199
+ "created_at": self.createdAt,
200
+ }
201
+
202
+ def get_data(self) -> Dict[str, Any]:
203
+ return {
204
+ "name": self.name,
205
+ "type": self.deployment.manifest.type,
206
+ "fqn": self.fqn,
207
+ "active_version": self.activeVersion,
208
+ "last_version": self.lastVersion,
209
+ "workspace_fqn": self.workspace.fqn,
210
+ "cluster_fqn": self.workspace.clusterId,
211
+ "created_at": self.createdAt,
212
+ "updated_at": self.updatedAt,
213
+ "created_by": self.createdBy,
214
+ }
215
+
216
+
217
+ class UserType(Enum):
218
+ user = "user"
219
+ serviceaccount = "serviceaccount"
220
+
221
+
222
+ class UserInfo(BaseModel):
223
+ user_id: constr(min_length=1)
224
+ user_type: UserType = UserType.user
225
+ email: Optional[str] = None
226
+ tenant_name: constr(min_length=1) = Field(alias="tenantName")
227
+
228
+ class Config:
229
+ allow_population_by_field_name = True
230
+ allow_mutation = False
231
+
232
+
233
+ class TenantInfo(BaseModel):
234
+ tenant_name: constr(min_length=1) = Field(alias="tenantName")
235
+ auth_server_url: str
236
+
237
+ class Config:
238
+ allow_population_by_field_name = True
239
+ allow_mutation = False
240
+
241
+
242
+ class Token(BaseModel):
243
+ access_token: constr(min_length=1) = Field(alias="accessToken", repr=False)
244
+ refresh_token: Optional[constr(min_length=1)] = Field(
245
+ alias="refreshToken", repr=False
246
+ )
247
+ decoded_value: Optional[Dict] = Field(exclude=True, repr=False)
248
+
249
+ class Config:
250
+ allow_population_by_field_name = True
251
+ allow_mutation = False
252
+
253
+ @validator("decoded_value", always=True, pre=True)
254
+ def _decode_jwt(cls, v, values, **kwargs):
255
+ access_token = values["access_token"]
256
+ return jwt.decode(
257
+ access_token,
258
+ options={
259
+ "verify_signature": False,
260
+ "verify_aud": False,
261
+ "verify_exp": False,
262
+ },
263
+ )
264
+
265
+ @property
266
+ def tenant_name(self) -> str:
267
+ return self.decoded_value["tenantName"]
268
+
269
+ def is_going_to_be_expired(self, buffer_in_seconds: int = 120) -> bool:
270
+ exp = int(self.decoded_value["exp"])
271
+ return (exp - time.time()) < buffer_in_seconds
272
+
273
+ def to_user_info(self) -> UserInfo:
274
+ return UserInfo(
275
+ user_id=self.decoded_value["username"],
276
+ email=self.decoded_value["email"]
277
+ if "email" in self.decoded_value
278
+ else None,
279
+ user_type=UserType(self.decoded_value.get("userType", UserType.user.value)),
280
+ tenant_name=self.tenant_name,
281
+ )
282
+
283
+
284
+ class CredentialsFileContent(BaseModel):
285
+ access_token: constr(min_length=1) = Field(repr=False)
286
+ refresh_token: Optional[constr(min_length=1)] = Field(repr=False)
287
+ host: constr(min_length=1)
288
+
289
+ class Config:
290
+ allow_mutation = False
291
+
292
+ def to_token(self) -> Token:
293
+ return Token(access_token=self.access_token, refresh_token=self.refresh_token)
294
+
295
+
296
+ class DeviceCode(BaseModel):
297
+ user_code: str = Field(alias="userCode")
298
+ device_code: str = Field(alias="deviceCode")
299
+
300
+ class Config:
301
+ allow_population_by_field_name = True
302
+ allow_mutation = False
303
+
304
+ def get_user_clickable_url(self, auth_host: str) -> str:
305
+ return f"{auth_host}/authorize/device?userCode={self.user_code}"
306
+
307
+
308
+ class JobRun(Base):
309
+ name: str
310
+ applicationName: str
311
+ deploymentVersion: int
312
+ createdAt: int
313
+ endTime: Optional[int] = None
314
+ duration: Optional[str] = None
315
+ command: str
316
+ totalRetries: Optional[int] = 0
317
+ status: str
318
+
319
+ def list_row_data(self) -> Dict[str, Any]:
320
+ from truefoundry.deploy.cli.display_util import display_time_passed
321
+
322
+ triggered_at = (
323
+ (datetime.datetime.now().timestamp() * 1000) - self.createdAt
324
+ ) // 1000
325
+ triggered_at = f"{display_time_passed(triggered_at)} ago"
326
+ duration = ""
327
+ if self.duration:
328
+ duration = display_time_passed(int(float(self.duration)))
329
+ return {
330
+ "name": self.name,
331
+ "deployment_version": self.deploymentVersion,
332
+ "status": self.status,
333
+ "triggered_at": triggered_at,
334
+ "duration": duration,
335
+ }
336
+
337
+ def get_data(self) -> Dict[str, Any]:
338
+ from truefoundry.deploy.cli.display_util import display_time_passed
339
+
340
+ created_at = datetime.datetime.fromtimestamp(self.createdAt // 1000)
341
+ end_time = ""
342
+ if self.endTime:
343
+ end_time = datetime.datetime.fromtimestamp(self.endTime // 1000)
344
+ duration = ""
345
+ if self.duration:
346
+ duration = display_time_passed(int(float(self.duration)))
347
+ return {
348
+ "name": self.name,
349
+ "application_name": self.applicationName,
350
+ "deployment_version": self.deploymentVersion,
351
+ "created_at": created_at,
352
+ "end_time": end_time,
353
+ "duration": duration,
354
+ "command": self.command,
355
+ "status": self.status,
356
+ }
357
+
358
+
359
+ class TriggerJobResult(Base):
360
+ message: str = Field(default="Unknown")
361
+ jobRunName: str = Field(default=None)
362
+
363
+
364
+ class DockerRegistryCredentials(Base):
365
+ fqn: str
366
+ registryUrl: str
367
+ username: str
368
+ password: str
369
+
370
+
371
+ class CreateDockerRepositoryResponse(Base):
372
+ repoName: str
373
+
374
+
375
+ class ApplyResult(BaseModel):
376
+ success: bool
377
+ message: str
378
+
379
+
380
+ class Manifest(Base):
381
+ type: str
382
+ name: str
@@ -0,0 +1,146 @@
1
+ import os
2
+ from typing import Optional
3
+
4
+ import rich_click as click
5
+
6
+ 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.servicefoundry_client import (
11
+ ServiceFoundryServiceClient,
12
+ )
13
+ from truefoundry.deploy.lib.clients.utils import resolve_base_url
14
+ from truefoundry.deploy.lib.const import (
15
+ API_KEY_ENV_NAME,
16
+ HOST_ENV_NAME,
17
+ OLD_SFY_PROFILES_FILEPATH,
18
+ OLD_SFY_SESSIONS_FILEPATH,
19
+ RICH_OUTPUT_CALLBACK,
20
+ )
21
+ from truefoundry.deploy.lib.messages import (
22
+ PROMPT_ALREADY_LOGGED_OUT,
23
+ PROMPT_LOGOUT_SUCCESSFUL,
24
+ )
25
+ from truefoundry.deploy.lib.model.entity import CredentialsFileContent, Token
26
+ from truefoundry.logger import logger
27
+
28
+ if OLD_SFY_PROFILES_FILEPATH.exists():
29
+ logger.warning(
30
+ "%s file is deprecated. You can delete this file now.",
31
+ OLD_SFY_PROFILES_FILEPATH,
32
+ )
33
+
34
+
35
+ if OLD_SFY_SESSIONS_FILEPATH.exists():
36
+ logger.warning(
37
+ "%s file is deprecated. You can delete this file now.",
38
+ OLD_SFY_SESSIONS_FILEPATH,
39
+ )
40
+
41
+
42
+ def login(
43
+ api_key: Optional[str] = None,
44
+ host: Optional[str] = None,
45
+ relogin: bool = False,
46
+ output_hook: OutputCallBack = RICH_OUTPUT_CALLBACK,
47
+ ) -> bool:
48
+ if API_KEY_ENV_NAME in os.environ and HOST_ENV_NAME in os.environ:
49
+ logger.warning(
50
+ "Skipping login because environment variables %s and "
51
+ "%s are set and will be used when running truefoundry. "
52
+ "If you want to relogin then unset these environment keys.",
53
+ HOST_ENV_NAME,
54
+ API_KEY_ENV_NAME,
55
+ )
56
+ return False
57
+
58
+ if EnvCredentialProvider.can_provide():
59
+ logger.warning(
60
+ "TFY_API_KEY env var is already set. "
61
+ "When running truefoundry, it will use the api key to authorize.\n"
62
+ "Login will just save the credentials on disk."
63
+ )
64
+
65
+ host = resolve_base_url(host).strip("/")
66
+
67
+ with CredentialsFileManager() as cred_file:
68
+ if not relogin and cred_file.exists():
69
+ cred_file_content = cred_file.read()
70
+ if host != cred_file_content.host:
71
+ if click.confirm(
72
+ f"Already logged in to {cred_file_content.host!r}\n"
73
+ f"Do you want to relogin to {host!r}?"
74
+ ):
75
+ return login(api_key=api_key, host=host, relogin=True)
76
+
77
+ user_info = cred_file_content.to_token().to_user_info()
78
+ user_name_display_info = user_info.email or user_info.user_type.value
79
+ output_hook.print_line(
80
+ f"Already logged in to {cred_file_content.host!r} as "
81
+ f"{user_info.user_id!r} ({user_name_display_info})\n"
82
+ "Please use `tfy login --relogin` or `tfy.login(relogin=True)` "
83
+ "to force relogin"
84
+ )
85
+ return False
86
+
87
+ if api_key:
88
+ servicefoundry_client = ServiceFoundryServiceClient(
89
+ init_session=False, base_url=host
90
+ )
91
+ token = _login_with_api_key(
92
+ api_key=api_key, servicefoundry_client=servicefoundry_client
93
+ )
94
+ else:
95
+ auth_service = AuthServiceClient(base_url=host)
96
+ # interactive login
97
+ token = _login_with_device_code(base_url=host, auth_service=auth_service)
98
+
99
+ cred_file_content = CredentialsFileContent(
100
+ access_token=token.access_token,
101
+ refresh_token=token.refresh_token,
102
+ host=host,
103
+ )
104
+ cred_file.write(cred_file_content)
105
+
106
+ user_info = token.to_user_info()
107
+ user_name_display_info = user_info.email or user_info.user_type.value
108
+ output_hook.print_line(
109
+ f"Successfully logged in to {cred_file_content.host!r} as "
110
+ f"{user_info.user_id!r} ({user_name_display_info})"
111
+ )
112
+ return True
113
+
114
+
115
+ def logout(
116
+ output_hook: OutputCallBack = RICH_OUTPUT_CALLBACK,
117
+ ) -> None:
118
+ with CredentialsFileManager() as cred_file:
119
+ if cred_file.delete():
120
+ output_hook.print_line(PROMPT_LOGOUT_SUCCESSFUL)
121
+ else:
122
+ output_hook.print_line(PROMPT_ALREADY_LOGGED_OUT)
123
+
124
+
125
+ def _login_with_api_key(
126
+ api_key: str, servicefoundry_client: ServiceFoundryServiceClient
127
+ ) -> Token:
128
+ logger.debug("Logging in with api key")
129
+ return servicefoundry_client.get_token_from_api_key(api_key=api_key)
130
+
131
+
132
+ def _login_with_device_code(
133
+ base_url: str,
134
+ auth_service: AuthServiceClient,
135
+ output_hook: OutputCallBack = RICH_OUTPUT_CALLBACK,
136
+ ) -> Token:
137
+ logger.debug("Logging in with device code")
138
+ device_code = auth_service.get_device_code()
139
+ url_to_go = device_code.get_user_clickable_url(auth_host=base_url)
140
+ output_hook.print_line(f"Opening:- {url_to_go}")
141
+ output_hook.print(
142
+ "Please click on the above link if it is not "
143
+ "automatically opened in a browser window."
144
+ )
145
+ click.launch(url_to_go)
146
+ return auth_service.get_token_from_device_code(device_code=device_code.device_code)
@@ -0,0 +1,70 @@
1
+ import os
2
+ import re
3
+ from typing import Union
4
+
5
+ from truefoundry.deploy.lib.const import (
6
+ SFY_DEBUG_ENV_KEY,
7
+ SFY_INTERNAL_ENV_KEY,
8
+ TFY_DEBUG_ENV_KEY,
9
+ TFY_INTERNAL_ENV_KEY,
10
+ )
11
+
12
+
13
+ def is_debug_env_set() -> bool:
14
+ return (
15
+ True if os.getenv(TFY_DEBUG_ENV_KEY) or os.getenv(SFY_DEBUG_ENV_KEY) else False
16
+ )
17
+
18
+
19
+ def is_internal_env_set() -> bool:
20
+ return (
21
+ True
22
+ if os.getenv(TFY_INTERNAL_ENV_KEY) or os.getenv(SFY_INTERNAL_ENV_KEY)
23
+ else False
24
+ )
25
+
26
+
27
+ def get_application_fqn_from_deployment_fqn(deployment_fqn: str) -> str:
28
+ if not re.search(r":\d+$", deployment_fqn):
29
+ raise ValueError(
30
+ "Invalid `deployment_fqn` format. A deployment fqn is supposed to end with a version number"
31
+ )
32
+ application_fqn, _ = deployment_fqn.rsplit(":", 1)
33
+ return application_fqn
34
+
35
+
36
+ def get_deployment_fqn_from_application_fqn(
37
+ application_fqn: str, version: Union[str, int]
38
+ ) -> str:
39
+ return f"{application_fqn}:{version}"
40
+
41
+
42
+ def is_notebook():
43
+ # https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook
44
+ try:
45
+ # noinspection PyUnresolvedReferences
46
+ shell = get_ipython().__class__
47
+ if shell.__name__ == "ZMQInteractiveShell":
48
+ return True # Jupyter notebook or qtconsole
49
+ elif shell.__name__ == "TerminalInteractiveShell":
50
+ return False # Terminal running IPython
51
+ elif "google.colab" in str(shell):
52
+ return True # google colab notebook
53
+ else:
54
+ return False # Other type (?)
55
+ except NameError:
56
+ return False # Probably standard Python interpreter
57
+
58
+
59
+ def find_list_paths(data, parent_key="", sep="."):
60
+ list_paths = []
61
+ if isinstance(data, dict):
62
+ for key, value in data.items():
63
+ new_key = f"{parent_key}{sep}{key}" if parent_key else key
64
+ list_paths.extend(find_list_paths(value, new_key, sep))
65
+ elif isinstance(data, list):
66
+ list_paths.append(parent_key)
67
+ for i, value in enumerate(data):
68
+ new_key = f"{parent_key}[{i}]"
69
+ list_paths.extend(find_list_paths(value, new_key, sep))
70
+ return list_paths