langgraph-api 0.2.25__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 +1 -1
- langgraph_api/asgi_transport.py +171 -0
- langgraph_api/asyncio.py +17 -0
- langgraph_api/auth/custom.py +7 -0
- {langgraph_api-0.2.25.dist-info → langgraph_api-0.2.27.dist-info}/METADATA +1 -1
- {langgraph_api-0.2.25.dist-info → langgraph_api-0.2.27.dist-info}/RECORD +9 -8
- {langgraph_api-0.2.25.dist-info → langgraph_api-0.2.27.dist-info}/LICENSE +0 -0
- {langgraph_api-0.2.25.dist-info → langgraph_api-0.2.27.dist-info}/WHEEL +0 -0
- {langgraph_api-0.2.25.dist-info → langgraph_api-0.2.27.dist-info}/entry_points.txt +0 -0
langgraph_api/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
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
|
|
langgraph_api/auth/custom.py
CHANGED
|
@@ -448,6 +448,9 @@ class DotDict:
|
|
|
448
448
|
def __len__(self):
|
|
449
449
|
return len(self._dict)
|
|
450
450
|
|
|
451
|
+
def __reduce__(self):
|
|
452
|
+
return (self.__class__, (self._dict,))
|
|
453
|
+
|
|
451
454
|
|
|
452
455
|
class ProxyUser(BaseUser):
|
|
453
456
|
"""A proxy that wraps a user object to ensure it has all BaseUser properties.
|
|
@@ -517,6 +520,10 @@ class ProxyUser(BaseUser):
|
|
|
517
520
|
def __str__(self) -> str:
|
|
518
521
|
return f"{self._user}"
|
|
519
522
|
|
|
523
|
+
def __reduce__(self):
|
|
524
|
+
# Only store the wrapped user
|
|
525
|
+
return (self.__class__, (self._user,))
|
|
526
|
+
|
|
520
527
|
|
|
521
528
|
class SimpleUser(ProxyUser):
|
|
522
529
|
def __init__(self, username: str):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
2
|
-
langgraph_api/__init__.py,sha256=
|
|
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,9 +9,10 @@ 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/
|
|
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
|
-
langgraph_api/auth/custom.py,sha256=
|
|
15
|
+
langgraph_api/auth/custom.py,sha256=f_gKqtz1BlPQcwDBlG91k78nxAWKLcxU3wF1tvbZByg,22120
|
|
15
16
|
langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
17
|
langgraph_api/auth/langsmith/backend.py,sha256=UNsXa1rXuUJy8fdnasdILIWoxWIlHafY03YJChV0USk,2764
|
|
17
18
|
langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
|
|
@@ -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.
|
|
101
|
-
langgraph_api-0.2.
|
|
102
|
-
langgraph_api-0.2.
|
|
103
|
-
langgraph_api-0.2.
|
|
104
|
-
langgraph_api-0.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|