truefoundry 0.7.2__py3-none-any.whl → 0.9.0rc1__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/_client.py +1 -1
- truefoundry/cli/util.py +3 -0
- truefoundry/common/entities.py +3 -0
- truefoundry/deploy/_autogen/models.py +11 -29
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +4 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +4 -0
- truefoundry/deploy/lib/clients/ask_client.py +2 -6
- truefoundry/deploy/v2/lib/deploy.py +30 -9
- {truefoundry-0.7.2.dist-info → truefoundry-0.9.0rc1.dist-info}/METADATA +3 -3
- {truefoundry-0.7.2.dist-info → truefoundry-0.9.0rc1.dist-info}/RECORD +12 -13
- truefoundry/deploy/lib/clients/_mcp_streamable_http.py +0 -264
- {truefoundry-0.7.2.dist-info → truefoundry-0.9.0rc1.dist-info}/WHEEL +0 -0
- {truefoundry-0.7.2.dist-info → truefoundry-0.9.0rc1.dist-info}/entry_points.txt +0 -0
truefoundry/_client.py
CHANGED
truefoundry/cli/util.py
CHANGED
|
@@ -51,6 +51,9 @@ def handle_exception(exception):
|
|
|
51
51
|
title="Command Failed",
|
|
52
52
|
border_style="red",
|
|
53
53
|
)
|
|
54
|
+
elif isinstance(exception, KeyError):
|
|
55
|
+
# KeyError messages in Python just suck - they tell you the key that was missing, but not the context
|
|
56
|
+
console.print_exception(show_locals=False, max_frames=1)
|
|
54
57
|
else:
|
|
55
58
|
print_dict_as_table_panel(
|
|
56
59
|
{"Error": str(exception)},
|
truefoundry/common/entities.py
CHANGED
|
@@ -109,6 +109,9 @@ class PythonSDKConfig(BaseModel):
|
|
|
109
109
|
use_sfy_server_auth_apis: Optional[bool] = Field(
|
|
110
110
|
alias="useSFYServerAuthAPIs", default=False
|
|
111
111
|
)
|
|
112
|
+
python_build_default_image_tag: str = Field(
|
|
113
|
+
alias="pythonBuildDefaultImageTag", default="3.11"
|
|
114
|
+
)
|
|
112
115
|
|
|
113
116
|
|
|
114
117
|
class DeviceCode(BaseModel):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# generated by datamodel-codegen:
|
|
2
2
|
# filename: application.json
|
|
3
|
-
# timestamp: 2025-05-
|
|
3
|
+
# timestamp: 2025-05-15T05:28:11+00:00
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -360,13 +360,6 @@ class Image(BaseModel):
|
|
|
360
360
|
)
|
|
361
361
|
|
|
362
362
|
|
|
363
|
-
class JobEvent(str, Enum):
|
|
364
|
-
START = "START"
|
|
365
|
-
SUCCEEDED = "SUCCEEDED"
|
|
366
|
-
FAILED = "FAILED"
|
|
367
|
-
TERMINATED = "TERMINATED"
|
|
368
|
-
|
|
369
|
-
|
|
370
363
|
class Claim(BaseModel):
|
|
371
364
|
key: str
|
|
372
365
|
values: List[str]
|
|
@@ -623,9 +616,11 @@ class PythonBuild(BaseModel):
|
|
|
623
616
|
"""
|
|
624
617
|
|
|
625
618
|
type: Literal["tfy-python-buildpack"] = Field(..., description="")
|
|
626
|
-
python_version: constr(regex=r"^\d+(\.\d+){1,2}([\-\.a-z0-9]+)?$") =
|
|
627
|
-
|
|
628
|
-
|
|
619
|
+
python_version: Optional[constr(regex=r"^\d+(\.\d+){1,2}([\-\.a-z0-9]+)?$")] = (
|
|
620
|
+
Field(
|
|
621
|
+
None,
|
|
622
|
+
description="Python version to run your application. Should be one of the tags listed on [Official Python Docker Page](https://hub.docker.com/_/python)",
|
|
623
|
+
)
|
|
629
624
|
)
|
|
630
625
|
build_context_path: str = Field(
|
|
631
626
|
"./", description="Build path relative to project root path."
|
|
@@ -963,9 +958,11 @@ class TaskPythonBuild(BaseModel):
|
|
|
963
958
|
None,
|
|
964
959
|
description="FQN of the container registry. If you can't find your registry here,\nadd it through the [Integrations](/integrations?tab=docker-registry) page",
|
|
965
960
|
)
|
|
966
|
-
python_version: constr(regex=r"^\d+(\.\d+){1,2}([\-\.a-z0-9]+)?$") =
|
|
967
|
-
|
|
968
|
-
|
|
961
|
+
python_version: Optional[constr(regex=r"^\d+(\.\d+){1,2}([\-\.a-z0-9]+)?$")] = (
|
|
962
|
+
Field(
|
|
963
|
+
None,
|
|
964
|
+
description="Python version to run your application. Should be one of the tags listed on [Official Python Docker Page](https://hub.docker.com/_/python)",
|
|
965
|
+
)
|
|
969
966
|
)
|
|
970
967
|
requirements_path: Optional[str] = Field(
|
|
971
968
|
None,
|
|
@@ -1060,13 +1057,6 @@ class WorkbenchImage(BaseModel):
|
|
|
1060
1057
|
)
|
|
1061
1058
|
|
|
1062
1059
|
|
|
1063
|
-
class WorkflowEvent(str, Enum):
|
|
1064
|
-
SUCCEEDED = "SUCCEEDED"
|
|
1065
|
-
FAILED = "FAILED"
|
|
1066
|
-
ABORTED = "ABORTED"
|
|
1067
|
-
TIMED_OUT = "TIMED_OUT"
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
1060
|
class ArtifactsDownload(BaseModel):
|
|
1071
1061
|
"""
|
|
1072
1062
|
Download and cache models in a volume to enhance loading speeds and reduce costs by avoiding repeated downloads. [Docs](https://docs.truefoundry.com/docs/download-and-cache-models)
|
|
@@ -1485,10 +1475,6 @@ class WorkflowAlert(BaseModel):
|
|
|
1485
1475
|
"""
|
|
1486
1476
|
|
|
1487
1477
|
notification_target: Optional[Union[Email, SlackWebhook, SlackBot]] = None
|
|
1488
|
-
events: List[WorkflowEvent] = Field(
|
|
1489
|
-
...,
|
|
1490
|
-
description="Specify the events to send alerts for, it should be one of the following: SUCCEEDED, FAILED, ABORTED, TIMED_OUT",
|
|
1491
|
-
)
|
|
1492
1478
|
on_completion: bool = Field(
|
|
1493
1479
|
False, description="Send an alert when the job completes"
|
|
1494
1480
|
)
|
|
@@ -1556,10 +1542,6 @@ class JobAlert(BaseModel):
|
|
|
1556
1542
|
description="List of recipients' email addresses if the notification channel is Email.",
|
|
1557
1543
|
)
|
|
1558
1544
|
notification_target: Optional[Union[Email, SlackWebhook, SlackBot]] = None
|
|
1559
|
-
events: List[JobEvent] = Field(
|
|
1560
|
-
...,
|
|
1561
|
-
description="Specify the events to send alerts for, it should be one of the following: START, SUCCEEDED, FAILED, TERMINATED",
|
|
1562
|
-
)
|
|
1563
1545
|
on_start: bool = Field(False, description="Send an alert when the job starts")
|
|
1564
1546
|
on_completion: bool = False
|
|
1565
1547
|
on_failure: bool = Field(True, description="Send an alert when the job fails")
|
|
@@ -36,6 +36,10 @@ def build(
|
|
|
36
36
|
build_configuration: PythonBuild,
|
|
37
37
|
extra_opts: Optional[List[str]] = None,
|
|
38
38
|
):
|
|
39
|
+
if not build_configuration.python_version:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
42
|
+
)
|
|
39
43
|
mount_python_package_manager_conf_secret = (
|
|
40
44
|
has_python_package_manager_conf_secret(extra_opts) if extra_opts else False
|
|
41
45
|
)
|
|
@@ -174,6 +174,10 @@ def generate_dockerfile_content(
|
|
|
174
174
|
requirements_destination_path = (
|
|
175
175
|
"/tmp/requirements.txt" if requirements_path else None
|
|
176
176
|
)
|
|
177
|
+
if not build_configuration.python_version:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"`python_version` is required for `tfy-python-buildpack` builder"
|
|
180
|
+
)
|
|
177
181
|
if package_manager == PythonPackageManager.PIP.value:
|
|
178
182
|
python_packages_install_command = generate_pip_install_command(
|
|
179
183
|
requirements_path=requirements_destination_path,
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
try:
|
|
2
2
|
from mcp import ClientSession
|
|
3
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
3
4
|
from mcp.types import TextContent
|
|
4
|
-
|
|
5
|
-
from truefoundry.deploy.lib.clients._mcp_streamable_http import (
|
|
6
|
-
streamablehttp_client,
|
|
7
|
-
)
|
|
8
5
|
except ImportError:
|
|
9
6
|
import sys
|
|
10
7
|
|
|
@@ -75,7 +72,7 @@ class AskClient:
|
|
|
75
72
|
(
|
|
76
73
|
read_stream,
|
|
77
74
|
write_stream,
|
|
78
|
-
|
|
75
|
+
_,
|
|
79
76
|
) = await self._streams_context.__aenter__()
|
|
80
77
|
self._session_context = ClientSession(
|
|
81
78
|
read_stream=read_stream, write_stream=write_stream
|
|
@@ -99,7 +96,6 @@ class AskClient:
|
|
|
99
96
|
|
|
100
97
|
async def cleanup(self):
|
|
101
98
|
"""Properly close all async contexts opened during session initialization."""
|
|
102
|
-
await self._terminate_cb()
|
|
103
99
|
for context in [
|
|
104
100
|
getattr(self, "_session_context", None),
|
|
105
101
|
getattr(self, "_streams_context", None),
|
|
@@ -25,6 +25,19 @@ from truefoundry.pydantic_v1 import BaseModel
|
|
|
25
25
|
Component = TypeVar("Component", bound=BaseModel)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _set_python_version_if_missing(build_spec: autogen_models.PythonBuild) -> None:
|
|
29
|
+
if build_spec.python_version is None:
|
|
30
|
+
client = ServiceFoundryServiceClient()
|
|
31
|
+
server_default_python_version = (
|
|
32
|
+
client.python_sdk_config.python_build_default_image_tag
|
|
33
|
+
)
|
|
34
|
+
logger.warning(
|
|
35
|
+
f"No python version was provided in the spec, "
|
|
36
|
+
f"using the default python version ({server_default_python_version}) from the server."
|
|
37
|
+
)
|
|
38
|
+
build_spec.python_version = server_default_python_version
|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
def _handle_if_local_source(component: Component, workspace_fqn: str) -> Component:
|
|
29
42
|
if (
|
|
30
43
|
hasattr(component, "image")
|
|
@@ -33,7 +46,7 @@ def _handle_if_local_source(component: Component, workspace_fqn: str) -> Compone
|
|
|
33
46
|
):
|
|
34
47
|
new_component = component.copy(deep=True)
|
|
35
48
|
|
|
36
|
-
if
|
|
49
|
+
if new_component.image.build_source.local_build:
|
|
37
50
|
if not env_has_docker():
|
|
38
51
|
logger.warning(
|
|
39
52
|
"Did not find Docker locally installed on this system, image will be built remotely. "
|
|
@@ -64,25 +77,33 @@ def _handle_if_local_source(component: Component, workspace_fqn: str) -> Compone
|
|
|
64
77
|
local_build = False
|
|
65
78
|
|
|
66
79
|
if local_build:
|
|
80
|
+
if isinstance(new_component.image.build_spec, autogen_models.PythonBuild):
|
|
81
|
+
_set_python_version_if_missing(new_component.image.build_spec)
|
|
67
82
|
# We are to build the image locally, push and update `image` in spec
|
|
68
|
-
logger.info(
|
|
83
|
+
logger.info(
|
|
84
|
+
"Building image for %s '%s'", new_component.type, new_component.name
|
|
85
|
+
)
|
|
69
86
|
new_component.image = local_source_to_image(
|
|
70
|
-
build=
|
|
71
|
-
docker_registry_fqn=
|
|
87
|
+
build=new_component.image,
|
|
88
|
+
docker_registry_fqn=new_component.image.docker_registry,
|
|
72
89
|
workspace_fqn=workspace_fqn,
|
|
73
|
-
component_name=
|
|
90
|
+
component_name=new_component.name,
|
|
74
91
|
)
|
|
75
92
|
else:
|
|
76
93
|
# We'll build image on TrueFoundry servers, upload the source and update image.build_source
|
|
77
|
-
logger.info(
|
|
94
|
+
logger.info(
|
|
95
|
+
"Uploading code for %s '%s'", new_component.type, new_component.name
|
|
96
|
+
)
|
|
78
97
|
client = ServiceFoundryServiceClient()
|
|
79
98
|
new_component.image.build_source = local_source_to_remote_source(
|
|
80
|
-
local_source=
|
|
99
|
+
local_source=new_component.image.build_source,
|
|
81
100
|
workspace_fqn=workspace_fqn,
|
|
82
|
-
component_name=
|
|
101
|
+
component_name=new_component.name,
|
|
83
102
|
upload_code_package=client.upload_code_package,
|
|
84
103
|
)
|
|
85
|
-
logger.debug(
|
|
104
|
+
logger.debug(
|
|
105
|
+
"Uploaded code for %s '%s'", new_component.type, new_component.name
|
|
106
|
+
)
|
|
86
107
|
return new_component
|
|
87
108
|
return component
|
|
88
109
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: truefoundry
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0rc1
|
|
4
4
|
Summary: TrueFoundry CLI
|
|
5
5
|
Author-email: TrueFoundry Team <abhishek@truefoundry.com>
|
|
6
6
|
Requires-Python: <3.14,>=3.8.1
|
|
@@ -14,7 +14,7 @@ Requires-Dist: gitpython<4.0.0,>=3.1.43
|
|
|
14
14
|
Requires-Dist: importlib-metadata<9.0.0,>=4.11.3
|
|
15
15
|
Requires-Dist: importlib-resources<7.0.0,>=5.2.0
|
|
16
16
|
Requires-Dist: mako<2.0.0,>=1.1.6
|
|
17
|
-
Requires-Dist: mcp==1.
|
|
17
|
+
Requires-Dist: mcp==1.8.1; python_version >= '3.10'
|
|
18
18
|
Requires-Dist: numpy<3.0.0,>=1.23.0
|
|
19
19
|
Requires-Dist: openai<2.0.0,>=1.16.2
|
|
20
20
|
Requires-Dist: packaging<26.0,>=20.0
|
|
@@ -31,7 +31,7 @@ Requires-Dist: requirements-parser<0.12.0,>=0.11.0
|
|
|
31
31
|
Requires-Dist: rich-click<2.0.0,>=1.2.1
|
|
32
32
|
Requires-Dist: rich<14.0.0,>=13.7.1
|
|
33
33
|
Requires-Dist: tqdm<5.0.0,>=4.0.0
|
|
34
|
-
Requires-Dist: truefoundry-sdk
|
|
34
|
+
Requires-Dist: truefoundry-sdk<0.2.0,>=0.1.1
|
|
35
35
|
Requires-Dist: typing-extensions>=4.0
|
|
36
36
|
Requires-Dist: urllib3<3,>=1.26.18
|
|
37
37
|
Requires-Dist: yq<4.0.0,>=3.1.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
truefoundry/__init__.py,sha256=VVpO-Awh1v93VOURe7hank8QpeSPc0dCykwr14GOFsw,967
|
|
2
|
-
truefoundry/_client.py,sha256=
|
|
2
|
+
truefoundry/_client.py,sha256=Y3qHi_Lg4Sx6GNvsjAHIoAfFr8PJnqgCrXmpNAI3ECg,1417
|
|
3
3
|
truefoundry/logger.py,sha256=u-YCNjg5HBwE70uQcpjIG64Ghos-K2ulTWaxC03BSj4,714
|
|
4
4
|
truefoundry/pydantic_v1.py,sha256=jSuhGtz0Mbk1qYu8jJ1AcnIDK4oxUsdhALc4spqstmM,345
|
|
5
5
|
truefoundry/version.py,sha256=bqiT4Q-VWrTC6P4qfK43mez-Ppf-smWfrl6DcwV7mrw,137
|
|
@@ -33,13 +33,13 @@ truefoundry/cli/config.py,sha256=f7z0_gmYZiNImB7Bxz0AnOlrxY2X4lFnX4jYW1I7NHQ,139
|
|
|
33
33
|
truefoundry/cli/console.py,sha256=9-dMy4YPisCJQziRKTg8Qa0UJnOGl1soiUnJjsnLDvE,242
|
|
34
34
|
truefoundry/cli/const.py,sha256=dVHPo1uAiDSSMXwXoT2mR5kNQjExT98QNVRz98Hz_Ts,510
|
|
35
35
|
truefoundry/cli/display_util.py,sha256=s0_eWUUAK1dbmqW5h_qAG93roH81dh-g1nLjuQVFm6k,5130
|
|
36
|
-
truefoundry/cli/util.py,sha256=
|
|
36
|
+
truefoundry/cli/util.py,sha256=7DmKXY5OPslPu2LO6vrUUfDtoHeo12sJTDUA0GOi8IM,3922
|
|
37
37
|
truefoundry/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
truefoundry/common/auth_service_client.py,sha256=N3YxKlx63r6cPZqbgb2lqBOPI69ShB7D7RCIq4FSCjc,7949
|
|
39
39
|
truefoundry/common/constants.py,sha256=eWcElAYIVb0jnHUAcsHvgnkdKf2E1nCg_Ybbi8ibxF0,4365
|
|
40
40
|
truefoundry/common/credential_file_manager.py,sha256=1yEk1Zm2xS4G0VDFwKSZ4w0VUrcPWQ1nJnoBaz9xyKA,4251
|
|
41
41
|
truefoundry/common/credential_provider.py,sha256=_OhJ2XFlDaVsrUO-FyywxctcGGqDdC2pgcvwEKqQD0Q,4071
|
|
42
|
-
truefoundry/common/entities.py,sha256=
|
|
42
|
+
truefoundry/common/entities.py,sha256=b4R6ss06-ygDS3C4Tqa_GOq5LFKDYbt7x4Mghnfz6yo,4007
|
|
43
43
|
truefoundry/common/exceptions.py,sha256=jkU0N7hV_P-EhXeud4I5vuB9glXXZSWPf8LcH04mSbw,459
|
|
44
44
|
truefoundry/common/request_utils.py,sha256=e9qrAQ1MutU7JALDKcucmNd0KQEVBqgW3yx0w1zeHIU,5700
|
|
45
45
|
truefoundry/common/servicefoundry_client.py,sha256=2fYhdVPSvLXz5C5tosOq86JD8WM3IRUIy1VO9deDxZI,3340
|
|
@@ -50,7 +50,7 @@ truefoundry/common/utils.py,sha256=j3QP0uOsaGD_VmDDR68JTwoYE1okkAq6OqpVkzVf48Q,6
|
|
|
50
50
|
truefoundry/common/warnings.py,sha256=rs6BHwk7imQYedo07iwh3TWEOywAR3Lqhj0AY4khByg,504
|
|
51
51
|
truefoundry/deploy/__init__.py,sha256=6D22iiCgd5xlzBaG34q9Cx4rGgwf5qIAKQrOCgaCXYY,2746
|
|
52
52
|
truefoundry/deploy/python_deploy_codegen.py,sha256=AainOFR20XvhNeztJkLPWGZ40lAT_nwc-ZmG77Kum4o,6525
|
|
53
|
-
truefoundry/deploy/_autogen/models.py,sha256=
|
|
53
|
+
truefoundry/deploy/_autogen/models.py,sha256=gGH63evQTTnU0fEjtNgCsD0aqIxhdp316GL3Xb65NJk,71461
|
|
54
54
|
truefoundry/deploy/builder/__init__.py,sha256=nGQiR3r16iumRy7xbVQ6q-k0EApmijspsfVpXDE-9po,4953
|
|
55
55
|
truefoundry/deploy/builder/constants.py,sha256=amUkHoHvVKzGv0v_knfiioRuKiJM0V0xW0diERgWiI0,508
|
|
56
56
|
truefoundry/deploy/builder/docker_service.py,sha256=sm7GWeIqyrKaZpxskdLejZlsxcZnM3BTDJr6orvPN4E,3948
|
|
@@ -59,8 +59,8 @@ truefoundry/deploy/builder/builders/__init__.py,sha256=tlFLXqyDaKLd4iZbo4Hcu_8gO
|
|
|
59
59
|
truefoundry/deploy/builder/builders/dockerfile.py,sha256=XMbMlPUTMPCyaHl7jJQY1ODtlRkpI61PcvgG6Ck5jNc,1522
|
|
60
60
|
truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py,sha256=RGWGqY8xOF7vycUPJd10N7ZzahWv24lO0anrOPtLuDU,1796
|
|
61
61
|
truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py,sha256=rQgdvKmAT9HArVW4TAG5yd2QTKRs3S5LJ9RQbc_EkHE,2518
|
|
62
|
-
truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py,sha256=
|
|
63
|
-
truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py,sha256=
|
|
62
|
+
truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py,sha256=_fjqHKn80qKi68SAMMALge7_A6e1sTsQWichw8uoGIw,2025
|
|
63
|
+
truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py,sha256=f4l3fH21E2b8W3-JotMKc0AdPcCxV7LRPxxYJa7z_UQ,9134
|
|
64
64
|
truefoundry/deploy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
65
|
truefoundry/deploy/cli/commands/__init__.py,sha256=f7sXiQK9UuxDJmvBa-QCFNyumUpGGMhZbCxdwJzWXwQ,1116
|
|
66
66
|
truefoundry/deploy/cli/commands/apply_command.py,sha256=Y2e_C8HVpo8CssVod-3JRz-89qStC5JRaNzJ7O2mRlY,2039
|
|
@@ -95,8 +95,7 @@ truefoundry/deploy/lib/session.py,sha256=fLdgR6ZDp8-hFl5NTON4ngnWLsMzGxvKtfpDOOw
|
|
|
95
95
|
truefoundry/deploy/lib/util.py,sha256=J7r8San2wKo48A7-BlH2-OKTlBO67zlPjLEhMsL8os0,1059
|
|
96
96
|
truefoundry/deploy/lib/win32.py,sha256=1RcvPTdlOAJ48rt8rCbE2Ufha2ztRqBAE9dueNXArrY,5009
|
|
97
97
|
truefoundry/deploy/lib/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
|
-
truefoundry/deploy/lib/clients/
|
|
99
|
-
truefoundry/deploy/lib/clients/ask_client.py,sha256=PINeyjZF9IMkphU6ln6JVKlFJrHPhkkE5wolFBrp_f8,13589
|
|
98
|
+
truefoundry/deploy/lib/clients/ask_client.py,sha256=77106708EC16wsi2M1n1_5HgOVboEZoq9_obKsf24M0,13494
|
|
100
99
|
truefoundry/deploy/lib/clients/servicefoundry_client.py,sha256=fmRlPYCimk1ZLbMgdzfJVCbcKRCVnFYL5T3j2uJA0Tc,27037
|
|
101
100
|
truefoundry/deploy/lib/dao/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
101
|
truefoundry/deploy/lib/dao/application.py,sha256=oMszpueXPUfTUuN_XdKwoRjQyqAgWHhZ-10cbprCVdM,9226
|
|
@@ -108,7 +107,7 @@ truefoundry/deploy/lib/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
108
107
|
truefoundry/deploy/lib/model/entity.py,sha256=Bp9sLB-M5INCpw5lPmFdygHWS1zvnLicnSiSCi2iqhQ,8591
|
|
109
108
|
truefoundry/deploy/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
109
|
truefoundry/deploy/v2/lib/__init__.py,sha256=WEiVMZXOVljzEE3tpGJil14liIn_PCDoACJ6b3tZ6sI,188
|
|
111
|
-
truefoundry/deploy/v2/lib/deploy.py,sha256=
|
|
110
|
+
truefoundry/deploy/v2/lib/deploy.py,sha256=kAnh6RO4ci7AVjlIoN1Sr5FmcOU7nbkwNvbrS802spY,12625
|
|
112
111
|
truefoundry/deploy/v2/lib/deploy_workflow.py,sha256=G5BzMIbap8pgDX1eY-TITruUxQdkKhYtBmRwLL6lDeY,14342
|
|
113
112
|
truefoundry/deploy/v2/lib/deployable_patched_models.py,sha256=xbHFD3pURflvCm8EODPvjfvRrv67mlSrjPUknY8SMB8,4060
|
|
114
113
|
truefoundry/deploy/v2/lib/models.py,sha256=ogc1UYs1Z2nBdGSKCrde9sk8d0GxFKMkem99uqO5CmM,1148
|
|
@@ -376,7 +375,7 @@ truefoundry/workflow/remote_filesystem/__init__.py,sha256=LQ95ViEjJ7Ts4JcCGOxMPs
|
|
|
376
375
|
truefoundry/workflow/remote_filesystem/logger.py,sha256=em2l7D6sw7xTLDP0kQSLpgfRRCLpN14Qw85TN7ujQcE,1022
|
|
377
376
|
truefoundry/workflow/remote_filesystem/tfy_signed_url_client.py,sha256=xcT0wQmQlgzcj0nP3tJopyFSVWT1uv3nhiTIuwfXYeg,12342
|
|
378
377
|
truefoundry/workflow/remote_filesystem/tfy_signed_url_fs.py,sha256=nSGPZu0Gyd_jz0KsEE-7w_BmnTD8CVF1S8cUJoxaCbc,13305
|
|
379
|
-
truefoundry-0.
|
|
380
|
-
truefoundry-0.
|
|
381
|
-
truefoundry-0.
|
|
382
|
-
truefoundry-0.
|
|
378
|
+
truefoundry-0.9.0rc1.dist-info/METADATA,sha256=Jke--bMLUmA_EhDKqqVyPODCMgbsIHC0W7yH2nRsjqk,2468
|
|
379
|
+
truefoundry-0.9.0rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
380
|
+
truefoundry-0.9.0rc1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
|
|
381
|
+
truefoundry-0.9.0rc1.dist-info/RECORD,,
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
StreamableHTTP Client Transport Module
|
|
3
|
-
# From https://github.com/modelcontextprotocol/python-sdk/pull/573
|
|
4
|
-
|
|
5
|
-
This module implements the StreamableHTTP transport for MCP clients,
|
|
6
|
-
providing support for HTTP POST requests with optional SSE streaming responses
|
|
7
|
-
and session management.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
from contextlib import asynccontextmanager
|
|
12
|
-
from datetime import timedelta
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
|
-
import anyio
|
|
16
|
-
import httpx
|
|
17
|
-
from httpx_sse import EventSource, aconnect_sse
|
|
18
|
-
from mcp.types import (
|
|
19
|
-
ErrorData,
|
|
20
|
-
JSONRPCError,
|
|
21
|
-
JSONRPCMessage,
|
|
22
|
-
JSONRPCNotification,
|
|
23
|
-
JSONRPCRequest,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
# Header names
|
|
29
|
-
MCP_SESSION_ID_HEADER = "mcp-session-id"
|
|
30
|
-
LAST_EVENT_ID_HEADER = "last-event-id"
|
|
31
|
-
|
|
32
|
-
# Content types
|
|
33
|
-
CONTENT_TYPE_JSON = "application/json"
|
|
34
|
-
CONTENT_TYPE_SSE = "text/event-stream"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@asynccontextmanager
|
|
38
|
-
async def streamablehttp_client(
|
|
39
|
-
url: str,
|
|
40
|
-
headers: dict[str, Any] | None = None,
|
|
41
|
-
timeout: timedelta = timedelta(seconds=30),
|
|
42
|
-
sse_read_timeout: timedelta = timedelta(seconds=60 * 5),
|
|
43
|
-
):
|
|
44
|
-
"""
|
|
45
|
-
Client transport for StreamableHTTP.
|
|
46
|
-
|
|
47
|
-
`sse_read_timeout` determines how long (in seconds) the client will wait for a new
|
|
48
|
-
event before disconnecting. All other HTTP operations are controlled by `timeout`.
|
|
49
|
-
|
|
50
|
-
Yields:
|
|
51
|
-
Tuple of (read_stream, write_stream, terminate_callback)
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
read_stream_writer, read_stream = anyio.create_memory_object_stream[
|
|
55
|
-
JSONRPCMessage | Exception
|
|
56
|
-
](0)
|
|
57
|
-
write_stream, write_stream_reader = anyio.create_memory_object_stream[
|
|
58
|
-
JSONRPCMessage
|
|
59
|
-
](0)
|
|
60
|
-
|
|
61
|
-
async def get_stream():
|
|
62
|
-
"""
|
|
63
|
-
Optional GET stream for server-initiated messages
|
|
64
|
-
"""
|
|
65
|
-
nonlocal session_id
|
|
66
|
-
try:
|
|
67
|
-
# Only attempt GET if we have a session ID
|
|
68
|
-
if not session_id:
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
get_headers = request_headers.copy()
|
|
72
|
-
get_headers[MCP_SESSION_ID_HEADER] = session_id
|
|
73
|
-
|
|
74
|
-
async with aconnect_sse(
|
|
75
|
-
client,
|
|
76
|
-
"GET",
|
|
77
|
-
url,
|
|
78
|
-
headers=get_headers,
|
|
79
|
-
timeout=httpx.Timeout(timeout.seconds, read=sse_read_timeout.seconds),
|
|
80
|
-
) as event_source:
|
|
81
|
-
event_source.response.raise_for_status()
|
|
82
|
-
logger.debug("GET SSE connection established")
|
|
83
|
-
|
|
84
|
-
async for sse in event_source.aiter_sse():
|
|
85
|
-
if sse.event == "message":
|
|
86
|
-
try:
|
|
87
|
-
message = JSONRPCMessage.model_validate_json(sse.data)
|
|
88
|
-
logger.debug(f"GET message: {message}")
|
|
89
|
-
await read_stream_writer.send(message)
|
|
90
|
-
except Exception as exc:
|
|
91
|
-
logger.error(f"Error parsing GET message: {exc}")
|
|
92
|
-
await read_stream_writer.send(exc)
|
|
93
|
-
else:
|
|
94
|
-
logger.warning(f"Unknown SSE event from GET: {sse.event}")
|
|
95
|
-
except Exception as exc:
|
|
96
|
-
# GET stream is optional, so don't propagate errors
|
|
97
|
-
logger.debug(f"GET stream error (non-fatal): {exc}")
|
|
98
|
-
|
|
99
|
-
async def post_writer(client: httpx.AsyncClient):
|
|
100
|
-
nonlocal session_id
|
|
101
|
-
try:
|
|
102
|
-
async with write_stream_reader:
|
|
103
|
-
async for message in write_stream_reader:
|
|
104
|
-
# Add session ID to headers if we have one
|
|
105
|
-
post_headers = request_headers.copy()
|
|
106
|
-
if session_id:
|
|
107
|
-
post_headers[MCP_SESSION_ID_HEADER] = session_id
|
|
108
|
-
|
|
109
|
-
logger.debug(f"Sending client message: {message}")
|
|
110
|
-
|
|
111
|
-
# Handle initial initialization request
|
|
112
|
-
is_initialization = (
|
|
113
|
-
isinstance(message.root, JSONRPCRequest)
|
|
114
|
-
and message.root.method == "initialize"
|
|
115
|
-
)
|
|
116
|
-
if (
|
|
117
|
-
isinstance(message.root, JSONRPCNotification)
|
|
118
|
-
and message.root.method == "notifications/initialized"
|
|
119
|
-
):
|
|
120
|
-
tg.start_soon(get_stream)
|
|
121
|
-
|
|
122
|
-
async with client.stream(
|
|
123
|
-
"POST",
|
|
124
|
-
url,
|
|
125
|
-
json=message.model_dump(
|
|
126
|
-
by_alias=True, mode="json", exclude_none=True
|
|
127
|
-
),
|
|
128
|
-
headers=post_headers,
|
|
129
|
-
) as response:
|
|
130
|
-
if response.status_code == 202:
|
|
131
|
-
logger.debug("Received 202 Accepted")
|
|
132
|
-
continue
|
|
133
|
-
# Check for 404 (session expired/invalid)
|
|
134
|
-
if response.status_code == 404:
|
|
135
|
-
if isinstance(message.root, JSONRPCRequest):
|
|
136
|
-
jsonrpc_error = JSONRPCError(
|
|
137
|
-
jsonrpc="2.0",
|
|
138
|
-
id=message.root.id,
|
|
139
|
-
error=ErrorData(
|
|
140
|
-
code=32600,
|
|
141
|
-
message="Session terminated",
|
|
142
|
-
),
|
|
143
|
-
)
|
|
144
|
-
await read_stream_writer.send(
|
|
145
|
-
JSONRPCMessage(jsonrpc_error)
|
|
146
|
-
)
|
|
147
|
-
continue
|
|
148
|
-
|
|
149
|
-
if not response.is_success:
|
|
150
|
-
_response_content = await response.aread()
|
|
151
|
-
logger.error(
|
|
152
|
-
f"Response: {response.status_code} {_response_content}"
|
|
153
|
-
)
|
|
154
|
-
response.raise_for_status()
|
|
155
|
-
|
|
156
|
-
# Extract session ID from response headers
|
|
157
|
-
if is_initialization:
|
|
158
|
-
new_session_id = response.headers.get(MCP_SESSION_ID_HEADER)
|
|
159
|
-
if new_session_id:
|
|
160
|
-
session_id = new_session_id
|
|
161
|
-
logger.info(f"Received session ID: {session_id}")
|
|
162
|
-
|
|
163
|
-
# Handle different response types
|
|
164
|
-
content_type = response.headers.get("content-type", "").lower()
|
|
165
|
-
|
|
166
|
-
if content_type.startswith(CONTENT_TYPE_JSON):
|
|
167
|
-
try:
|
|
168
|
-
content = await response.aread()
|
|
169
|
-
json_message = JSONRPCMessage.model_validate_json(
|
|
170
|
-
content
|
|
171
|
-
)
|
|
172
|
-
await read_stream_writer.send(json_message)
|
|
173
|
-
except Exception as exc:
|
|
174
|
-
logger.error(f"Error parsing JSON response: {exc}")
|
|
175
|
-
await read_stream_writer.send(exc)
|
|
176
|
-
|
|
177
|
-
elif content_type.startswith(CONTENT_TYPE_SSE):
|
|
178
|
-
# Parse SSE events from the response
|
|
179
|
-
try:
|
|
180
|
-
event_source = EventSource(response)
|
|
181
|
-
async for sse in event_source.aiter_sse():
|
|
182
|
-
if sse.event == "message":
|
|
183
|
-
try:
|
|
184
|
-
await read_stream_writer.send(
|
|
185
|
-
JSONRPCMessage.model_validate_json(
|
|
186
|
-
sse.data
|
|
187
|
-
)
|
|
188
|
-
)
|
|
189
|
-
except Exception as exc:
|
|
190
|
-
logger.exception("Error parsing message")
|
|
191
|
-
await read_stream_writer.send(exc)
|
|
192
|
-
else:
|
|
193
|
-
logger.warning(f"Unknown event: {sse.event}")
|
|
194
|
-
|
|
195
|
-
except Exception as e:
|
|
196
|
-
logger.exception("Error reading SSE stream:")
|
|
197
|
-
await read_stream_writer.send(e)
|
|
198
|
-
|
|
199
|
-
else:
|
|
200
|
-
# For 202 Accepted with no body
|
|
201
|
-
if response.status_code == 202:
|
|
202
|
-
logger.debug("Received 202 Accepted")
|
|
203
|
-
continue
|
|
204
|
-
|
|
205
|
-
error_msg = f"Unexpected content type: {content_type}"
|
|
206
|
-
logger.error(error_msg)
|
|
207
|
-
await read_stream_writer.send(ValueError(error_msg))
|
|
208
|
-
|
|
209
|
-
except Exception as exc:
|
|
210
|
-
logger.error(f"Error in post_writer: {exc}")
|
|
211
|
-
finally:
|
|
212
|
-
await read_stream_writer.aclose()
|
|
213
|
-
await write_stream.aclose()
|
|
214
|
-
|
|
215
|
-
async def terminate_session():
|
|
216
|
-
"""
|
|
217
|
-
Terminate the session by sending a DELETE request.
|
|
218
|
-
"""
|
|
219
|
-
nonlocal session_id
|
|
220
|
-
if not session_id:
|
|
221
|
-
return # No session to terminate
|
|
222
|
-
|
|
223
|
-
try:
|
|
224
|
-
delete_headers = request_headers.copy()
|
|
225
|
-
delete_headers[MCP_SESSION_ID_HEADER] = session_id
|
|
226
|
-
|
|
227
|
-
response = await client.delete(
|
|
228
|
-
url,
|
|
229
|
-
headers=delete_headers,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
if response.status_code == 405:
|
|
233
|
-
# Server doesn't allow client-initiated termination
|
|
234
|
-
logger.debug("Server does not allow session termination")
|
|
235
|
-
elif response.status_code != 200:
|
|
236
|
-
logger.warning(f"Session termination failed: {response.status_code}")
|
|
237
|
-
except Exception as exc:
|
|
238
|
-
logger.warning(f"Session termination failed: {exc}")
|
|
239
|
-
|
|
240
|
-
async with anyio.create_task_group() as tg:
|
|
241
|
-
try:
|
|
242
|
-
logger.debug(f"Connecting to StreamableHTTP endpoint: {url}")
|
|
243
|
-
# Set up headers with required Accept header
|
|
244
|
-
request_headers = {
|
|
245
|
-
"Accept": f"{CONTENT_TYPE_JSON}, {CONTENT_TYPE_SSE}",
|
|
246
|
-
"Content-Type": CONTENT_TYPE_JSON,
|
|
247
|
-
**(headers or {}),
|
|
248
|
-
}
|
|
249
|
-
# Track session ID if provided by server
|
|
250
|
-
session_id: str | None = None
|
|
251
|
-
|
|
252
|
-
async with httpx.AsyncClient(
|
|
253
|
-
headers=request_headers,
|
|
254
|
-
timeout=httpx.Timeout(timeout.seconds, read=sse_read_timeout.seconds),
|
|
255
|
-
follow_redirects=True,
|
|
256
|
-
) as client:
|
|
257
|
-
tg.start_soon(post_writer, client)
|
|
258
|
-
try:
|
|
259
|
-
yield read_stream, write_stream, terminate_session
|
|
260
|
-
finally:
|
|
261
|
-
tg.cancel_scope.cancel()
|
|
262
|
-
finally:
|
|
263
|
-
await read_stream_writer.aclose()
|
|
264
|
-
await write_stream.aclose()
|
|
File without changes
|
|
File without changes
|