langgraph-api 0.2.26__py3-none-any.whl → 0.2.27__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 langgraph-api might be problematic. Click here for more details.

langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.26"
1
+ __version__ = "0.2.27"
@@ -0,0 +1,171 @@
1
+ """ASGI transport that lets you schedule to the main loop.
2
+
3
+ Adapted from: https://github.com/encode/httpx/blob/6c7af967734bafd011164f2a1653abc87905a62b/httpx/_transports/asgi.py#L1
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import typing
9
+
10
+ from httpx import ASGITransport as ASGITransportBase
11
+ from httpx import AsyncByteStream, Request, Response
12
+
13
+ if typing.TYPE_CHECKING: # pragma: no cover
14
+ import asyncio
15
+
16
+ import trio
17
+
18
+ Event = asyncio.Event | trio.Event
19
+
20
+ __all__ = ["ASGITransport"]
21
+
22
+
23
+ def is_running_trio() -> bool:
24
+ try:
25
+ # sniffio is a dependency of trio.
26
+
27
+ # See https://github.com/python-trio/trio/issues/2802
28
+ import sniffio
29
+
30
+ if sniffio.current_async_library() == "trio":
31
+ return True
32
+ except ImportError: # pragma: nocover
33
+ pass
34
+
35
+ return False
36
+
37
+
38
+ def create_event() -> Event:
39
+ if is_running_trio():
40
+ import trio
41
+
42
+ return trio.Event()
43
+
44
+ import asyncio
45
+
46
+ return asyncio.Event()
47
+
48
+
49
+ class ASGIResponseStream(AsyncByteStream):
50
+ def __init__(self, body: list[bytes]) -> None:
51
+ self._body = body
52
+
53
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
54
+ yield b"".join(self._body)
55
+
56
+
57
+ class ASGITransport(ASGITransportBase):
58
+ """
59
+ A custom AsyncTransport that handles sending requests directly to an ASGI app.
60
+
61
+ ```python
62
+ transport = httpx.ASGITransport(
63
+ app=app,
64
+ root_path="/submount",
65
+ client=("1.2.3.4", 123)
66
+ )
67
+ client = httpx.AsyncClient(transport=transport)
68
+ ```
69
+
70
+ Arguments:
71
+
72
+ * `app` - The ASGI application.
73
+ * `raise_app_exceptions` - Boolean indicating if exceptions in the application
74
+ should be raised. Default to `True`. Can be set to `False` for use cases
75
+ such as testing the content of a client 500 response.
76
+ * `root_path` - The root path on which the ASGI application should be mounted.
77
+ * `client` - A two-tuple indicating the client IP and port of incoming requests.
78
+ ```
79
+ """
80
+
81
+ async def handle_async_request(
82
+ self,
83
+ request: Request,
84
+ ) -> Response:
85
+ from langgraph_api.asyncio import call_soon_in_main_loop
86
+
87
+ assert isinstance(request.stream, AsyncByteStream)
88
+
89
+ # ASGI scope.
90
+ scope = {
91
+ "type": "http",
92
+ "asgi": {"version": "3.0"},
93
+ "http_version": "1.1",
94
+ "method": request.method,
95
+ "headers": [(k.lower(), v) for (k, v) in request.headers.raw],
96
+ "scheme": request.url.scheme,
97
+ "path": request.url.path,
98
+ "raw_path": request.url.raw_path.split(b"?")[0],
99
+ "query_string": request.url.query,
100
+ "server": (request.url.host, request.url.port),
101
+ "client": self.client,
102
+ "root_path": self.root_path,
103
+ }
104
+
105
+ # Request.
106
+ request_body_chunks = request.stream.__aiter__()
107
+ request_complete = False
108
+
109
+ # Response.
110
+ status_code = None
111
+ response_headers = None
112
+ body_parts = []
113
+ response_started = False
114
+ response_complete = create_event()
115
+
116
+ # ASGI callables.
117
+
118
+ async def receive() -> dict[str, typing.Any]:
119
+ nonlocal request_complete
120
+
121
+ if request_complete:
122
+ await response_complete.wait()
123
+ return {"type": "http.disconnect"}
124
+
125
+ try:
126
+ body = await request_body_chunks.__anext__()
127
+ except StopAsyncIteration:
128
+ request_complete = True
129
+ return {"type": "http.request", "body": b"", "more_body": False}
130
+ return {"type": "http.request", "body": body, "more_body": True}
131
+
132
+ async def send(message: typing.MutableMapping[str, typing.Any]) -> None:
133
+ nonlocal status_code, response_headers, response_started
134
+
135
+ if message["type"] == "http.response.start":
136
+ assert not response_started
137
+
138
+ status_code = message["status"]
139
+ response_headers = message.get("headers", [])
140
+ response_started = True
141
+
142
+ elif message["type"] == "http.response.body":
143
+ assert not response_complete.is_set()
144
+ body = message.get("body", b"")
145
+ more_body = message.get("more_body", False)
146
+
147
+ if body and request.method != "HEAD":
148
+ body_parts.append(body)
149
+
150
+ if not more_body:
151
+ response_complete.set()
152
+
153
+ try:
154
+ await call_soon_in_main_loop(self.app(scope, receive, send))
155
+ except Exception: # noqa: PIE-786
156
+ if self.raise_app_exceptions:
157
+ raise
158
+
159
+ response_complete.set()
160
+ if status_code is None:
161
+ status_code = 500
162
+ if response_headers is None:
163
+ response_headers = {}
164
+
165
+ assert response_complete.is_set()
166
+ assert status_code is not None
167
+ assert response_headers is not None
168
+
169
+ stream = ASGIResponseStream(body_parts)
170
+
171
+ return Response(status_code, headers=response_headers, stream=stream)
langgraph_api/asyncio.py CHANGED
@@ -6,6 +6,7 @@ from functools import partial
6
6
  from typing import Any, Generic, TypeVar
7
7
 
8
8
  import structlog
9
+ from langgraph.utils.future import chain_future
9
10
 
10
11
  T = TypeVar("T")
11
12
 
@@ -19,6 +20,12 @@ def set_event_loop(loop: asyncio.AbstractEventLoop) -> None:
19
20
  _MAIN_LOOP = loop
20
21
 
21
22
 
23
+ def get_event_loop() -> asyncio.AbstractEventLoop:
24
+ if _MAIN_LOOP is None:
25
+ raise RuntimeError("No event loop set")
26
+ return _MAIN_LOOP
27
+
28
+
22
29
  async def sleep_if_not_done(delay: float, done: asyncio.Event) -> None:
23
30
  try:
24
31
  await asyncio.wait_for(done.wait(), delay)
@@ -118,6 +125,16 @@ def run_coroutine_threadsafe(
118
125
  return future
119
126
 
120
127
 
128
+ def call_soon_in_main_loop(coro: Coroutine[Any, Any, T]) -> asyncio.Future[T]:
129
+ """Run a coroutine in the main event loop."""
130
+ if _MAIN_LOOP is None:
131
+ raise RuntimeError("No event loop set")
132
+ main_loop_fut = asyncio.ensure_future(coro, loop=_MAIN_LOOP)
133
+ this_loop_fut = asyncio.get_running_loop().create_future()
134
+ _MAIN_LOOP.call_soon_threadsafe(chain_future, main_loop_fut, this_loop_fut)
135
+ return this_loop_fut
136
+
137
+
121
138
  class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
122
139
  """An async task group that can be configured to wait and/or cancel tasks on exit.
123
140
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.2.26
3
+ Version: 0.2.27
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -1,5 +1,5 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
- langgraph_api/__init__.py,sha256=d4vvnQjTf3nZeJvMOie0EHTHbjNFC-xqHs5rOv3P7oQ,23
2
+ langgraph_api/__init__.py,sha256=gCfzMhLTA5cL7eXcOlChBuYFkjCn8H1SjU3SRPiSnV8,23
3
3
  langgraph_api/api/__init__.py,sha256=YVzpbn5IQotvuuLG9fhS9QMrxXfP4s4EpEMG0n4q3Nw,5625
4
4
  langgraph_api/api/assistants.py,sha256=4_tZCXlOmFg-YwgtsCZPc1wr-3fexqJ6A3_2a5giF90,15811
5
5
  langgraph_api/api/mcp.py,sha256=RvRYgANqRzNQzSmgjNkq4RlKTtoEJYil04ot9lsmEtE,14352
@@ -9,7 +9,8 @@ langgraph_api/api/runs.py,sha256=ys8X3g2EGbuF_OF1htM4jcLu4Mqztd8v7ttosmIbdsw,179
9
9
  langgraph_api/api/store.py,sha256=G4Fm8hgFLlJUW5_ekLS_IOgCfpFy-TK9uq5r9QTOELQ,5447
10
10
  langgraph_api/api/threads.py,sha256=ogMKmEoiycuaV3fa5kpupDohJ7fwUOfVczt6-WSK4FE,9322
11
11
  langgraph_api/api/ui.py,sha256=2nlipYV2nUGR4T9pceaAbgN1lS3-T2zPBh7Nv3j9eZQ,2479
12
- langgraph_api/asyncio.py,sha256=h0eZ7aoDGnJpoxnHLZABVlj1jQ78UxjgiHntTmAEWek,8613
12
+ langgraph_api/asgi_transport.py,sha256=eqifhHxNnxvI7jJqrY1_8RjL4Fp9NdN4prEub2FWBt8,5091
13
+ langgraph_api/asyncio.py,sha256=nelZwKL7iOjM5GHj1rVjiPE7igUIKLNKtc-3urxmlfo,9250
13
14
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  langgraph_api/auth/custom.py,sha256=f_gKqtz1BlPQcwDBlG91k78nxAWKLcxU3wF1tvbZByg,22120
15
16
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -97,8 +98,8 @@ langgraph_license/validation.py,sha256=ZKraAVJArAABKqrmHN-EN18ncoNUmRm500Yt1Sc7t
97
98
  langgraph_runtime/__init__.py,sha256=O4GgSmu33c-Pr8Xzxj_brcK5vkm70iNTcyxEjICFZxA,1075
98
99
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
99
100
  openapi.json,sha256=ZMY9UXZJYiFK59z8QmDxVZ7LV6KonQbHzG-D5h-ZTYE,135412
100
- langgraph_api-0.2.26.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
- langgraph_api-0.2.26.dist-info/METADATA,sha256=rufG48q5XwNj15-eMSn0ObNMtA3PmzYLRrFwLME4q7w,4275
102
- langgraph_api-0.2.26.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
103
- langgraph_api-0.2.26.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
104
- langgraph_api-0.2.26.dist-info/RECORD,,
101
+ langgraph_api-0.2.27.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
102
+ langgraph_api-0.2.27.dist-info/METADATA,sha256=4P_VhygryWO5uvruB4jT-yFWLh2b3DDdh0yrgHzX38A,4275
103
+ langgraph_api-0.2.27.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
104
+ langgraph_api-0.2.27.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
105
+ langgraph_api-0.2.27.dist-info/RECORD,,