truefoundry 0.7.2__py3-none-any.whl → 0.7.3rc1__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 CHANGED
@@ -33,4 +33,4 @@ class _LazyTrueFoundry(truefoundry_sdk.TrueFoundry):
33
33
  )
34
34
 
35
35
 
36
- client = _LazyTrueFoundry().v1
36
+ client = _LazyTrueFoundry()
@@ -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
- self._terminate_cb,
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),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truefoundry
3
- Version: 0.7.2
3
+ Version: 0.7.3rc1
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.7.1; python_version >= '3.10'
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==0.0.16
34
+ Requires-Dist: truefoundry-sdk==0.1.0
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=VQEfRvPE7nuqq--q28cpmnIYPG3RH52RSifIFOzzvTg,1420
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
@@ -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/_mcp_streamable_http.py,sha256=oO_pdgVy0BkksLDZo-arcnaJ4gSaTJmMMZF9u-uF7Mk,10851
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
@@ -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.7.2.dist-info/METADATA,sha256=wjXiGA-c9eUYLeH9Qcy2QfYLBmi9FOa1j-R5AABgtgg,2459
380
- truefoundry-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
381
- truefoundry-0.7.2.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
382
- truefoundry-0.7.2.dist-info/RECORD,,
378
+ truefoundry-0.7.3rc1.dist-info/METADATA,sha256=lZHcPxXnM4vcAzc06dsU8ssksI2aCkSOoplZR9of_TA,2461
379
+ truefoundry-0.7.3rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
380
+ truefoundry-0.7.3rc1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
381
+ truefoundry-0.7.3rc1.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()