langgraph-sdk 0.4.2__tar.gz → 0.4.3__tar.gz
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.
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/.gitignore +5 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/MIGRATION.md +3 -1
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/Makefile +0 -1
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/PKG-INFO +38 -15
- langgraph_sdk-0.4.3/README.md +98 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/Dockerfile +13 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/docker-compose.yml +10 -3
- langgraph_sdk-0.4.3/integration/graph/factory_graph.py +70 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/langgraph.json +2 -1
- langgraph_sdk-0.4.3/langgraph_sdk/__init__.py +15 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/cron.py +12 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/cron.py +12 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/auth/__init__.py +2 -3
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/encryption/__init__.py +12 -5
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/encryption/types.py +30 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/schema.py +1 -1
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/pyproject.toml +8 -5
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/conftest.py +6 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_assistants.py +5 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_cancel.py +5 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_crons.py +5 -4
- langgraph_sdk-0.4.3/tests/integration/test_factory_graph.py +66 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_runs.py +5 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_store.py +5 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_websocket.py +5 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_controller.py +9 -15
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_decoders.py +2 -1
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_extensions_projection.py +1 -2
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_lifecycle_watcher.py +2 -2
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_scoped_handles.py +4 -10
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_shared_stream.py +3 -11
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_extensions_projection.py +1 -2
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_projections.py +3 -7
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_scoped_handles.py +2 -2
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_thread_stream.py +22 -87
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_transport_ws.py +2 -3
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_thread_stream.py +7 -66
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_tool_calls_projection.py +3 -5
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_transport_http.py +10 -42
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_transport_ws.py +2 -4
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_client_stream.py +5 -5
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_crons_client.py +49 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_encryption.py +32 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_langsmith_tracing.py +2 -5
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_path_encoding.py +2 -1
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_serde.py +1 -1
- langgraph_sdk-0.4.3/uv.lock +1547 -0
- langgraph_sdk-0.4.2/README.md +0 -75
- langgraph_sdk-0.4.2/langgraph_sdk/__init__.py +0 -8
- langgraph_sdk-0.4.2/uv.lock +0 -1662
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/CHANGELOG.md +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/LICENSE +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/graph/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/graph/deep_agent.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/graph/streaming_graph.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/graph/tools_agent.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/_common.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_cancel.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_concurrent.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_extensions.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_helpers.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_lifecycle.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_messages.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_reconnect.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_subgraphs.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_tools.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_update_state.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_values.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/integration/scripts/test_websocket.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/assistants.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/client.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/http.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/runs.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/store.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/stream.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_async/threads.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_shared/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_shared/types.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_shared/utilities.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/assistants.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/client.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/http.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/runs.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/store.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/stream.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/_sync/threads.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/auth/exceptions.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/auth/types.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/cache.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/client.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/errors.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/py.typed +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/runtime.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/sse.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/controller.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/decoders.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/multi_cursor_buffer.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/subscription.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/sync_controller.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/base.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/http.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/sync_http.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/sync_ws.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/langgraph_sdk/stream/transport/ws.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/fixtures/response.txt +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_concurrent.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_extensions.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_helpers.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_lifecycle.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_messages.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_reconnect.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_remote_graph_v3.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_subgraphs.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_threads_crud.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_tools.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_update_state.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/integration/test_values.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/__init__.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/_events.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/_fake_server.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/_sync_fake_server.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/assert_transport_replays.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_messages_projection.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_multi_cursor_buffer.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_output.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_replay_conformance.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_subscription.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_shared_stream.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_sync_transport_http.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_transport_path_encoding.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/streaming/test_values_projection.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_api_parity.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_assistants_client.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_cache.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_client_exports.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_errors.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_serde_schema.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_skip_auto_load_api_key.py +0 -0
- {langgraph_sdk-0.4.2 → langgraph_sdk-0.4.3}/tests/test_threads_client.py +0 -0
|
@@ -77,6 +77,10 @@ __pypackages__/
|
|
|
77
77
|
# Environments
|
|
78
78
|
.env
|
|
79
79
|
.envrc
|
|
80
|
+
*.crt
|
|
81
|
+
*.key
|
|
82
|
+
*.pem
|
|
83
|
+
credentials.json
|
|
80
84
|
.venv
|
|
81
85
|
.venvs
|
|
82
86
|
env/
|
|
@@ -98,6 +102,7 @@ dmypy.json
|
|
|
98
102
|
|
|
99
103
|
.vercel
|
|
100
104
|
.turbo
|
|
105
|
+
node_modules/
|
|
101
106
|
.editorconfig
|
|
102
107
|
.scratch
|
|
103
108
|
.worktrees/
|
|
@@ -87,7 +87,9 @@ async with client.threads.stream(assistant_id="agent") as thread:
|
|
|
87
87
|
|
|
88
88
|
```python
|
|
89
89
|
async with client.threads.stream(assistant_id="agent") as thread:
|
|
90
|
-
await thread.run.start(
|
|
90
|
+
await thread.run.start(
|
|
91
|
+
input={"messages": [{"role": "user", "content": "book a flight"}]}
|
|
92
|
+
)
|
|
91
93
|
|
|
92
94
|
# Wait for the run to pause at an interrupt node.
|
|
93
95
|
# thread.interrupted becomes True when input.requested arrives.
|
|
@@ -9,7 +9,6 @@ test:
|
|
|
9
9
|
|
|
10
10
|
# Define a variable for Python and notebook files.
|
|
11
11
|
PYTHON_FILES=.
|
|
12
|
-
MYPY_CACHE=.mypy_cache
|
|
13
12
|
lint format: PYTHON_FILES=.
|
|
14
13
|
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
|
|
15
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: langgraph-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: SDK for interacting with LangGraph API
|
|
5
5
|
Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-py
|
|
6
6
|
Project-URL: Twitter, https://x.com/langchain_oss
|
|
@@ -13,23 +13,36 @@ Requires-Dist: httpx>=0.25.2
|
|
|
13
13
|
Requires-Dist: langchain-core<2,>=1.4.0
|
|
14
14
|
Requires-Dist: langchain-protocol>=0.0.15
|
|
15
15
|
Requires-Dist: orjson>=3.11.5
|
|
16
|
-
Requires-Dist: websockets<
|
|
16
|
+
Requires-Dist: websockets<17,>=14
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
|
|
19
19
|
# LangGraph Python SDK
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
[](https://pypi.org/project/langgraph-sdk/#history)
|
|
22
|
+
[](https://opensource.org/licenses/MIT)
|
|
23
|
+
[](https://pypistats.org/packages/langgraph-sdk)
|
|
24
|
+
[](https://x.com/langchain_oss)
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
To help you ship LangGraph apps to production faster, check out [LangSmith](https://www.langchain.com/langsmith).
|
|
27
|
+
[LangSmith](https://www.langchain.com/langsmith) is a unified developer platform for building, testing, and monitoring LLM applications.
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
## Quick Install
|
|
26
30
|
|
|
27
31
|
```bash
|
|
28
|
-
|
|
32
|
+
uv add langgraph-sdk
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
## 🤔 What is this?
|
|
36
|
+
|
|
37
|
+
This library provides the Python SDK for interacting with the LangGraph API. Use it to connect to a running LangGraph API server, manage assistants and threads, and stream runs from Python applications.
|
|
38
|
+
|
|
39
|
+
You will need a running LangGraph API server. If you're running a server locally using `langgraph-cli`, the SDK will automatically point at `http://localhost:8123`; otherwise, specify the server URL when creating a client.
|
|
40
|
+
|
|
41
|
+
## 📖 Documentation
|
|
42
|
+
|
|
43
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langgraph-sdk/). For conceptual guides and tutorials, see the [LangGraph Docs](https://docs.langchain.com/oss/python/langgraph/overview).
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
33
46
|
|
|
34
47
|
```python
|
|
35
48
|
from langgraph_sdk import get_client
|
|
@@ -48,7 +61,9 @@ thread = await client.threads.create()
|
|
|
48
61
|
|
|
49
62
|
# Start a streaming run
|
|
50
63
|
input = {"messages": [{"role": "human", "content": "what's the weather in la"}]}
|
|
51
|
-
async for chunk in client.runs.stream(
|
|
64
|
+
async for chunk in client.runs.stream(
|
|
65
|
+
thread["thread_id"], agent["assistant_id"], input=input
|
|
66
|
+
):
|
|
52
67
|
print(chunk)
|
|
53
68
|
```
|
|
54
69
|
|
|
@@ -61,9 +76,7 @@ async for chunk in client.runs.stream(thread['thread_id'], agent['assistant_id']
|
|
|
61
76
|
|
|
62
77
|
## Thread-Centric Streaming (v3)
|
|
63
78
|
|
|
64
|
-
`client.threads.stream()` returns a context manager that owns the SSE session for one
|
|
65
|
-
thread. Typed projections — values snapshots, message streams, tool calls, custom
|
|
66
|
-
events — all share the same underlying connection.
|
|
79
|
+
`client.threads.stream()` returns a context manager that owns the SSE session for one thread. Typed projections — values snapshots, message streams, tool calls, custom events — all share the same underlying connection.
|
|
67
80
|
|
|
68
81
|
```python
|
|
69
82
|
from langgraph_sdk import get_client
|
|
@@ -87,7 +100,17 @@ async with client.threads.stream(
|
|
|
87
100
|
messages, tool_calls = await asyncio.gather(get_messages(), get_tool_calls())
|
|
88
101
|
|
|
89
102
|
for stream in messages:
|
|
90
|
-
print(await stream.text)
|
|
103
|
+
print(await stream.text) # accumulated text
|
|
91
104
|
|
|
92
|
-
final = await thread.output
|
|
105
|
+
final = await thread.output # terminal state values
|
|
93
106
|
```
|
|
107
|
+
|
|
108
|
+
## 📕 Releases & Versioning
|
|
109
|
+
|
|
110
|
+
See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
|
|
111
|
+
|
|
112
|
+
## 💁 Contributing
|
|
113
|
+
|
|
114
|
+
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
|
|
115
|
+
|
|
116
|
+
For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# LangGraph Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/langgraph-sdk/#history)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://pypistats.org/packages/langgraph-sdk)
|
|
6
|
+
[](https://x.com/langchain_oss)
|
|
7
|
+
|
|
8
|
+
To help you ship LangGraph apps to production faster, check out [LangSmith](https://www.langchain.com/langsmith).
|
|
9
|
+
[LangSmith](https://www.langchain.com/langsmith) is a unified developer platform for building, testing, and monitoring LLM applications.
|
|
10
|
+
|
|
11
|
+
## Quick Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add langgraph-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 🤔 What is this?
|
|
18
|
+
|
|
19
|
+
This library provides the Python SDK for interacting with the LangGraph API. Use it to connect to a running LangGraph API server, manage assistants and threads, and stream runs from Python applications.
|
|
20
|
+
|
|
21
|
+
You will need a running LangGraph API server. If you're running a server locally using `langgraph-cli`, the SDK will automatically point at `http://localhost:8123`; otherwise, specify the server URL when creating a client.
|
|
22
|
+
|
|
23
|
+
## 📖 Documentation
|
|
24
|
+
|
|
25
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langgraph-sdk/). For conceptual guides and tutorials, see the [LangGraph Docs](https://docs.langchain.com/oss/python/langgraph/overview).
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from langgraph_sdk import get_client
|
|
31
|
+
|
|
32
|
+
# If you're using a remote server, initialize the client with `get_client(url=REMOTE_URL)`
|
|
33
|
+
client = get_client()
|
|
34
|
+
|
|
35
|
+
# List all assistants
|
|
36
|
+
assistants = await client.assistants.search()
|
|
37
|
+
|
|
38
|
+
# We auto-create an assistant for each graph you register in config.
|
|
39
|
+
agent = assistants[0]
|
|
40
|
+
|
|
41
|
+
# Start a new thread
|
|
42
|
+
thread = await client.threads.create()
|
|
43
|
+
|
|
44
|
+
# Start a streaming run
|
|
45
|
+
input = {"messages": [{"role": "human", "content": "what's the weather in la"}]}
|
|
46
|
+
async for chunk in client.runs.stream(
|
|
47
|
+
thread["thread_id"], agent["assistant_id"], input=input
|
|
48
|
+
):
|
|
49
|
+
print(chunk)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Known Limitations
|
|
53
|
+
|
|
54
|
+
- **WebSocket transport** requires `websockets>=14` and is only available on the async client (`AsyncThreadStream`). The sync client (`SyncThreadStream`) uses SSE exclusively.
|
|
55
|
+
- **`thread.extensions[name]`** opens a new subscription each time the same name is accessed. Assign the projection to a variable and reuse it within a single session rather than re-indexing across multiple iterations.
|
|
56
|
+
- **Sync streaming** drives the lifecycle watcher in a background thread. Long-lived sync sessions will hold that thread open until the context manager exits.
|
|
57
|
+
- **Reconnect attempts** are limited to 5 by default for both the shared SSE fan-out and the lifecycle watcher. Persistent network partitions will surface as `RuntimeError` on in-flight projections.
|
|
58
|
+
|
|
59
|
+
## Thread-Centric Streaming (v3)
|
|
60
|
+
|
|
61
|
+
`client.threads.stream()` returns a context manager that owns the SSE session for one thread. Typed projections — values snapshots, message streams, tool calls, custom events — all share the same underlying connection.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from langgraph_sdk import get_client
|
|
65
|
+
import asyncio
|
|
66
|
+
|
|
67
|
+
client = get_client()
|
|
68
|
+
|
|
69
|
+
async with client.threads.stream(
|
|
70
|
+
thread_id="my-thread",
|
|
71
|
+
assistant_id="agent",
|
|
72
|
+
) as thread:
|
|
73
|
+
await thread.run.start(input={"messages": [{"role": "user", "content": "hi"}]})
|
|
74
|
+
|
|
75
|
+
# Start all consumers concurrently so they share one SSE connection.
|
|
76
|
+
async def get_messages():
|
|
77
|
+
return [s async for s in thread.messages]
|
|
78
|
+
|
|
79
|
+
async def get_tool_calls():
|
|
80
|
+
return [c async for c in thread.tool_calls]
|
|
81
|
+
|
|
82
|
+
messages, tool_calls = await asyncio.gather(get_messages(), get_tool_calls())
|
|
83
|
+
|
|
84
|
+
for stream in messages:
|
|
85
|
+
print(await stream.text) # accumulated text
|
|
86
|
+
|
|
87
|
+
final = await thread.output # terminal state values
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 📕 Releases & Versioning
|
|
91
|
+
|
|
92
|
+
See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
|
|
93
|
+
|
|
94
|
+
## 💁 Contributing
|
|
95
|
+
|
|
96
|
+
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
|
|
97
|
+
|
|
98
|
+
For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
|
|
@@ -22,6 +22,19 @@ RUN pip install --no-cache-dir \
|
|
|
22
22
|
"langchain>=1.3.0" \
|
|
23
23
|
"deepagents>=0.6.2"
|
|
24
24
|
|
|
25
|
+
# Swap the published langgraph *core* for this monorepo's local copy, so the
|
|
26
|
+
# server executes the langgraph under test rather than the latest release.
|
|
27
|
+
# We keep the rest of the base image (langgraph-api, runtime, Go core server)
|
|
28
|
+
# on `latest` — that still surfaces upstream regressions — but the core the
|
|
29
|
+
# server runs is now the PR's, which is what lets this suite catch core
|
|
30
|
+
# regressions (e.g. ensure_config / runtime changes) before they're published.
|
|
31
|
+
# `--no-deps` keeps the base image's already-compatible
|
|
32
|
+
# checkpoint/prebuilt/sdk; we only replace core. The local source comes from
|
|
33
|
+
# the `langgraph_src` additional build context (see docker-compose.yml).
|
|
34
|
+
COPY --from=langgraph_src pyproject.toml README.md LICENSE /opt/langgraph-src/
|
|
35
|
+
COPY --from=langgraph_src langgraph /opt/langgraph-src/langgraph/
|
|
36
|
+
RUN pip install --no-cache-dir --force-reinstall --no-deps /opt/langgraph-src
|
|
37
|
+
|
|
25
38
|
# Project graphs + registration config.
|
|
26
39
|
COPY graph/ /app/graph/
|
|
27
40
|
COPY langgraph.json /app/langgraph.json
|
|
@@ -36,11 +36,18 @@ services:
|
|
|
36
36
|
api:
|
|
37
37
|
# Thin layer on top of langchain/langgraph-api:latest-py3.12 —
|
|
38
38
|
# see ./Dockerfile. The base image bundles langgraph-api +
|
|
39
|
-
# langgraph_runtime_postgres + langgraph_license + the Go core-server
|
|
40
|
-
#
|
|
39
|
+
# langgraph_runtime_postgres + langgraph_license + the Go core-server;
|
|
40
|
+
# on top we add graph deps (deepagents), the graph files, and this
|
|
41
|
+
# monorepo's local langgraph *core* (so the server runs the code under
|
|
42
|
+
# test, not the released langgraph).
|
|
41
43
|
build:
|
|
42
44
|
context: .
|
|
43
45
|
dockerfile: Dockerfile
|
|
46
|
+
additional_contexts:
|
|
47
|
+
# The monorepo's local langgraph core (libs/langgraph), installed over
|
|
48
|
+
# the base image so the server runs the langgraph under test. See the
|
|
49
|
+
# Dockerfile for why.
|
|
50
|
+
langgraph_src: ../../langgraph
|
|
44
51
|
image: langgraph-v3-integration-api:local
|
|
45
52
|
depends_on:
|
|
46
53
|
postgres:
|
|
@@ -70,7 +77,7 @@ services:
|
|
|
70
77
|
# Tell langgraph-api which graphs to register. The langgraph CLI sets
|
|
71
78
|
# this from langgraph.json; we're bypassing the CLI (running uvicorn
|
|
72
79
|
# directly) so we set it manually.
|
|
73
|
-
LANGSERVE_GRAPHS: '{"agent":"/app/graph/streaming_graph.py:graph","tools_agent":"/app/graph/tools_agent.py:graph","deep_agent":"/app/graph/deep_agent.py:graph"}'
|
|
80
|
+
LANGSERVE_GRAPHS: '{"agent":"/app/graph/streaming_graph.py:graph","tools_agent":"/app/graph/tools_agent.py:graph","deep_agent":"/app/graph/deep_agent.py:graph","factory_agent":"/app/graph/factory_graph.py:make_graph"}'
|
|
74
81
|
|
|
75
82
|
# The published langgraph-api image is the `licensed` variant and
|
|
76
83
|
# requires a real LANGSMITH_API_KEY (or LANGGRAPH_CLOUD_LICENSE_KEY)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Integration fixture: a graph *factory* (not a pre-compiled graph).
|
|
2
|
+
|
|
3
|
+
Registered as `factory_agent`. The other integration graphs are all
|
|
4
|
+
pre-compiled objects, so this is the only fixture that drives the server's
|
|
5
|
+
graph-factory code path on a run. That path is where langgraph-api seeds a
|
|
6
|
+
`ServerRuntime` into the run config; executing a run against this graph is the
|
|
7
|
+
end-to-end regression guard for the langgraph 1.2.3 `ensure_config`
|
|
8
|
+
configurable-merge surfacing a leaked `__pregel_runtime`
|
|
9
|
+
(`AttributeError: '_ExecutionRuntime' object has no attribute 'control'`).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from langgraph.graph.state import END, CompiledStateGraph, StateGraph
|
|
17
|
+
from langgraph.store.base import BaseStore
|
|
18
|
+
from typing_extensions import TypedDict
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from langgraph.types import RunnableConfig
|
|
22
|
+
|
|
23
|
+
from langgraph_sdk.runtime import ServerRuntime
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class State(TypedDict, total=False):
|
|
27
|
+
text: str
|
|
28
|
+
access_context: str
|
|
29
|
+
is_for_execution: bool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def make_graph(
|
|
33
|
+
config: RunnableConfig, runtime: ServerRuntime
|
|
34
|
+
) -> CompiledStateGraph:
|
|
35
|
+
"""2-arg factory (config + runtime) returning a compiled echo graph.
|
|
36
|
+
|
|
37
|
+
Validates that the server injected a real `ServerRuntime`, then builds a
|
|
38
|
+
one-node graph whose output echoes the input and reports the runtime's
|
|
39
|
+
access context (so the test can confirm the factory ran under execution).
|
|
40
|
+
"""
|
|
41
|
+
from langgraph_sdk.runtime import (
|
|
42
|
+
_ExecutionRuntime,
|
|
43
|
+
_ReadRuntime,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# `if/raise` rather than `assert` so the contract holds under `python -O`.
|
|
47
|
+
if not isinstance(runtime, (_ExecutionRuntime, _ReadRuntime)):
|
|
48
|
+
raise TypeError(
|
|
49
|
+
f"factory must receive a ServerRuntime, got {type(runtime).__name__}"
|
|
50
|
+
)
|
|
51
|
+
if not isinstance(runtime.store, BaseStore):
|
|
52
|
+
raise TypeError(
|
|
53
|
+
f"runtime.store must be a BaseStore, got {type(runtime.store).__name__}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
access_context = runtime.access_context
|
|
57
|
+
is_for_execution = runtime.execution_runtime is not None
|
|
58
|
+
|
|
59
|
+
def echo(state: State) -> State:
|
|
60
|
+
return {
|
|
61
|
+
"text": (state.get("text") or "") + " echoed",
|
|
62
|
+
"access_context": access_context,
|
|
63
|
+
"is_for_execution": is_for_execution,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
workflow = StateGraph(State)
|
|
67
|
+
workflow.add_node("echo", echo)
|
|
68
|
+
workflow.set_entry_point("echo")
|
|
69
|
+
workflow.add_edge("echo", END)
|
|
70
|
+
return workflow.compile()
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"graphs": {
|
|
5
5
|
"agent": "./graph/streaming_graph.py:graph",
|
|
6
6
|
"tools_agent": "./graph/tools_agent.py:graph",
|
|
7
|
-
"deep_agent": "./graph/deep_agent.py:graph"
|
|
7
|
+
"deep_agent": "./graph/deep_agent.py:graph",
|
|
8
|
+
"factory_agent": "./graph/factory_graph.py:make_graph"
|
|
8
9
|
},
|
|
9
10
|
"env": {}
|
|
10
11
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from langgraph_sdk.auth import Auth
|
|
2
|
+
from langgraph_sdk.client import get_client, get_sync_client
|
|
3
|
+
from langgraph_sdk.encryption import Encryption
|
|
4
|
+
from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
|
|
5
|
+
|
|
6
|
+
__version__ = "0.4.3"
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Auth",
|
|
10
|
+
"DecryptResult",
|
|
11
|
+
"Encryption",
|
|
12
|
+
"EncryptionContext",
|
|
13
|
+
"get_client",
|
|
14
|
+
"get_sync_client",
|
|
15
|
+
]
|
|
@@ -8,7 +8,11 @@ from datetime import datetime, tzinfo
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from langgraph_sdk._async.http import HttpClient
|
|
11
|
-
from langgraph_sdk._shared.utilities import
|
|
11
|
+
from langgraph_sdk._shared.utilities import (
|
|
12
|
+
NOT_PROVIDED,
|
|
13
|
+
_quote_path_param,
|
|
14
|
+
_resolve_timezone,
|
|
15
|
+
)
|
|
12
16
|
from langgraph_sdk.schema import (
|
|
13
17
|
All,
|
|
14
18
|
Config,
|
|
@@ -324,7 +328,7 @@ class CronClient:
|
|
|
324
328
|
cron_id: str,
|
|
325
329
|
*,
|
|
326
330
|
schedule: str | None = None,
|
|
327
|
-
end_time: datetime | None =
|
|
331
|
+
end_time: datetime | None = NOT_PROVIDED,
|
|
328
332
|
input: Input | None = None,
|
|
329
333
|
metadata: Mapping[str, Any] | None = None,
|
|
330
334
|
config: Config | None = None,
|
|
@@ -348,7 +352,8 @@ class CronClient:
|
|
|
348
352
|
cron_id: The cron ID to update.
|
|
349
353
|
schedule: The cron schedule to execute this job on.
|
|
350
354
|
Schedules are interpreted in UTC unless a timezone is specified.
|
|
351
|
-
end_time: The end date to stop running the cron.
|
|
355
|
+
end_time: The end date to stop running the cron. Pass ``None`` to
|
|
356
|
+
clear a previously set end time; omit to leave it unchanged.
|
|
352
357
|
input: The input to the graph.
|
|
353
358
|
metadata: Metadata to assign to the cron job runs.
|
|
354
359
|
config: The configuration for the assistant.
|
|
@@ -386,7 +391,6 @@ class CronClient:
|
|
|
386
391
|
"""
|
|
387
392
|
payload = {
|
|
388
393
|
"schedule": schedule,
|
|
389
|
-
"end_time": end_time.isoformat() if end_time else None,
|
|
390
394
|
"input": input,
|
|
391
395
|
"metadata": metadata,
|
|
392
396
|
"config": config,
|
|
@@ -403,6 +407,10 @@ class CronClient:
|
|
|
403
407
|
"durability": durability,
|
|
404
408
|
}
|
|
405
409
|
payload = {k: v for k, v in payload.items() if v is not None}
|
|
410
|
+
# An explicit end_time=None clears the end time; NOT_PROVIDED leaves it
|
|
411
|
+
# unchanged. Inject after the None-strip so the explicit null survives.
|
|
412
|
+
if end_time is not NOT_PROVIDED:
|
|
413
|
+
payload["end_time"] = end_time.isoformat() if end_time is not None else None
|
|
406
414
|
return await self.http.patch(
|
|
407
415
|
f"/runs/crons/{_quote_path_param(cron_id)}",
|
|
408
416
|
json=payload,
|
|
@@ -7,7 +7,11 @@ from collections.abc import Mapping, Sequence
|
|
|
7
7
|
from datetime import datetime, tzinfo
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from langgraph_sdk._shared.utilities import
|
|
10
|
+
from langgraph_sdk._shared.utilities import (
|
|
11
|
+
NOT_PROVIDED,
|
|
12
|
+
_quote_path_param,
|
|
13
|
+
_resolve_timezone,
|
|
14
|
+
)
|
|
11
15
|
from langgraph_sdk._sync.http import SyncHttpClient
|
|
12
16
|
from langgraph_sdk.schema import (
|
|
13
17
|
All,
|
|
@@ -313,7 +317,7 @@ class SyncCronClient:
|
|
|
313
317
|
cron_id: str,
|
|
314
318
|
*,
|
|
315
319
|
schedule: str | None = None,
|
|
316
|
-
end_time: datetime | None =
|
|
320
|
+
end_time: datetime | None = NOT_PROVIDED,
|
|
317
321
|
input: Input | None = None,
|
|
318
322
|
metadata: Mapping[str, Any] | None = None,
|
|
319
323
|
config: Config | None = None,
|
|
@@ -337,7 +341,8 @@ class SyncCronClient:
|
|
|
337
341
|
cron_id: The cron ID to update.
|
|
338
342
|
schedule: The cron schedule to execute this job on.
|
|
339
343
|
Schedules are interpreted in UTC unless a timezone is specified.
|
|
340
|
-
end_time: The end date to stop running the cron.
|
|
344
|
+
end_time: The end date to stop running the cron. Pass ``None`` to
|
|
345
|
+
clear a previously set end time; omit to leave it unchanged.
|
|
341
346
|
input: The input to the graph.
|
|
342
347
|
metadata: Metadata to assign to the cron job runs.
|
|
343
348
|
config: The configuration for the assistant.
|
|
@@ -375,7 +380,6 @@ class SyncCronClient:
|
|
|
375
380
|
"""
|
|
376
381
|
payload = {
|
|
377
382
|
"schedule": schedule,
|
|
378
|
-
"end_time": end_time.isoformat() if end_time else None,
|
|
379
383
|
"input": input,
|
|
380
384
|
"metadata": metadata,
|
|
381
385
|
"config": config,
|
|
@@ -392,6 +396,10 @@ class SyncCronClient:
|
|
|
392
396
|
"durability": durability,
|
|
393
397
|
}
|
|
394
398
|
payload = {k: v for k, v in payload.items() if v is not None}
|
|
399
|
+
# An explicit end_time=None clears the end time; NOT_PROVIDED leaves it
|
|
400
|
+
# unchanged. Inject after the None-strip so the explicit null survives.
|
|
401
|
+
if end_time is not NOT_PROVIDED:
|
|
402
|
+
payload["end_time"] = end_time.isoformat() if end_time is not None else None
|
|
395
403
|
return self.http.patch(
|
|
396
404
|
f"/runs/crons/{_quote_path_param(cron_id)}",
|
|
397
405
|
json=payload,
|
|
@@ -867,9 +867,8 @@ def _validate_handler(fn: Callable[..., typing.Any]) -> None:
|
|
|
867
867
|
def is_studio_user(
|
|
868
868
|
user: types.MinimalUser | types.BaseUser | types.MinimalUserDict,
|
|
869
869
|
) -> bool:
|
|
870
|
-
return (
|
|
871
|
-
isinstance(user,
|
|
872
|
-
or (isinstance(user, dict) and user.get("kind") == "StudioUser") # ty: ignore[invalid-argument-type]
|
|
870
|
+
return isinstance(user, types.StudioUser) or (
|
|
871
|
+
isinstance(user, dict) and user.get("kind") == "StudioUser"
|
|
873
872
|
)
|
|
874
873
|
|
|
875
874
|
|
|
@@ -18,6 +18,9 @@ import warnings
|
|
|
18
18
|
|
|
19
19
|
from langgraph_sdk.encryption import types
|
|
20
20
|
|
|
21
|
+
_BlobDecryptorT = typing.TypeVar("_BlobDecryptorT", bound=types.BlobDecryptor)
|
|
22
|
+
_JsonDecryptorT = typing.TypeVar("_JsonDecryptorT", bound=types.JsonDecryptor)
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
class LangGraphBetaWarning(UserWarning):
|
|
23
26
|
"""Warning for beta features in LangGraph SDK."""
|
|
@@ -141,7 +144,7 @@ class _DecryptDecorators:
|
|
|
141
144
|
def __init__(self, parent: Encryption):
|
|
142
145
|
self._parent = parent
|
|
143
146
|
|
|
144
|
-
def blob(self, fn:
|
|
147
|
+
def blob(self, fn: _BlobDecryptorT) -> _BlobDecryptorT:
|
|
145
148
|
"""Register a blob decryption handler.
|
|
146
149
|
|
|
147
150
|
The handler will be called to decrypt opaque data like checkpoint blobs.
|
|
@@ -149,7 +152,9 @@ class _DecryptDecorators:
|
|
|
149
152
|
Example:
|
|
150
153
|
```python
|
|
151
154
|
@encryption.decrypt.blob
|
|
152
|
-
async def decrypt_blob(
|
|
155
|
+
async def decrypt_blob(
|
|
156
|
+
ctx: EncryptionContext, blob: bytes
|
|
157
|
+
) -> bytes | DecryptResult[bytes]:
|
|
153
158
|
# Decrypt the blob using your encryption service
|
|
154
159
|
return decrypted_blob
|
|
155
160
|
```
|
|
@@ -170,13 +175,15 @@ class _DecryptDecorators:
|
|
|
170
175
|
self._parent._blob_decryptor = fn
|
|
171
176
|
return fn
|
|
172
177
|
|
|
173
|
-
def json(self, fn:
|
|
178
|
+
def json(self, fn: _JsonDecryptorT) -> _JsonDecryptorT:
|
|
174
179
|
"""Register the JSON decryption handler.
|
|
175
180
|
|
|
176
181
|
Example:
|
|
177
182
|
```python
|
|
178
183
|
@encryption.decrypt.json
|
|
179
|
-
async def decrypt_json(
|
|
184
|
+
async def decrypt_json(
|
|
185
|
+
ctx: EncryptionContext, data: dict
|
|
186
|
+
) -> dict | DecryptResult[dict]:
|
|
180
187
|
# Decrypt the data
|
|
181
188
|
return decrypt_data(data)
|
|
182
189
|
```
|
|
@@ -369,7 +376,7 @@ class Encryption:
|
|
|
369
376
|
"""Reference to encryption type definitions.
|
|
370
377
|
|
|
371
378
|
Provides access to all type definitions used in the encryption system,
|
|
372
|
-
including EncryptionContext, BlobEncryptor, BlobDecryptor,
|
|
379
|
+
including EncryptionContext, DecryptResult, BlobEncryptor, BlobDecryptor,
|
|
373
380
|
JsonEncryptor, and JsonDecryptor.
|
|
374
381
|
"""
|
|
375
382
|
|
|
@@ -9,10 +9,30 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import typing
|
|
11
11
|
from collections.abc import Awaitable, Callable
|
|
12
|
+
from dataclasses import dataclass
|
|
12
13
|
|
|
13
14
|
Json = dict[str, typing.Any]
|
|
14
15
|
"""JSON-serializable dictionary type for structured data encryption."""
|
|
15
16
|
|
|
17
|
+
T = typing.TypeVar("T")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class DecryptResult(typing.Generic[T]):
|
|
22
|
+
"""Decrypted data and optional replacement ciphertext.
|
|
23
|
+
|
|
24
|
+
Return this from a decrypt handler when encrypted data should be replaced,
|
|
25
|
+
such as after rotating its encryption key. Returning plaintext directly
|
|
26
|
+
remains supported when no replacement is needed.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
plaintext: Decrypted data returned to the caller
|
|
30
|
+
replacement: New encrypted data to persist in place of the input
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
plaintext: T
|
|
34
|
+
replacement: T | None = None
|
|
35
|
+
|
|
16
36
|
|
|
17
37
|
class EncryptionContext:
|
|
18
38
|
"""Context passed to encryption/decryption handlers.
|
|
@@ -57,7 +77,9 @@ Returns:
|
|
|
57
77
|
Awaitable that resolves to encrypted bytes
|
|
58
78
|
"""
|
|
59
79
|
|
|
60
|
-
BlobDecryptor = Callable[
|
|
80
|
+
BlobDecryptor = Callable[
|
|
81
|
+
[EncryptionContext, bytes], Awaitable[bytes | DecryptResult[bytes]]
|
|
82
|
+
]
|
|
61
83
|
"""Handler for decrypting opaque blob data like checkpoints.
|
|
62
84
|
|
|
63
85
|
Note: Must be an async function. Decryption typically involves I/O operations
|
|
@@ -68,7 +90,8 @@ Args:
|
|
|
68
90
|
blob: The encrypted bytes to decrypt
|
|
69
91
|
|
|
70
92
|
Returns:
|
|
71
|
-
Awaitable that resolves to decrypted bytes
|
|
93
|
+
Awaitable that resolves to decrypted bytes, or a DecryptResult containing
|
|
94
|
+
decrypted bytes and replacement ciphertext
|
|
72
95
|
"""
|
|
73
96
|
|
|
74
97
|
JsonEncryptor = Callable[[EncryptionContext, Json], Awaitable[Json]]
|
|
@@ -101,7 +124,9 @@ Returns:
|
|
|
101
124
|
Awaitable that resolves to encrypted JSON dictionary
|
|
102
125
|
"""
|
|
103
126
|
|
|
104
|
-
JsonDecryptor = Callable[
|
|
127
|
+
JsonDecryptor = Callable[
|
|
128
|
+
[EncryptionContext, Json], Awaitable[Json | DecryptResult[Json]]
|
|
129
|
+
]
|
|
105
130
|
"""Handler for decrypting structured JSON data.
|
|
106
131
|
|
|
107
132
|
Note: Must be an async function. Decryption typically involves I/O operations
|
|
@@ -115,7 +140,8 @@ Args:
|
|
|
115
140
|
data: The encrypted JSON dictionary
|
|
116
141
|
|
|
117
142
|
Returns:
|
|
118
|
-
Awaitable that resolves to decrypted JSON dictionary
|
|
143
|
+
Awaitable that resolves to a decrypted JSON dictionary, or a DecryptResult
|
|
144
|
+
containing decrypted JSON and replacement ciphertext
|
|
119
145
|
"""
|
|
120
146
|
|
|
121
147
|
if typing.TYPE_CHECKING:
|
|
@@ -963,7 +963,7 @@ class _BaseModelLike(Protocol):
|
|
|
963
963
|
) -> dict[str, Any]: ...
|
|
964
964
|
|
|
965
965
|
|
|
966
|
-
_JSONLike: TypeAlias =
|
|
966
|
+
_JSONLike: TypeAlias = str | int | float | bool | None
|
|
967
967
|
_JSONMap: TypeAlias = Mapping[
|
|
968
968
|
str, Union[_JSONLike, list[_JSONLike], "_JSONMap", list["_JSONMap"]]
|
|
969
969
|
]
|