truefoundry 0.7.1rc1__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()
@@ -100,13 +100,13 @@ def create_truefoundry_cli() -> click.Group:
100
100
  cli.add_command(get_terminate_command())
101
101
  cli.add_command(get_ml_cli())
102
102
  cli.add_command(get_get_command())
103
+ cli.add_command(get_ask_command())
103
104
 
104
105
  if not (sys.platform.startswith("win32") or sys.platform.startswith("cygwin")):
105
106
  cli.add_command(get_patch_command())
106
107
 
107
108
  if is_internal_env_set():
108
109
  cli.add_command(get_build_command())
109
- cli.add_command(get_ask_command())
110
110
  return cli
111
111
 
112
112
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: application.json
3
- # timestamp: 2025-04-21T06:26:10+00:00
3
+ # timestamp: 2025-05-03T01:45:46+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1485,6 +1485,10 @@ class WorkflowAlert(BaseModel):
1485
1485
  """
1486
1486
 
1487
1487
  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
+ )
1488
1492
  on_completion: bool = Field(
1489
1493
  False, description="Send an alert when the job completes"
1490
1494
  )
@@ -1552,10 +1556,12 @@ class JobAlert(BaseModel):
1552
1556
  description="List of recipients' email addresses if the notification channel is Email.",
1553
1557
  )
1554
1558
  notification_target: Optional[Union[Email, SlackWebhook, SlackBot]] = None
1555
- on_start: bool = Field(False, description="Send an alert when the job starts")
1556
- on_completion: bool = Field(
1557
- False, description="Send an alert when the job completes"
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",
1558
1562
  )
1563
+ on_start: bool = Field(False, description="Send an alert when the job starts")
1564
+ on_completion: bool = False
1559
1565
  on_failure: bool = Field(True, description="Send an alert when the job fails")
1560
1566
 
1561
1567
 
@@ -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.1rc1
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.6.0; 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
@@ -28,7 +28,7 @@ truefoundry/autodeploy/utils/client.py,sha256=PvbSkfgAjAogGjisinqmh4mP4svowxAC0I
28
28
  truefoundry/autodeploy/utils/diff.py,sha256=Ef8Y-VffDKel_-q-GxRam6gqiv8qTLMcqVg6iifXfcA,5358
29
29
  truefoundry/autodeploy/utils/pydantic_compat.py,sha256=hEAUy5kLjhPdzw7yGZ2iXGMXbbMVXVlGzIofmyHafXQ,412
30
30
  truefoundry/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- truefoundry/cli/__main__.py,sha256=DUEAL34G8EHchlgJ38pXyVvXa-AmHu498s2JRK0m1xY,3980
31
+ truefoundry/cli/__main__.py,sha256=k9wgXDoUOYdsK5Ply_GpDV-Ns4DNn0jRKNUQSCRnZ-0,3976
32
32
  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
@@ -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=yIGmwEptVUIM1R5DOL8JYp2-8T-YDDumADS4ZAFoEcI,71727
53
+ truefoundry/deploy/_autogen/models.py,sha256=p6sZVLmrFbzujNW6PzjGbPA1OiNhq5TgX5tH0evJvRE,72038
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
@@ -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.1rc1.dist-info/METADATA,sha256=suz7u8aYbxmDQlQs-V-bMwEPxlNM0h0aH4l4zqJ8PnE,2462
380
- truefoundry-0.7.1rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
381
- truefoundry-0.7.1rc1.dist-info/entry_points.txt,sha256=xVjn7RMN-MW2-9f7YU-bBdlZSvvrwzhpX1zmmRmsNPU,98
382
- truefoundry-0.7.1rc1.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()