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.
- truefoundry/__init__.py +1 -0
- truefoundry/autodeploy/cli.py +31 -18
- truefoundry/deploy/__init__.py +112 -1
- truefoundry/deploy/auto_gen/models.py +1714 -0
- truefoundry/deploy/builder/__init__.py +134 -0
- truefoundry/deploy/builder/builders/__init__.py +22 -0
- truefoundry/deploy/builder/builders/dockerfile.py +57 -0
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py +46 -0
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +66 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +44 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +158 -0
- truefoundry/deploy/builder/docker_service.py +168 -0
- truefoundry/deploy/cli/cli.py +21 -26
- truefoundry/deploy/cli/commands/__init__.py +18 -0
- truefoundry/deploy/cli/commands/apply_command.py +52 -0
- truefoundry/deploy/cli/commands/build_command.py +45 -0
- truefoundry/deploy/cli/commands/build_logs_command.py +89 -0
- truefoundry/deploy/cli/commands/create_command.py +75 -0
- truefoundry/deploy/cli/commands/delete_command.py +77 -0
- truefoundry/deploy/cli/commands/deploy_command.py +102 -0
- truefoundry/deploy/cli/commands/get_command.py +216 -0
- truefoundry/deploy/cli/commands/list_command.py +171 -0
- truefoundry/deploy/cli/commands/login_command.py +33 -0
- truefoundry/deploy/cli/commands/logout_command.py +20 -0
- truefoundry/deploy/cli/commands/logs_command.py +134 -0
- truefoundry/deploy/cli/commands/patch_application_command.py +81 -0
- truefoundry/deploy/cli/commands/patch_command.py +70 -0
- truefoundry/deploy/cli/commands/redeploy_command.py +41 -0
- truefoundry/deploy/cli/commands/terminate_comand.py +44 -0
- truefoundry/deploy/cli/commands/trigger_command.py +145 -0
- truefoundry/deploy/cli/config.py +10 -0
- truefoundry/deploy/cli/console.py +5 -0
- truefoundry/deploy/cli/const.py +12 -0
- truefoundry/deploy/cli/display_util.py +118 -0
- truefoundry/deploy/cli/util.py +129 -0
- truefoundry/deploy/core/__init__.py +7 -0
- truefoundry/deploy/core/login.py +9 -0
- truefoundry/deploy/core/logout.py +5 -0
- truefoundry/deploy/function_service/__init__.py +3 -0
- truefoundry/deploy/function_service/__main__.py +27 -0
- truefoundry/deploy/function_service/app.py +92 -0
- truefoundry/deploy/function_service/build.py +45 -0
- truefoundry/deploy/function_service/remote/__init__.py +6 -0
- truefoundry/deploy/function_service/remote/context.py +3 -0
- truefoundry/deploy/function_service/remote/method.py +67 -0
- truefoundry/deploy/function_service/remote/remote.py +144 -0
- truefoundry/deploy/function_service/route.py +137 -0
- truefoundry/deploy/function_service/service.py +113 -0
- truefoundry/deploy/function_service/utils.py +53 -0
- truefoundry/deploy/io/__init__.py +0 -0
- truefoundry/deploy/io/output_callback.py +23 -0
- truefoundry/deploy/io/rich_output_callback.py +27 -0
- truefoundry/deploy/json_util.py +7 -0
- truefoundry/deploy/lib/__init__.py +0 -0
- truefoundry/deploy/lib/auth/auth_service_client.py +181 -0
- truefoundry/deploy/lib/auth/credential_file_manager.py +115 -0
- truefoundry/deploy/lib/auth/credential_provider.py +131 -0
- truefoundry/deploy/lib/auth/servicefoundry_session.py +59 -0
- truefoundry/deploy/lib/clients/__init__.py +0 -0
- truefoundry/deploy/lib/clients/servicefoundry_client.py +746 -0
- truefoundry/deploy/lib/clients/shell_client.py +13 -0
- truefoundry/deploy/lib/clients/utils.py +41 -0
- truefoundry/deploy/lib/const.py +43 -0
- truefoundry/deploy/lib/dao/__init__.py +0 -0
- truefoundry/deploy/lib/dao/application.py +263 -0
- truefoundry/deploy/lib/dao/apply.py +80 -0
- truefoundry/deploy/lib/dao/version.py +33 -0
- truefoundry/deploy/lib/dao/workspace.py +71 -0
- truefoundry/deploy/lib/exceptions.py +26 -0
- truefoundry/deploy/lib/logs_utils.py +43 -0
- truefoundry/deploy/lib/messages.py +12 -0
- truefoundry/deploy/lib/model/__init__.py +0 -0
- truefoundry/deploy/lib/model/entity.py +400 -0
- truefoundry/deploy/lib/session.py +158 -0
- truefoundry/deploy/lib/util.py +90 -0
- truefoundry/deploy/lib/win32.py +129 -0
- truefoundry/deploy/v2/__init__.py +0 -0
- truefoundry/deploy/v2/lib/__init__.py +3 -0
- truefoundry/deploy/v2/lib/deploy.py +283 -0
- truefoundry/deploy/v2/lib/deploy_workflow.py +295 -0
- truefoundry/deploy/v2/lib/deployable_patched_models.py +86 -0
- truefoundry/deploy/v2/lib/models.py +53 -0
- truefoundry/deploy/v2/lib/patched_models.py +479 -0
- truefoundry/deploy/v2/lib/source.py +267 -0
- truefoundry/langchain/__init__.py +12 -1
- truefoundry/langchain/deprecated.py +302 -0
- truefoundry/langchain/truefoundry_chat.py +130 -0
- truefoundry/langchain/truefoundry_embeddings.py +171 -0
- truefoundry/langchain/truefoundry_llm.py +106 -0
- truefoundry/langchain/utils.py +85 -0
- truefoundry/logger.py +17 -0
- truefoundry/pydantic_v1.py +5 -0
- truefoundry/python_deploy_codegen.py +132 -0
- truefoundry/version.py +6 -0
- truefoundry/workflow/__init__.py +19 -0
- truefoundry/workflow/container_task.py +12 -0
- truefoundry/workflow/example/deploy.sh +1 -0
- truefoundry/workflow/example/hello_world_package/workflow.py +20 -0
- truefoundry/workflow/example/package/test_workflow.py +152 -0
- truefoundry/workflow/example/truefoundry.yaml +9 -0
- truefoundry/workflow/example/workflow.yaml +116 -0
- truefoundry/workflow/map_task.py +45 -0
- truefoundry/workflow/python_task.py +32 -0
- truefoundry/workflow/task.py +50 -0
- truefoundry/workflow/workflow.py +114 -0
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/METADATA +27 -7
- truefoundry-0.3.0.dist-info/RECORD +136 -0
- truefoundry/deploy/cli/deploy.py +0 -165
- truefoundry/deploy/cli/version.py +0 -6
- truefoundry-0.2.10.dist-info/RECORD +0 -38
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/WHEEL +0 -0
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Win32 compatibility utilities.
|
|
3
|
+
https://github.com/zeromq/pyzmq/blob/3e7e83a841829fcb46b7e1a881d968fb5471ef62/zmq/utils/win32.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# -----------------------------------------------------------------------------
|
|
7
|
+
# Copyright (C) PyZMQ Developers
|
|
8
|
+
# Distributed under the terms of the Modified BSD License.
|
|
9
|
+
# -----------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Callable, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class allow_interrupt:
|
|
16
|
+
"""Utility for fixing CTRL-C events on Windows.
|
|
17
|
+
|
|
18
|
+
On Windows, the Python interpreter intercepts CTRL-C events in order to
|
|
19
|
+
translate them into ``KeyboardInterrupt`` exceptions. It (presumably)
|
|
20
|
+
does this by setting a flag in its "console control handler" and
|
|
21
|
+
checking it later at a convenient location in the interpreter.
|
|
22
|
+
|
|
23
|
+
However, when the Python interpreter is blocked waiting for the ZMQ
|
|
24
|
+
poll operation to complete, it must wait for ZMQ's ``select()``
|
|
25
|
+
operation to complete before translating the CTRL-C event into the
|
|
26
|
+
``KeyboardInterrupt`` exception.
|
|
27
|
+
|
|
28
|
+
The only way to fix this seems to be to add our own "console control
|
|
29
|
+
handler" and perform some application-defined operation that will
|
|
30
|
+
unblock the ZMQ polling operation in order to force ZMQ to pass control
|
|
31
|
+
back to the Python interpreter.
|
|
32
|
+
|
|
33
|
+
This context manager performs all that Windows-y stuff, providing you
|
|
34
|
+
with a hook that is called when a CTRL-C event is intercepted. This
|
|
35
|
+
hook allows you to unblock your ZMQ poll operation immediately, which
|
|
36
|
+
will then result in the expected ``KeyboardInterrupt`` exception.
|
|
37
|
+
|
|
38
|
+
Without this context manager, your ZMQ-based application will not
|
|
39
|
+
respond normally to CTRL-C events on Windows. If a CTRL-C event occurs
|
|
40
|
+
while blocked on ZMQ socket polling, the translation to a
|
|
41
|
+
``KeyboardInterrupt`` exception will be delayed until the I/O completes
|
|
42
|
+
and control returns to the Python interpreter (this may never happen if
|
|
43
|
+
you use an infinite timeout).
|
|
44
|
+
|
|
45
|
+
A no-op implementation is provided on non-Win32 systems to avoid the
|
|
46
|
+
application from having to conditionally use it.
|
|
47
|
+
|
|
48
|
+
Example usage:
|
|
49
|
+
|
|
50
|
+
.. sourcecode:: python
|
|
51
|
+
|
|
52
|
+
def stop_my_application():
|
|
53
|
+
# ...
|
|
54
|
+
|
|
55
|
+
with allow_interrupt(stop_my_application):
|
|
56
|
+
# main polling loop.
|
|
57
|
+
|
|
58
|
+
In a typical ZMQ application, you would use the "self pipe trick" to
|
|
59
|
+
send message to a ``PAIR`` socket in order to interrupt your blocking
|
|
60
|
+
socket polling operation.
|
|
61
|
+
|
|
62
|
+
In a Tornado event loop, you can use the ``IOLoop.stop`` method to
|
|
63
|
+
unblock your I/O loop.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, action: Optional[Callable[[], Any]] = None) -> None:
|
|
67
|
+
"""Translate ``action`` into a CTRL-C handler.
|
|
68
|
+
|
|
69
|
+
``action`` is a callable that takes no arguments and returns no
|
|
70
|
+
value (returned value is ignored). It must *NEVER* raise an
|
|
71
|
+
exception.
|
|
72
|
+
|
|
73
|
+
If unspecified, a no-op will be used.
|
|
74
|
+
"""
|
|
75
|
+
if os.name != "nt":
|
|
76
|
+
return
|
|
77
|
+
self._init_action(action)
|
|
78
|
+
|
|
79
|
+
def _init_action(self, action):
|
|
80
|
+
from ctypes import WINFUNCTYPE, windll
|
|
81
|
+
from ctypes.wintypes import BOOL, DWORD
|
|
82
|
+
|
|
83
|
+
kernel32 = windll.LoadLibrary("kernel32")
|
|
84
|
+
|
|
85
|
+
# <http://msdn.microsoft.com/en-us/library/ms686016.aspx>
|
|
86
|
+
PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD)
|
|
87
|
+
SetConsoleCtrlHandler = self._SetConsoleCtrlHandler = (
|
|
88
|
+
kernel32.SetConsoleCtrlHandler
|
|
89
|
+
)
|
|
90
|
+
SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL)
|
|
91
|
+
SetConsoleCtrlHandler.restype = BOOL
|
|
92
|
+
|
|
93
|
+
if action is None:
|
|
94
|
+
action = lambda: None # noqa: E731
|
|
95
|
+
self.action = action
|
|
96
|
+
|
|
97
|
+
@PHANDLER_ROUTINE
|
|
98
|
+
def handle(event):
|
|
99
|
+
if event == 0: # CTRL_C_EVENT
|
|
100
|
+
action()
|
|
101
|
+
# Typical C implementations would return 1 to indicate that
|
|
102
|
+
# the event was processed and other control handlers in the
|
|
103
|
+
# stack should not be executed. However, that would
|
|
104
|
+
# prevent the Python interpreter's handler from translating
|
|
105
|
+
# CTRL-C to a `KeyboardInterrupt` exception, so we pretend
|
|
106
|
+
# that we didn't handle it.
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
self.handle = handle
|
|
110
|
+
|
|
111
|
+
def __enter__(self):
|
|
112
|
+
"""Install the custom CTRL-C handler."""
|
|
113
|
+
if os.name != "nt":
|
|
114
|
+
return
|
|
115
|
+
result = self._SetConsoleCtrlHandler(self.handle, 1)
|
|
116
|
+
if result == 0:
|
|
117
|
+
# Have standard library automatically call `GetLastError()` and
|
|
118
|
+
# `FormatMessage()` into a nice exception object :-)
|
|
119
|
+
raise OSError()
|
|
120
|
+
|
|
121
|
+
def __exit__(self, *args):
|
|
122
|
+
"""Remove the custom CTRL-C handler."""
|
|
123
|
+
if os.name != "nt":
|
|
124
|
+
return
|
|
125
|
+
result = self._SetConsoleCtrlHandler(self.handle, 0)
|
|
126
|
+
if result == 0:
|
|
127
|
+
# Have standard library automatically call `GetLastError()` and
|
|
128
|
+
# `FormatMessage()` into a nice exception object :-)
|
|
129
|
+
raise OSError()
|
|
File without changes
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
from typing import List, Optional, TypeVar
|
|
4
|
+
|
|
5
|
+
from rich.status import Status
|
|
6
|
+
|
|
7
|
+
from truefoundry.deploy.auto_gen import models as auto_gen_models
|
|
8
|
+
from truefoundry.deploy.builder.docker_service import env_has_docker
|
|
9
|
+
from truefoundry.deploy.lib.clients.servicefoundry_client import (
|
|
10
|
+
ServiceFoundryServiceClient,
|
|
11
|
+
)
|
|
12
|
+
from truefoundry.deploy.lib.clients.utils import poll_for_function
|
|
13
|
+
from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
|
|
14
|
+
from truefoundry.deploy.lib.model.entity import Deployment, DeploymentTransitionStatus
|
|
15
|
+
from truefoundry.deploy.lib.util import get_application_fqn_from_deployment_fqn
|
|
16
|
+
from truefoundry.deploy.v2.lib.models import BuildResponse
|
|
17
|
+
from truefoundry.deploy.v2.lib.source import (
|
|
18
|
+
local_source_to_image,
|
|
19
|
+
local_source_to_remote_source,
|
|
20
|
+
)
|
|
21
|
+
from truefoundry.logger import logger
|
|
22
|
+
from truefoundry.pydantic_v1 import BaseModel
|
|
23
|
+
|
|
24
|
+
Component = TypeVar("Component", bound=BaseModel)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _handle_if_local_source(component: Component, workspace_fqn: str) -> Component:
|
|
28
|
+
if (
|
|
29
|
+
hasattr(component, "image")
|
|
30
|
+
and isinstance(component.image, auto_gen_models.Build)
|
|
31
|
+
and isinstance(component.image.build_source, auto_gen_models.LocalSource)
|
|
32
|
+
):
|
|
33
|
+
new_component = component.copy(deep=True)
|
|
34
|
+
|
|
35
|
+
if component.image.build_source.local_build:
|
|
36
|
+
if not env_has_docker():
|
|
37
|
+
logger.warning(
|
|
38
|
+
"Did not find Docker locally installed on this system, image will be built remotely. "
|
|
39
|
+
"For faster builds it is recommended to install Docker locally. "
|
|
40
|
+
"If you always want to build remotely, "
|
|
41
|
+
"please set `image.build_source.local_build` to `false` in your YAML spec or equivalently set "
|
|
42
|
+
"`image=Build(build_source=LocalSource(local_build=False, ...))` in your "
|
|
43
|
+
"`Service` or `Job` definition code."
|
|
44
|
+
)
|
|
45
|
+
local_build = False
|
|
46
|
+
else:
|
|
47
|
+
logger.info(
|
|
48
|
+
"Found locally installed docker, image will be built locally and then pushed. "
|
|
49
|
+
"If you want to always build remotely instead of locally, "
|
|
50
|
+
"please set `image.build_source.local_build` to `false` in your YAML spec or equivalently set "
|
|
51
|
+
"`image=Build(build_source=LocalSource(local_build=False, ...))` in your "
|
|
52
|
+
"`Service` or `Job` definition code."
|
|
53
|
+
)
|
|
54
|
+
local_build = True
|
|
55
|
+
else:
|
|
56
|
+
logger.info(
|
|
57
|
+
"Image will be built remotely because `image.build_source.local_build` is set to `false`. "
|
|
58
|
+
"For faster builds it is recommended to install Docker locally and "
|
|
59
|
+
"set `image.build_source.local_build` to `true` in your YAML spec "
|
|
60
|
+
"or equivalently set `image=Build(build_source=LocalSource(local_build=True, ...))` "
|
|
61
|
+
"in your `Service` or `Job` definition code."
|
|
62
|
+
)
|
|
63
|
+
local_build = False
|
|
64
|
+
|
|
65
|
+
if local_build:
|
|
66
|
+
# We are to build the image locally, push and update `image` in spec
|
|
67
|
+
logger.info("Building image for %s '%s'", component.type, component.name)
|
|
68
|
+
new_component.image = local_source_to_image(
|
|
69
|
+
build=component.image,
|
|
70
|
+
docker_registry_fqn=component.image.docker_registry,
|
|
71
|
+
workspace_fqn=workspace_fqn,
|
|
72
|
+
component_name=component.name,
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
# We'll build image on Truefoundry servers, upload the source and update image.build_source
|
|
76
|
+
logger.info("Uploading code for %s '%s'", component.type, component.name)
|
|
77
|
+
new_component.image.build_source = local_source_to_remote_source(
|
|
78
|
+
local_source=component.image.build_source,
|
|
79
|
+
workspace_fqn=workspace_fqn,
|
|
80
|
+
component_name=component.name,
|
|
81
|
+
)
|
|
82
|
+
logger.debug("Uploaded code for %s '%s'", component.type, component.name)
|
|
83
|
+
return new_component
|
|
84
|
+
return component
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _log_application_dashboard_url(deployment: Deployment, log_message: str):
|
|
88
|
+
application_id = deployment.applicationId
|
|
89
|
+
|
|
90
|
+
# TODO: is there any simpler way to get this? :cry
|
|
91
|
+
client = ServiceFoundryServiceClient()
|
|
92
|
+
|
|
93
|
+
url = f"{client.base_url.strip('/')}/applications/{application_id}?tab=deployments"
|
|
94
|
+
logger.info(log_message, url)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _tail_build_logs(build_responses: List[BuildResponse]) -> None:
|
|
98
|
+
client = ServiceFoundryServiceClient()
|
|
99
|
+
|
|
100
|
+
# TODO: Explore other options like,
|
|
101
|
+
# https://rich.readthedocs.io/en/stable/live.html#live-display
|
|
102
|
+
# How does docker/compose does multiple build logs?
|
|
103
|
+
for build_response in build_responses:
|
|
104
|
+
logger.info("Tailing build logs for '%s'", build_response.componentName)
|
|
105
|
+
client.tail_build_logs(build_response=build_response, wait=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _deploy_wait_handler(
|
|
109
|
+
deployment: Deployment,
|
|
110
|
+
) -> Optional[DeploymentTransitionStatus]:
|
|
111
|
+
_log_application_dashboard_url(
|
|
112
|
+
deployment=deployment,
|
|
113
|
+
log_message=(
|
|
114
|
+
"You can track the progress below or on the dashboard:- '%s'\n"
|
|
115
|
+
"You can press Ctrl + C to exit the tailing of build logs "
|
|
116
|
+
"and deployment will continue on the server"
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
with Status(status="Polling for deployment status") as spinner:
|
|
120
|
+
last_status_printed = None
|
|
121
|
+
client = ServiceFoundryServiceClient()
|
|
122
|
+
start_time = time.monotonic()
|
|
123
|
+
total_timeout_time: int = 300
|
|
124
|
+
poll_interval_seconds = 5
|
|
125
|
+
time_elapsed = 0
|
|
126
|
+
|
|
127
|
+
for deployment_statuses in poll_for_function(
|
|
128
|
+
client.get_deployment_statuses,
|
|
129
|
+
poll_after_secs=poll_interval_seconds,
|
|
130
|
+
application_id=deployment.applicationId,
|
|
131
|
+
deployment_id=deployment.id,
|
|
132
|
+
):
|
|
133
|
+
if len(deployment_statuses) == 0:
|
|
134
|
+
logger.warning("Did not receive any deployment status")
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
latest_deployment_status = deployment_statuses[-1]
|
|
138
|
+
|
|
139
|
+
status_to_print = (
|
|
140
|
+
latest_deployment_status.transition or latest_deployment_status.status
|
|
141
|
+
)
|
|
142
|
+
spinner.update(status=f"Current state: {status_to_print}")
|
|
143
|
+
if status_to_print != last_status_printed:
|
|
144
|
+
if DeploymentTransitionStatus.is_failure_state(status_to_print):
|
|
145
|
+
logger.error("State: %r", status_to_print)
|
|
146
|
+
else:
|
|
147
|
+
logger.info("State: %r", status_to_print)
|
|
148
|
+
last_status_printed = status_to_print
|
|
149
|
+
|
|
150
|
+
if latest_deployment_status.state.isTerminalState:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
latest_deployment_status.transition
|
|
155
|
+
== DeploymentTransitionStatus.BUILDING
|
|
156
|
+
):
|
|
157
|
+
build_responses = client.get_deployment_build_response(
|
|
158
|
+
application_id=deployment.applicationId, deployment_id=deployment.id
|
|
159
|
+
)
|
|
160
|
+
_tail_build_logs(build_responses)
|
|
161
|
+
|
|
162
|
+
time_elapsed = time.monotonic() - start_time
|
|
163
|
+
if time_elapsed > total_timeout_time:
|
|
164
|
+
logger.warning("Polled server for %s secs.", int(time_elapsed))
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
return last_status_printed
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _warn_when_gpu_selected_without_cuda(component: Component):
|
|
171
|
+
is_python_build_without_cuda = (
|
|
172
|
+
hasattr(component, "image")
|
|
173
|
+
and isinstance(component.image, auto_gen_models.Build)
|
|
174
|
+
and isinstance(component.image.build_spec, auto_gen_models.PythonBuild)
|
|
175
|
+
and not component.image.build_spec.cuda_version
|
|
176
|
+
)
|
|
177
|
+
uses_gpu = (
|
|
178
|
+
hasattr(component, "resources")
|
|
179
|
+
and isinstance(component, auto_gen_models.Resources)
|
|
180
|
+
and component.resources.gpu_count > 0
|
|
181
|
+
)
|
|
182
|
+
if is_python_build_without_cuda and uses_gpu:
|
|
183
|
+
logger.warning(
|
|
184
|
+
"Warning: `gpu_count` is greater than 0 in `Resources` (i.e. `resources.gpu_count`) "
|
|
185
|
+
"but no `cuda_version` was passed to `PythonBuild` "
|
|
186
|
+
"(i.e. `image.build_spec.cuda_version`). "
|
|
187
|
+
"Your application might optionally need CUDA toolkit installed "
|
|
188
|
+
"to utilize the GPU. You can choose one by passing one of "
|
|
189
|
+
"`truefoundry.deploy.CUDAVersion` in `PythonBuild` instance. "
|
|
190
|
+
"\n\nE.g.\n```\nPythonBuild(..., cuda_version=CUDAVersion.CUDA_11_3_CUDNN8)\n```"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _resolve_workspace_fqn(
|
|
195
|
+
component: Component, workspace_fqn: Optional[str] = None
|
|
196
|
+
) -> str:
|
|
197
|
+
if not workspace_fqn:
|
|
198
|
+
if hasattr(component, "workspace_fqn") and component.workspace_fqn:
|
|
199
|
+
resolved_workspace_fqn = component.workspace_fqn
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"""\
|
|
203
|
+
No Workspace FQN was provided or mentioned in the spec.
|
|
204
|
+
Either add a `workspace_fqn` to your yaml spec as
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
name: {getattr(component, 'name', 'my-app')}
|
|
208
|
+
type: {getattr(component, 'type', 'undefined')}
|
|
209
|
+
...
|
|
210
|
+
workspace_fqn: <your workspace fqn>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
or Python deployment spec as
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
app = {component.__class__.__name__}(
|
|
217
|
+
name='{getattr(component, 'name', 'my-app')}',
|
|
218
|
+
...
|
|
219
|
+
workspace_fqn='<your workspace fqn>'
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
or pass it explicitly using `--workspace-fqn` argument on CLI.
|
|
224
|
+
"""
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
if (
|
|
228
|
+
hasattr(component, "workspace_fqn")
|
|
229
|
+
and component.workspace_fqn
|
|
230
|
+
and component.workspace_fqn != workspace_fqn
|
|
231
|
+
):
|
|
232
|
+
logger.warning(
|
|
233
|
+
f"`workspace_fqn` set in the deployment spec doesn't match the provided `workspace_fqn` argument {component.workspace_fqn!r} \n"
|
|
234
|
+
f"Using `workspace_fqn`: {workspace_fqn!r} "
|
|
235
|
+
)
|
|
236
|
+
resolved_workspace_fqn = workspace_fqn
|
|
237
|
+
|
|
238
|
+
return resolved_workspace_fqn
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def deploy_component(
|
|
242
|
+
component: Component, workspace_fqn: Optional[str] = None, wait: bool = True
|
|
243
|
+
) -> Deployment:
|
|
244
|
+
_warn_when_gpu_selected_without_cuda(component=component)
|
|
245
|
+
workspace_fqn = _resolve_workspace_fqn(
|
|
246
|
+
component=component, workspace_fqn=workspace_fqn
|
|
247
|
+
)
|
|
248
|
+
component.workspace_fqn = workspace_fqn
|
|
249
|
+
workspace_id = get_workspace_by_fqn(workspace_fqn).id
|
|
250
|
+
updated_component = _handle_if_local_source(
|
|
251
|
+
component=component, workspace_fqn=workspace_fqn
|
|
252
|
+
)
|
|
253
|
+
client = ServiceFoundryServiceClient()
|
|
254
|
+
response = client.deploy_application(
|
|
255
|
+
workspace_id=workspace_id, application=updated_component
|
|
256
|
+
)
|
|
257
|
+
logger.info(
|
|
258
|
+
"🚀 Deployment started for application '%s'. Deployment FQN is '%s'.",
|
|
259
|
+
updated_component.name,
|
|
260
|
+
response.fqn,
|
|
261
|
+
)
|
|
262
|
+
if wait:
|
|
263
|
+
try:
|
|
264
|
+
last_status_printed = _deploy_wait_handler(deployment=response)
|
|
265
|
+
if not last_status_printed or DeploymentTransitionStatus.is_failure_state(
|
|
266
|
+
last_status_printed
|
|
267
|
+
):
|
|
268
|
+
deployment_tab_url = f"{client.base_url.strip('/')}/applications/{response.applicationId}?tab=deployments"
|
|
269
|
+
message = f"Deployment Failed. Please refer to the logs for additional details - {deployment_tab_url}"
|
|
270
|
+
sys.exit(message)
|
|
271
|
+
except KeyboardInterrupt:
|
|
272
|
+
logger.info("Ctrl-c executed. The deployment will still continue.")
|
|
273
|
+
|
|
274
|
+
deployment_fqn = response.fqn
|
|
275
|
+
application_fqn = get_application_fqn_from_deployment_fqn(deployment_fqn)
|
|
276
|
+
logger.info("Deployment FQN: %s", deployment_fqn)
|
|
277
|
+
logger.info("Application FQN: %s", application_fqn)
|
|
278
|
+
|
|
279
|
+
_log_application_dashboard_url(
|
|
280
|
+
deployment=response,
|
|
281
|
+
log_message="You can find the application on the dashboard:- '%s'",
|
|
282
|
+
)
|
|
283
|
+
return response
|