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,746 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import json
5
+ import os
6
+ import time
7
+ from datetime import datetime, timezone
8
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
9
+ from urllib.parse import urljoin, urlparse
10
+
11
+ import requests
12
+ import socketio
13
+ from dateutil.tz import tzlocal
14
+ from packaging import version
15
+ from rich.status import Status
16
+ from tqdm import tqdm
17
+ from tqdm.utils import CallbackIOWrapper
18
+
19
+ from truefoundry.deploy.io.output_callback import OutputCallBack
20
+ from truefoundry.deploy.lib.auth.servicefoundry_session import ServiceFoundrySession
21
+ from truefoundry.deploy.lib.clients.utils import request_handling
22
+ from truefoundry.deploy.lib.const import API_SERVER_RELATIVE_PATH, VERSION_PREFIX
23
+ from truefoundry.deploy.lib.model.entity import (
24
+ Application,
25
+ CreateDockerRepositoryResponse,
26
+ Deployment,
27
+ DockerRegistryCredentials,
28
+ JobRun,
29
+ PythonSDKConfig,
30
+ TenantInfo,
31
+ Token,
32
+ TriggerJobResult,
33
+ Workspace,
34
+ WorkspaceResources,
35
+ )
36
+ from truefoundry.deploy.lib.util import timed_lru_cache
37
+ from truefoundry.deploy.lib.win32 import allow_interrupt
38
+ from truefoundry.deploy.v2.lib.models import (
39
+ AppDeploymentStatusResponse,
40
+ ApplicationFqnResponse,
41
+ BuildResponse,
42
+ DeploymentFqnResponse,
43
+ )
44
+ from truefoundry.logger import logger
45
+ from truefoundry.pydantic_v1 import parse_obj_as
46
+ from truefoundry.version import __version__
47
+
48
+ DEPLOYMENT_LOGS_SUBSCRIBE_MESSAGE = "DEPLOYMENT_LOGS"
49
+ BUILD_LOGS_SUBSCRIBE_MESSAGE = "BUILD_LOGS"
50
+
51
+ if TYPE_CHECKING:
52
+ from truefoundry.deploy.auto_gen.models import Application
53
+
54
+
55
+ def _upload_packaged_code(metadata, package_file):
56
+ file_size = os.stat(package_file).st_size
57
+ with open(package_file, "rb") as file_to_upload:
58
+ with tqdm(
59
+ total=file_size,
60
+ unit="B",
61
+ unit_scale=True,
62
+ unit_divisor=1024,
63
+ desc="Uploading package",
64
+ ) as progress_bar:
65
+ wrapped_file = CallbackIOWrapper(
66
+ progress_bar.update, file_to_upload, "read"
67
+ )
68
+ headers = metadata.get("headers", {})
69
+ http_response = requests.put(
70
+ metadata["url"], data=wrapped_file, headers=headers
71
+ )
72
+
73
+ if http_response.status_code not in [204, 201, 200]:
74
+ raise RuntimeError(f"Failed to upload code {http_response.content}")
75
+
76
+
77
+ def check_min_cli_version(fn):
78
+ @functools.wraps(fn)
79
+ def inner(*args, **kwargs):
80
+ if __version__ != "0.0.0":
81
+ client: "ServiceFoundryServiceClient" = args[0]
82
+ # "0.0.0" indicates dev version
83
+ # noinspection PyProtectedMember
84
+ min_cli_version_required = client._min_cli_version_required
85
+ if version.parse(__version__) < version.parse(min_cli_version_required):
86
+ raise Exception(
87
+ "You are using an outdated version of `truefoundry`.\n"
88
+ f"Run `pip install truefoundry>={min_cli_version_required}` to install the supported version.",
89
+ )
90
+ else:
91
+ logger.debug("Ignoring minimum cli version check")
92
+
93
+ return fn(*args, **kwargs)
94
+
95
+ return inner
96
+
97
+
98
+ @timed_lru_cache(seconds=30 * 60)
99
+ def _cached_get_tenant_info(api_server_url: str) -> TenantInfo:
100
+ res = requests.get(
101
+ url=f"{api_server_url}/v1/tenant-id",
102
+ params={"hostName": urlparse(api_server_url).netloc},
103
+ )
104
+ res = request_handling(res)
105
+ return TenantInfo.parse_obj(res)
106
+
107
+
108
+ @timed_lru_cache(seconds=30 * 60)
109
+ def _cached_get_python_sdk_config(api_server_url: str) -> PythonSDKConfig:
110
+ url = f"{api_server_url}/v1/min-cli-version"
111
+ res = requests.get(url)
112
+ res = request_handling(res)
113
+ return PythonSDKConfig.parse_obj(res)
114
+
115
+
116
+ class ServiceFoundryServiceClient:
117
+ def __init__(self, init_session: bool = True, base_url: Optional[str] = None):
118
+ self._session: Optional[ServiceFoundrySession] = None
119
+ if init_session:
120
+ if base_url:
121
+ logger.warning("Passed base url %r will be ignored", base_url)
122
+ self._session = ServiceFoundrySession()
123
+ base_url = self._session.base_url
124
+ elif not base_url:
125
+ raise Exception("Neither session, not base_url provided")
126
+
127
+ self._base_url = base_url.strip("/")
128
+ self._api_server_url = f"{self._base_url}/{API_SERVER_RELATIVE_PATH}"
129
+
130
+ @property
131
+ def base_url(self) -> str:
132
+ return self._base_url
133
+
134
+ @property
135
+ def tenant_info(self) -> TenantInfo:
136
+ return _cached_get_tenant_info(self._api_server_url)
137
+
138
+ @property
139
+ def python_sdk_config(self) -> PythonSDKConfig:
140
+ return _cached_get_python_sdk_config(self._api_server_url)
141
+
142
+ @functools.cached_property
143
+ def _min_cli_version_required(self) -> str:
144
+ return _cached_get_python_sdk_config(
145
+ self._api_server_url
146
+ ).truefoundry_cli_min_version
147
+
148
+ def _get_header(self):
149
+ if not self._session:
150
+ return {}
151
+ return {"Authorization": f"Bearer {self._session.access_token}"}
152
+
153
+ @check_min_cli_version
154
+ def get_id_from_fqn(self, fqn_type: str, fqn: str):
155
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/fqn/{fqn_type}"
156
+ res = requests.get(url, headers=self._get_header(), params={"fqn": fqn})
157
+ return request_handling(res)
158
+
159
+ @check_min_cli_version
160
+ def list_workspace(self):
161
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace"
162
+ res = requests.get(url, headers=self._get_header())
163
+ return request_handling(res)
164
+
165
+ @check_min_cli_version
166
+ def list_workspaces(
167
+ self,
168
+ cluster_id: Optional[str] = None,
169
+ workspace_name: Optional[str] = None,
170
+ workspace_fqn: Optional[str] = None,
171
+ ) -> List[Workspace]:
172
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace"
173
+ params = {}
174
+ if cluster_id:
175
+ params["clusterId"] = cluster_id
176
+ if workspace_name:
177
+ params["workspaceName"] = workspace_name
178
+ if workspace_fqn:
179
+ params["workspaceFqn"] = workspace_fqn
180
+ res = requests.get(url, params=params, headers=self._get_header())
181
+ response = request_handling(res)
182
+ return parse_obj_as(List[Workspace], response)
183
+
184
+ @check_min_cli_version
185
+ def create_workspace(
186
+ self,
187
+ workspace_name: str,
188
+ cluster_name: str,
189
+ resources: WorkspaceResources,
190
+ ) -> Workspace:
191
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace"
192
+ res = requests.post(
193
+ url,
194
+ json={
195
+ "manifest": {
196
+ "cluster": cluster_name,
197
+ "name": workspace_name,
198
+ "resources": resources.dict(exclude_none=True),
199
+ }
200
+ },
201
+ headers=self._get_header(),
202
+ )
203
+ res = request_handling(res)
204
+ return Workspace.parse_obj(res)
205
+
206
+ @check_min_cli_version
207
+ def remove_workspace(self, workspace_id, force=False) -> Workspace:
208
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace/{workspace_id}"
209
+ force = json.dumps(
210
+ force
211
+ ) # this dumb conversion is required because `params` just casts as str
212
+ res = requests.delete(url, headers=self._get_header(), params={"force": force})
213
+ response = request_handling(res)
214
+ return Workspace.parse_obj(response["workspace"])
215
+
216
+ @check_min_cli_version
217
+ def get_workspace_by_name(self, workspace_name, cluster_id):
218
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace"
219
+ res = requests.get(
220
+ url,
221
+ headers=self._get_header(),
222
+ params={"name": workspace_name, "clusterId": cluster_id},
223
+ )
224
+ return request_handling(res)
225
+
226
+ @check_min_cli_version
227
+ def get_workspace(self, workspace_id):
228
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace/{workspace_id}"
229
+ res = requests.get(url, headers=self._get_header())
230
+ return request_handling(res)
231
+
232
+ @check_min_cli_version
233
+ def get_workspace_by_fqn(self, workspace_fqn: str) -> List[Workspace]:
234
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workspace"
235
+ res = requests.get(
236
+ url,
237
+ headers=self._get_header(),
238
+ params={"fqn": workspace_fqn},
239
+ )
240
+ response = request_handling(res)
241
+ return parse_obj_as(List[Workspace], response)
242
+
243
+ @check_min_cli_version
244
+ def list_deployments(self, workspace_id: str = None):
245
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/deployment"
246
+ params = {}
247
+ if workspace_id:
248
+ params["workspaceId"] = workspace_id
249
+ res = requests.get(url=url, params=params, headers=self._get_header())
250
+ return request_handling(res)
251
+
252
+ @check_min_cli_version
253
+ def list_cluster(self):
254
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/cluster"
255
+ res = requests.get(url, headers=self._get_header())
256
+ return request_handling(res)
257
+
258
+ @check_min_cli_version
259
+ def get_cluster(self, cluster_id):
260
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/cluster/{cluster_id}"
261
+ res = requests.get(url, headers=self._get_header())
262
+ return request_handling(res)
263
+
264
+ @check_min_cli_version
265
+ def get_presigned_url(self, space_name, service_name, env):
266
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/deployment/code-upload-url"
267
+ res = requests.post(
268
+ url,
269
+ json={
270
+ "workspaceFqn": space_name,
271
+ "serviceName": service_name,
272
+ "stage": env,
273
+ },
274
+ headers=self._get_header(),
275
+ )
276
+ return request_handling(res)
277
+
278
+ @check_min_cli_version
279
+ def upload_code_package(
280
+ self, workspace_fqn: str, component_name: str, package_local_path: str
281
+ ) -> str:
282
+ http_response = self.get_presigned_url(
283
+ space_name=workspace_fqn, service_name=component_name, env="default"
284
+ )
285
+ _upload_packaged_code(metadata=http_response, package_file=package_local_path)
286
+
287
+ return http_response["uri"]
288
+
289
+ @check_min_cli_version
290
+ def deploy_application(
291
+ self, workspace_id: str, application: Application
292
+ ) -> Deployment:
293
+ data = {
294
+ "workspaceId": workspace_id,
295
+ "name": application.name,
296
+ "manifest": application.dict(exclude_none=True),
297
+ }
298
+ logger.debug(json.dumps(data))
299
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/deployment"
300
+ deploy_response = requests.post(url, json=data, headers=self._get_header())
301
+ response = request_handling(deploy_response)
302
+ return Deployment.parse_obj(response["deployment"])
303
+
304
+ def _get_log_print_line(self, log: dict):
305
+ timestamp = int(log["time"]) / 1e6
306
+
307
+ time_obj = datetime.fromtimestamp(timestamp / 1000.0, tz=timezone.utc)
308
+ time_obj.replace(tzinfo=timezone.utc)
309
+ local_time = time_obj.astimezone(tzlocal())
310
+ local_time_str = local_time.isoformat()
311
+ return f'[{local_time_str}] {log["log"].strip()}'
312
+
313
+ def _tail_logs(
314
+ self,
315
+ tail_logs_url: str,
316
+ query_dict: dict,
317
+ # NOTE: Rather making this printer callback an argument,
318
+ # we should have global printer callback
319
+ # which will be initialized based on the running env (cli, lib, notebook)
320
+ subscribe_message: str,
321
+ socketio_path: str = "socket.io",
322
+ callback=None,
323
+ wait=True,
324
+ ):
325
+ callback = callback or OutputCallBack()
326
+ sio = socketio.Client(request_timeout=60)
327
+ callback.print_line("Waiting for the task to start...")
328
+
329
+ @sio.on(subscribe_message)
330
+ def logs(data):
331
+ try:
332
+ _log = json.loads(data)
333
+ callback.print_line(self._get_log_print_line(_log["body"]))
334
+ except Exception:
335
+ logger.exception(f"Error while parsing log line, {data!r}")
336
+
337
+ def sio_disconnect_no_exception():
338
+ try:
339
+ sio.disconnect()
340
+ except Exception:
341
+ logger.exception("Error while disconnecting from socket connection")
342
+
343
+ with allow_interrupt(sio_disconnect_no_exception):
344
+ sio.connect(
345
+ tail_logs_url,
346
+ transports="websocket",
347
+ headers=self._get_header(),
348
+ socketio_path=socketio_path,
349
+ )
350
+ # TODO: We should have have a timeout here. `emit` does
351
+ # not support timeout. Explore `sio.call`.
352
+ sio.emit(
353
+ subscribe_message,
354
+ json.dumps(query_dict),
355
+ )
356
+ if wait:
357
+ sio.wait()
358
+
359
+ @check_min_cli_version
360
+ def get_deployment(self, application_id: str, deployment_id: str) -> Deployment:
361
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}/deployments/{deployment_id}"
362
+ res = requests.get(url, headers=self._get_header())
363
+ res = request_handling(res)
364
+ return Deployment.parse_obj(res)
365
+
366
+ @check_min_cli_version
367
+ def get_deployment_statuses(
368
+ self, application_id: str, deployment_id: str
369
+ ) -> List[AppDeploymentStatusResponse]:
370
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}/deployments/{deployment_id}/statuses"
371
+ res = requests.get(url, headers=self._get_header())
372
+ res = request_handling(res)
373
+ return parse_obj_as(List[AppDeploymentStatusResponse], res)
374
+
375
+ @check_min_cli_version
376
+ def get_deployment_build_response(
377
+ self, application_id: str, deployment_id: str
378
+ ) -> List[BuildResponse]:
379
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}/deployments/{deployment_id}/builds"
380
+ res = requests.get(url, headers=self._get_header())
381
+ res = request_handling(res)
382
+ return parse_obj_as(List[BuildResponse], res)
383
+
384
+ def _get_deployment_logs(
385
+ self,
386
+ workspace_id: str,
387
+ application_id: str,
388
+ deployment_id: str,
389
+ job_run_name: Optional[str] = None,
390
+ start_ts_nano: Optional[int] = None,
391
+ end_ts_nano: Optional[int] = None,
392
+ limit: Optional[int] = None,
393
+ num_logs_to_ignore: Optional[int] = None,
394
+ ) -> List:
395
+ get_logs_query = {"applicationId": application_id}
396
+ if deployment_id:
397
+ get_logs_query["deploymentId"] = deployment_id
398
+ data = {"getLogsQuery": json.dumps(get_logs_query)}
399
+ if start_ts_nano:
400
+ data["startTs"] = str(start_ts_nano)
401
+ if end_ts_nano:
402
+ data["endTs"] = str(end_ts_nano)
403
+ if limit:
404
+ data["limit"] = str(limit)
405
+ if num_logs_to_ignore:
406
+ data["numLogsToIgnore"] = int(num_logs_to_ignore)
407
+ if job_run_name:
408
+ data["jobRunName"] = job_run_name
409
+
410
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/logs/{workspace_id}"
411
+ res = requests.get(url=url, params=data, headers=self._get_header())
412
+ res = request_handling(res)
413
+ return list(res["logs"])
414
+
415
+ @check_min_cli_version
416
+ def tail_build_logs(
417
+ self,
418
+ build_response: BuildResponse,
419
+ callback=None,
420
+ wait: bool = True,
421
+ ):
422
+ callback = callback or OutputCallBack()
423
+ tail_logs_obj = json.loads(build_response.tailLogsUrl)
424
+ self._tail_logs(
425
+ tail_logs_url=urljoin(
426
+ tail_logs_obj["uri"], f"/?type={BUILD_LOGS_SUBSCRIBE_MESSAGE}"
427
+ ),
428
+ socketio_path=tail_logs_obj["path"],
429
+ query_dict={
430
+ "pipelineRunName": build_response.name,
431
+ "startTs": build_response.logsStartTs,
432
+ },
433
+ callback=callback,
434
+ wait=wait,
435
+ subscribe_message=BUILD_LOGS_SUBSCRIBE_MESSAGE,
436
+ )
437
+
438
+ @check_min_cli_version
439
+ def tail_logs_for_deployment(
440
+ self,
441
+ workspace_id: str,
442
+ application_id: str,
443
+ deployment_id: str,
444
+ start_ts: int,
445
+ limit: int,
446
+ callback=None,
447
+ wait: bool = True,
448
+ ):
449
+ callback = callback or OutputCallBack()
450
+ self._tail_logs(
451
+ tail_logs_url=urljoin(
452
+ self._api_server_url, f"/?type={DEPLOYMENT_LOGS_SUBSCRIBE_MESSAGE}"
453
+ ),
454
+ query_dict={
455
+ "workspaceId": workspace_id,
456
+ "startTs": str(int(start_ts * 1e6)),
457
+ "limit": limit,
458
+ "getLogsQuery": {
459
+ "applicationId": application_id,
460
+ "deploymentId": deployment_id,
461
+ },
462
+ },
463
+ callback=callback,
464
+ wait=wait,
465
+ subscribe_message=DEPLOYMENT_LOGS_SUBSCRIBE_MESSAGE,
466
+ )
467
+
468
+ @check_min_cli_version
469
+ def poll_logs_for_deployment(
470
+ self,
471
+ workspace_id: str,
472
+ application_id: str,
473
+ deployment_id: str,
474
+ job_run_name: Optional[str],
475
+ start_ts: int,
476
+ limit: int,
477
+ poll_interval_seconds: int,
478
+ callback=None,
479
+ ):
480
+ callback = callback or OutputCallBack()
481
+ start_ts_nano = int(start_ts * 1e6)
482
+
483
+ with Status(status="Polling for logs") as spinner:
484
+ num_logs_to_ignore = 0
485
+
486
+ while True:
487
+ logs_list = self._get_deployment_logs(
488
+ workspace_id=workspace_id,
489
+ application_id=application_id,
490
+ deployment_id=deployment_id,
491
+ job_run_name=job_run_name,
492
+ start_ts_nano=start_ts_nano,
493
+ limit=limit,
494
+ num_logs_to_ignore=num_logs_to_ignore,
495
+ )
496
+
497
+ if len(logs_list) == 0:
498
+ logger.warning("Did not receive any logs")
499
+ time.sleep(poll_interval_seconds)
500
+ continue
501
+
502
+ for log in logs_list:
503
+ callback.print_line(self._get_log_print_line(log))
504
+
505
+ last_log_time = logs_list[-1]["time"]
506
+ num_logs_to_ignore = 0
507
+ for log in reversed(logs_list):
508
+ if log["time"] != last_log_time:
509
+ break
510
+ num_logs_to_ignore += 1
511
+
512
+ start_ts_nano = int(last_log_time)
513
+ spinner.update(status=f"Waiting for {poll_interval_seconds} secs.")
514
+ time.sleep(poll_interval_seconds)
515
+
516
+ @check_min_cli_version
517
+ def fetch_deployment_logs(
518
+ self,
519
+ workspace_id: str,
520
+ application_id: str,
521
+ deployment_id: str,
522
+ job_run_name: Optional[str],
523
+ start_ts: Optional[int],
524
+ end_ts: Optional[int],
525
+ limit: Optional[int],
526
+ callback=None,
527
+ ):
528
+ callback = callback or OutputCallBack()
529
+ logs_list = self._get_deployment_logs(
530
+ workspace_id=workspace_id,
531
+ application_id=application_id,
532
+ deployment_id=deployment_id,
533
+ job_run_name=job_run_name,
534
+ start_ts_nano=int(start_ts * 1e6),
535
+ end_ts_nano=int(end_ts * 1e6),
536
+ limit=limit,
537
+ )
538
+ for log in logs_list:
539
+ callback.print_line(self._get_log_print_line(log))
540
+
541
+ @check_min_cli_version
542
+ def fetch_build_logs(
543
+ self,
544
+ build_response: BuildResponse,
545
+ callback=None,
546
+ ) -> None:
547
+ callback = callback or OutputCallBack()
548
+ url = build_response.getLogsUrl
549
+ res = requests.get(url=url, headers=self._get_header())
550
+ logs_list = request_handling(res)
551
+ for log in logs_list["logs"]:
552
+ # TODO: Have to establish a log line format that includes timestamp, level, message
553
+ callback.print_line(self._get_log_print_line(log))
554
+
555
+ @check_min_cli_version
556
+ def get_deployment_info_by_fqn(self, deployment_fqn: str) -> DeploymentFqnResponse:
557
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/fqn/deployment"
558
+ res = requests.get(
559
+ url, headers=self._get_header(), params={"fqn": deployment_fqn}
560
+ )
561
+ res = request_handling(res)
562
+ return DeploymentFqnResponse.parse_obj(res)
563
+
564
+ @check_min_cli_version
565
+ def get_application_info_by_fqn(
566
+ self, application_fqn: str
567
+ ) -> ApplicationFqnResponse:
568
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/fqn/app"
569
+ res = requests.get(
570
+ url, headers=self._get_header(), params={"fqn": application_fqn}
571
+ )
572
+ res = request_handling(res)
573
+ return ApplicationFqnResponse.parse_obj(res)
574
+
575
+ @check_min_cli_version
576
+ def remove_application(self, application_id: str):
577
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}"
578
+ res = requests.delete(url, headers=self._get_header())
579
+ response = request_handling(res)
580
+ # TODO: Add pydantic here.
581
+ return response
582
+
583
+ @check_min_cli_version
584
+ def get_application_info(self, application_id: str) -> Application:
585
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}"
586
+ res = requests.get(url, headers=self._get_header())
587
+ response = request_handling(res)
588
+ return Application.parse_obj(response)
589
+
590
+ def list_job_runs(
591
+ self,
592
+ application_id: str,
593
+ limit: Optional[int] = None,
594
+ offset: Optional[int] = None,
595
+ search_prefix: Optional[str] = None,
596
+ ) -> List[JobRun]:
597
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/jobs/{application_id}/runs"
598
+ params = {}
599
+ if limit:
600
+ params["limit"] = limit
601
+ if offset:
602
+ params["offset"] = offset
603
+ if search_prefix:
604
+ params["searchPrefix"] = search_prefix
605
+ res = requests.get(url, headers=self._get_header(), params=params)
606
+ res = request_handling(res)
607
+ return parse_obj_as(List[JobRun], res["data"])
608
+
609
+ def get_job_run(
610
+ self,
611
+ application_id: str,
612
+ job_run_name: str,
613
+ ):
614
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/jobs/{application_id}/runs/{job_run_name}"
615
+ res = requests.get(url, headers=self._get_header())
616
+ res = request_handling(res)
617
+ return parse_obj_as(JobRun, res)
618
+
619
+ def trigger_job(
620
+ self,
621
+ deployment_id: str,
622
+ command: Optional[str] = None,
623
+ params: Optional[Dict[str, str]] = None,
624
+ ) -> TriggerJobResult:
625
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/jobs/trigger"
626
+ body = {
627
+ "deploymentId": deployment_id,
628
+ "input": {},
629
+ }
630
+ if command:
631
+ body["input"]["command"] = command
632
+ if params:
633
+ body["input"]["params"] = params
634
+ res = requests.post(url, json=body, headers=self._get_header())
635
+ response = request_handling(res)
636
+ return TriggerJobResult.parse_obj(response)
637
+
638
+ def trigger_workflow(self, application_id: str, inputs: Dict[str, Any]):
639
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/workflow/{application_id}/executions"
640
+ res = requests.post(url, json=inputs, headers=self._get_header())
641
+ response = request_handling(res)
642
+ return response
643
+
644
+ @check_min_cli_version
645
+ def get_docker_registry_creds(
646
+ self, docker_registry_fqn: str, cluster_id: str
647
+ ) -> DockerRegistryCredentials:
648
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/docker-registry/creds"
649
+ res = requests.get(
650
+ url,
651
+ headers=self._get_header(),
652
+ params={
653
+ "fqn": docker_registry_fqn,
654
+ "clusterId": cluster_id,
655
+ },
656
+ )
657
+ response = request_handling(res)
658
+ return DockerRegistryCredentials.parse_obj(response)
659
+
660
+ @check_min_cli_version
661
+ def create_repo_in_registry(
662
+ self, docker_registry_fqn: str, workspace_fqn: str, application_name: str
663
+ ) -> CreateDockerRepositoryResponse:
664
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/docker-registry/create-repo"
665
+ res = requests.post(
666
+ url,
667
+ headers=self._get_header(),
668
+ data={
669
+ "fqn": docker_registry_fqn,
670
+ "workspaceFqn": workspace_fqn,
671
+ "applicationName": application_name,
672
+ },
673
+ )
674
+ response = request_handling(res)
675
+ return CreateDockerRepositoryResponse.parse_obj(response)
676
+
677
+ @check_min_cli_version
678
+ def list_applications(
679
+ self,
680
+ application_id: Optional[str] = None,
681
+ workspace_id: Optional[str] = None,
682
+ application_name: Optional[str] = None,
683
+ ) -> List[Application]:
684
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/app"
685
+ params = {}
686
+ if application_id:
687
+ params["applicationId"] = application_id
688
+ if workspace_id:
689
+ params["workspaceId"] = workspace_id
690
+ if application_name:
691
+ params["applicationName"] = application_name
692
+ res = requests.get(url, params=params, headers=self._get_header())
693
+ response = request_handling(res)
694
+ return parse_obj_as(List[Application], response)
695
+
696
+ @check_min_cli_version
697
+ def list_versions(
698
+ self,
699
+ application_id: str,
700
+ deployment_version: Optional[int] = None,
701
+ deployment_id: Optional[str] = None,
702
+ ) -> List[Deployment]:
703
+ url = (
704
+ f"{self._api_server_url}/{VERSION_PREFIX}/app/{application_id}/deployments"
705
+ )
706
+ params = {}
707
+ if deployment_version:
708
+ params["version"] = deployment_version
709
+ if deployment_id:
710
+ params["deploymentId"] = deployment_id
711
+ res = requests.get(url, params=params, headers=self._get_header())
712
+ response = request_handling(res)
713
+ return parse_obj_as(List[Deployment], response)
714
+
715
+ @check_min_cli_version
716
+ def get_token_from_api_key(self, api_key: str) -> Token:
717
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/oauth/api-key/token"
718
+ data = {"apiKey": api_key}
719
+ res = requests.get(url, params=data)
720
+ res = request_handling(res)
721
+ return Token.parse_obj(res)
722
+
723
+ @check_min_cli_version
724
+ def apply(self, manifest: Dict[str, Any]) -> None:
725
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/apply"
726
+ body = {"manifest": manifest}
727
+ res = requests.put(url, headers=self._get_header(), json=body)
728
+ res = request_handling(res)
729
+ return res
730
+
731
+ def terminate_job_run(
732
+ self,
733
+ deployment_id: str,
734
+ job_run_name: str,
735
+ callback=None,
736
+ ):
737
+ callback = callback or OutputCallBack()
738
+ url = f"{self._api_server_url}/{VERSION_PREFIX}/jobs/terminate?deploymentId={deployment_id}&jobRunName={job_run_name}"
739
+ body = {
740
+ "deploymentId": deployment_id,
741
+ "jobRunName": job_run_name,
742
+ }
743
+ res = requests.post(url, json=body, headers=self._get_header())
744
+ res = request_handling(res)
745
+
746
+ return res