langgraph-checkpoint-dmdb 0.1.0__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_checkpoint_dmdb-0.1.0/.gitignore +142 -0
- langgraph_checkpoint_dmdb-0.1.0/LICENSE +21 -0
- langgraph_checkpoint_dmdb-0.1.0/Makefile +27 -0
- langgraph_checkpoint_dmdb-0.1.0/PKG-INFO +192 -0
- langgraph_checkpoint_dmdb-0.1.0/README.md +178 -0
- langgraph_checkpoint_dmdb-0.1.0/docs/dmdb-compatibility-plan.md +1383 -0
- langgraph_checkpoint_dmdb-0.1.0/docs/mysql-feature-parity.md +69 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/__init__.py +12 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/_internal.py +173 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/aio.py +182 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/base.py +461 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/dmpython.py +796 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/py.typed +0 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/shallow.py +171 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/__init__.py +6 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/aio.py +103 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/base.py +246 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/dmpython.py +380 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/py.typed +0 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/__init__.py +0 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/agents.py +43 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/any_int.py +6 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/any_str.py +92 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest.py +66 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest_checkpointer.py +30 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest_store.py +30 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/dmdb_fakes.py +18 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/fake_tracer.py +91 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/messages.py +50 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_checkpoint_migration.py +1733 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_interruption.py +92 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_large_cases.py +6512 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_large_cases_async.py +3475 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_pregel.py +5439 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_pregel_async.py +6894 -0
- langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_react_agent.py +168 -0
- langgraph_checkpoint_dmdb-0.1.0/pyproject.toml +77 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/__init__.py +0 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/conftest.py +8 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/fakes.py +491 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_async.py +84 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_async_store.py +46 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_graph_integration.py +113 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_integration.py +313 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_internal.py +149 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_shallow.py +91 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_store.py +163 -0
- langgraph_checkpoint_dmdb-0.1.0/tests/test_sync.py +435 -0
- langgraph_checkpoint_dmdb-0.1.0/uv.lock +2439 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
98
|
+
__pypackages__/
|
|
99
|
+
|
|
100
|
+
# Celery stuff
|
|
101
|
+
celerybeat-schedule
|
|
102
|
+
celerybeat.pid
|
|
103
|
+
|
|
104
|
+
# SageMath parsed files
|
|
105
|
+
*.sage.py
|
|
106
|
+
|
|
107
|
+
# Environments
|
|
108
|
+
.env
|
|
109
|
+
.venv
|
|
110
|
+
env/
|
|
111
|
+
venv/
|
|
112
|
+
ENV/
|
|
113
|
+
env.bak/
|
|
114
|
+
venv.bak/
|
|
115
|
+
|
|
116
|
+
# Spyder project settings
|
|
117
|
+
.spyderproject
|
|
118
|
+
.spyproject
|
|
119
|
+
|
|
120
|
+
# Rope project settings
|
|
121
|
+
.ropeproject
|
|
122
|
+
|
|
123
|
+
# mkdocs documentation
|
|
124
|
+
/site
|
|
125
|
+
|
|
126
|
+
# mypy
|
|
127
|
+
.mypy_cache/
|
|
128
|
+
.dmypy.json
|
|
129
|
+
dmypy.json
|
|
130
|
+
|
|
131
|
+
# Pyre type checker
|
|
132
|
+
.pyre/
|
|
133
|
+
|
|
134
|
+
# pytype static type analyzer
|
|
135
|
+
.pytype/
|
|
136
|
+
|
|
137
|
+
# Cython debug symbols
|
|
138
|
+
cython_debug/
|
|
139
|
+
|
|
140
|
+
#owner
|
|
141
|
+
.idea/
|
|
142
|
+
*.DS_Store
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Theodore Ni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.PHONY: test test-integration test-watch lint format build
|
|
2
|
+
|
|
3
|
+
TEST ?= .
|
|
4
|
+
PYTHON_FILES ?= .
|
|
5
|
+
MYPY_CACHE ?= .mypy_cache
|
|
6
|
+
|
|
7
|
+
test:
|
|
8
|
+
uv run pytest --ignore=langgraph-tests -m "not integration" $(TEST)
|
|
9
|
+
cd langgraph-tests && PYTHONPATH=.. uv run --project .. pytest tests
|
|
10
|
+
|
|
11
|
+
test-integration:
|
|
12
|
+
uv run pytest --ignore=langgraph-tests -m integration $(TEST)
|
|
13
|
+
|
|
14
|
+
test-watch:
|
|
15
|
+
uv run ptw $(TEST)
|
|
16
|
+
|
|
17
|
+
lint:
|
|
18
|
+
uv run ruff check $(PYTHON_FILES)
|
|
19
|
+
uv run ruff format $(PYTHON_FILES) --check
|
|
20
|
+
uv run mypy langgraph --cache-dir $(MYPY_CACHE)
|
|
21
|
+
|
|
22
|
+
format:
|
|
23
|
+
uv run ruff format $(PYTHON_FILES)
|
|
24
|
+
uv run ruff check --select I --fix $(PYTHON_FILES)
|
|
25
|
+
|
|
26
|
+
build:
|
|
27
|
+
uv build
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langgraph-checkpoint-dmdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DM Database checkpoint and Store persistence for LangGraph.
|
|
5
|
+
Project-URL: Repository, https://gitee.com/maxbanana/langgraph-checkpoint-dmdb
|
|
6
|
+
Author-email: Theodore Ni <dev@ted.bio>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: dmpython==2.5.32
|
|
11
|
+
Requires-Dist: langgraph-checkpoint<2.2,>=2.1.1
|
|
12
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# LangGraph Checkpoint DMDB
|
|
16
|
+
|
|
17
|
+
[LangGraph](https://github.com/langchain-ai/langgraph) checkpoint and Store persistence for DM Database, using `dmPython`.
|
|
18
|
+
|
|
19
|
+
## Compatibility
|
|
20
|
+
|
|
21
|
+
- `langgraph-checkpoint>=2.1.1,<2.2`
|
|
22
|
+
- `dmpython==2.5.32`
|
|
23
|
+
- Python 3.9+
|
|
24
|
+
|
|
25
|
+
The package provides:
|
|
26
|
+
|
|
27
|
+
- `DMDBSaver`: complete checkpoint history and time travel.
|
|
28
|
+
- `ShallowDMDBSaver`: latest-checkpoint-only persistence.
|
|
29
|
+
- `AsyncDMDBSaver` and `AsyncShallowDMDBSaver`: async graph APIs backed by isolated worker-thread connections.
|
|
30
|
+
- `DMDBStore` and `AsyncDMDBStore`: LangGraph Store CRUD, batch, JSON filtering, pagination, and namespace listing.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install langgraph-checkpoint-dmdb
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
For a long-lived compiled graph:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from langgraph.checkpoint.dmdb import DMDBSaver
|
|
44
|
+
|
|
45
|
+
saver = DMDBSaver.from_conn_string(
|
|
46
|
+
"dmdb://sysdba:password@localhost:5236/NLP_KNOWLEDGEBASE"
|
|
47
|
+
)
|
|
48
|
+
saver.setup()
|
|
49
|
+
graph = builder.compile(checkpointer=saver)
|
|
50
|
+
|
|
51
|
+
config = {"configurable": {"thread_id": "conversation-1"}}
|
|
52
|
+
for event in graph.stream(inputs, config=config):
|
|
53
|
+
...
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Applications that already have structured database configuration should avoid constructing a URI:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
saver = DMDBSaver.from_conn_params(
|
|
60
|
+
host=db.host,
|
|
61
|
+
port=int(db.port),
|
|
62
|
+
user=db.username,
|
|
63
|
+
password=db.password,
|
|
64
|
+
schema=db.database,
|
|
65
|
+
)
|
|
66
|
+
saver.setup()
|
|
67
|
+
graph = builder.compile(checkpointer=saver)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Short-lived scripts may use the Saver as a context manager:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
with DMDBSaver.from_conn_string(DMDB_URI) as saver:
|
|
74
|
+
saver.setup()
|
|
75
|
+
graph = builder.compile(checkpointer=saver)
|
|
76
|
+
result = graph.invoke(inputs, config=config)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`from_conn_string()` and `from_conn_params()` create a connection factory. Each checkpoint operation owns a new dmPython connection and closes it afterward, so a Saver can be shared by concurrent synchronous graph executions without sharing a connection.
|
|
80
|
+
|
|
81
|
+
Do not return a graph from inside the `with` block and use it after the block exits; the Saver is closed on context exit.
|
|
82
|
+
|
|
83
|
+
## Async Checkpointing
|
|
84
|
+
|
|
85
|
+
dmPython has no native async API. The async savers run each database operation with `asyncio.to_thread()` and obtain a fresh connection from the factory, so connections are never shared between worker threads:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from langgraph.checkpoint.dmdb import AsyncDMDBSaver
|
|
89
|
+
|
|
90
|
+
async with AsyncDMDBSaver.from_conn_params(
|
|
91
|
+
host=db.host,
|
|
92
|
+
port=int(db.port),
|
|
93
|
+
user=db.username,
|
|
94
|
+
password=db.password,
|
|
95
|
+
schema=db.database,
|
|
96
|
+
) as saver:
|
|
97
|
+
await saver.setup()
|
|
98
|
+
graph = builder.compile(checkpointer=saver)
|
|
99
|
+
result = await graph.ainvoke(inputs, config=config)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Async savers require a connection factory. Passing one shared dmPython connection is rejected because `dmPython.threadsafety == 1`.
|
|
103
|
+
|
|
104
|
+
## Shallow Saver
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from langgraph.checkpoint.dmdb import ShallowDMDBSaver
|
|
108
|
+
|
|
109
|
+
saver = ShallowDMDBSaver.from_conn_string(DMDB_URI)
|
|
110
|
+
saver.setup()
|
|
111
|
+
graph = builder.compile(checkpointer=saver)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The shallow saver retains only the latest checkpoint per thread and namespace. Do not use shallow and full savers for the same thread/namespace because shallow writes intentionally prune that history.
|
|
115
|
+
|
|
116
|
+
## LangGraph Store
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from langgraph.store.dmdb import DMDBStore
|
|
120
|
+
|
|
121
|
+
store = DMDBStore.from_conn_params(
|
|
122
|
+
host=db.host,
|
|
123
|
+
port=int(db.port),
|
|
124
|
+
user=db.username,
|
|
125
|
+
password=db.password,
|
|
126
|
+
schema=db.database,
|
|
127
|
+
)
|
|
128
|
+
store.setup()
|
|
129
|
+
store.put(("users", user_id), "preferences", {"language": "zh-CN"})
|
|
130
|
+
items = store.search(("users", user_id), filter={"language": "zh-CN"})
|
|
131
|
+
graph = builder.compile(checkpointer=saver, store=store)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`AsyncDMDBStore` exposes the corresponding `aget`, `aput`, `adelete`, `asearch`, `alist_namespaces`, and `abatch` APIs.
|
|
135
|
+
|
|
136
|
+
## Connection URI
|
|
137
|
+
|
|
138
|
+
Supported schemes are `dmdb://` and `dm://`:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
dmdb://user:password@host:5236/schema
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The path is treated as the DM schema. The Saver validates it and executes `SET SCHEMA` after connecting. Usernames and passwords containing URI-special characters must be percent encoded. `from_conn_params()` does not require URI encoding.
|
|
145
|
+
|
|
146
|
+
If dmPython raises `DatabaseError: [CODE:-70089] Encryption module failed to load` in Linux, add the wheel's bundled `dmssl` directory to `LD_LIBRARY_PATH` before starting Python. The exact site-packages path depends on the runtime image; see the dmPython 2.5.32 package metadata for its supported SSL setup.
|
|
147
|
+
|
|
148
|
+
## Transactions and concurrency
|
|
149
|
+
|
|
150
|
+
- dmPython connections use `autoCommit=False`.
|
|
151
|
+
- `put`, `put_writes`, and `delete_thread` commit atomically and roll back on error.
|
|
152
|
+
- Factory-backed Savers create an independent connection per operation.
|
|
153
|
+
- A directly supplied dmPython connection remains owned by the caller and is restricted to its creating thread.
|
|
154
|
+
- `setup()` is idempotent and must be called before first use.
|
|
155
|
+
- DM `MERGE` source parameters are explicitly cast to their target SQL types. BLOB/write batches execute row-by-row inside one transaction because dmPython/DM otherwise cache the first inferred bind length and can raise `-70005 String truncated` for later values.
|
|
156
|
+
|
|
157
|
+
## Driver Limitations
|
|
158
|
+
|
|
159
|
+
- Async APIs use worker threads rather than a native async DM protocol.
|
|
160
|
+
- Async classes cannot accept a single direct dmPython connection.
|
|
161
|
+
- Store JSON filtering and namespace matching run in Python to avoid DM-version-specific JSON functions.
|
|
162
|
+
- Store semantic `query` and vector indexing are not implemented; the MySQL 2.0.17 Store also did not implement semantic indexing.
|
|
163
|
+
|
|
164
|
+
## Tests
|
|
165
|
+
|
|
166
|
+
Unit and LangGraph integration tests do not require a DM instance:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
uv run pytest -m "not integration"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Real DM integration requires `dmPython` and these environment variables:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
DM_HOST
|
|
176
|
+
DM_PORT
|
|
177
|
+
DM_USER
|
|
178
|
+
DM_PASSWORD
|
|
179
|
+
DM_SCHEMA
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Run it with:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
uv run pytest -m integration
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Design
|
|
189
|
+
|
|
190
|
+
The implementation plan and release gates are documented in [docs/dmdb-compatibility-plan.md](docs/dmdb-compatibility-plan.md). The complete MySQL-to-DMDB feature mapping is in [docs/mysql-feature-parity.md](docs/mysql-feature-parity.md).
|
|
191
|
+
|
|
192
|
+
This project derives serialization behavior and tests from `langgraph-checkpoint-mysql` 2.0.17. The original MIT copyright notice is retained in [LICENSE](LICENSE).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# LangGraph Checkpoint DMDB
|
|
2
|
+
|
|
3
|
+
[LangGraph](https://github.com/langchain-ai/langgraph) checkpoint and Store persistence for DM Database, using `dmPython`.
|
|
4
|
+
|
|
5
|
+
## Compatibility
|
|
6
|
+
|
|
7
|
+
- `langgraph-checkpoint>=2.1.1,<2.2`
|
|
8
|
+
- `dmpython==2.5.32`
|
|
9
|
+
- Python 3.9+
|
|
10
|
+
|
|
11
|
+
The package provides:
|
|
12
|
+
|
|
13
|
+
- `DMDBSaver`: complete checkpoint history and time travel.
|
|
14
|
+
- `ShallowDMDBSaver`: latest-checkpoint-only persistence.
|
|
15
|
+
- `AsyncDMDBSaver` and `AsyncShallowDMDBSaver`: async graph APIs backed by isolated worker-thread connections.
|
|
16
|
+
- `DMDBStore` and `AsyncDMDBStore`: LangGraph Store CRUD, batch, JSON filtering, pagination, and namespace listing.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install langgraph-checkpoint-dmdb
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
For a long-lived compiled graph:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from langgraph.checkpoint.dmdb import DMDBSaver
|
|
30
|
+
|
|
31
|
+
saver = DMDBSaver.from_conn_string(
|
|
32
|
+
"dmdb://sysdba:password@localhost:5236/NLP_KNOWLEDGEBASE"
|
|
33
|
+
)
|
|
34
|
+
saver.setup()
|
|
35
|
+
graph = builder.compile(checkpointer=saver)
|
|
36
|
+
|
|
37
|
+
config = {"configurable": {"thread_id": "conversation-1"}}
|
|
38
|
+
for event in graph.stream(inputs, config=config):
|
|
39
|
+
...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Applications that already have structured database configuration should avoid constructing a URI:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
saver = DMDBSaver.from_conn_params(
|
|
46
|
+
host=db.host,
|
|
47
|
+
port=int(db.port),
|
|
48
|
+
user=db.username,
|
|
49
|
+
password=db.password,
|
|
50
|
+
schema=db.database,
|
|
51
|
+
)
|
|
52
|
+
saver.setup()
|
|
53
|
+
graph = builder.compile(checkpointer=saver)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Short-lived scripts may use the Saver as a context manager:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
with DMDBSaver.from_conn_string(DMDB_URI) as saver:
|
|
60
|
+
saver.setup()
|
|
61
|
+
graph = builder.compile(checkpointer=saver)
|
|
62
|
+
result = graph.invoke(inputs, config=config)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`from_conn_string()` and `from_conn_params()` create a connection factory. Each checkpoint operation owns a new dmPython connection and closes it afterward, so a Saver can be shared by concurrent synchronous graph executions without sharing a connection.
|
|
66
|
+
|
|
67
|
+
Do not return a graph from inside the `with` block and use it after the block exits; the Saver is closed on context exit.
|
|
68
|
+
|
|
69
|
+
## Async Checkpointing
|
|
70
|
+
|
|
71
|
+
dmPython has no native async API. The async savers run each database operation with `asyncio.to_thread()` and obtain a fresh connection from the factory, so connections are never shared between worker threads:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from langgraph.checkpoint.dmdb import AsyncDMDBSaver
|
|
75
|
+
|
|
76
|
+
async with AsyncDMDBSaver.from_conn_params(
|
|
77
|
+
host=db.host,
|
|
78
|
+
port=int(db.port),
|
|
79
|
+
user=db.username,
|
|
80
|
+
password=db.password,
|
|
81
|
+
schema=db.database,
|
|
82
|
+
) as saver:
|
|
83
|
+
await saver.setup()
|
|
84
|
+
graph = builder.compile(checkpointer=saver)
|
|
85
|
+
result = await graph.ainvoke(inputs, config=config)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Async savers require a connection factory. Passing one shared dmPython connection is rejected because `dmPython.threadsafety == 1`.
|
|
89
|
+
|
|
90
|
+
## Shallow Saver
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from langgraph.checkpoint.dmdb import ShallowDMDBSaver
|
|
94
|
+
|
|
95
|
+
saver = ShallowDMDBSaver.from_conn_string(DMDB_URI)
|
|
96
|
+
saver.setup()
|
|
97
|
+
graph = builder.compile(checkpointer=saver)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The shallow saver retains only the latest checkpoint per thread and namespace. Do not use shallow and full savers for the same thread/namespace because shallow writes intentionally prune that history.
|
|
101
|
+
|
|
102
|
+
## LangGraph Store
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from langgraph.store.dmdb import DMDBStore
|
|
106
|
+
|
|
107
|
+
store = DMDBStore.from_conn_params(
|
|
108
|
+
host=db.host,
|
|
109
|
+
port=int(db.port),
|
|
110
|
+
user=db.username,
|
|
111
|
+
password=db.password,
|
|
112
|
+
schema=db.database,
|
|
113
|
+
)
|
|
114
|
+
store.setup()
|
|
115
|
+
store.put(("users", user_id), "preferences", {"language": "zh-CN"})
|
|
116
|
+
items = store.search(("users", user_id), filter={"language": "zh-CN"})
|
|
117
|
+
graph = builder.compile(checkpointer=saver, store=store)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`AsyncDMDBStore` exposes the corresponding `aget`, `aput`, `adelete`, `asearch`, `alist_namespaces`, and `abatch` APIs.
|
|
121
|
+
|
|
122
|
+
## Connection URI
|
|
123
|
+
|
|
124
|
+
Supported schemes are `dmdb://` and `dm://`:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
dmdb://user:password@host:5236/schema
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The path is treated as the DM schema. The Saver validates it and executes `SET SCHEMA` after connecting. Usernames and passwords containing URI-special characters must be percent encoded. `from_conn_params()` does not require URI encoding.
|
|
131
|
+
|
|
132
|
+
If dmPython raises `DatabaseError: [CODE:-70089] Encryption module failed to load` in Linux, add the wheel's bundled `dmssl` directory to `LD_LIBRARY_PATH` before starting Python. The exact site-packages path depends on the runtime image; see the dmPython 2.5.32 package metadata for its supported SSL setup.
|
|
133
|
+
|
|
134
|
+
## Transactions and concurrency
|
|
135
|
+
|
|
136
|
+
- dmPython connections use `autoCommit=False`.
|
|
137
|
+
- `put`, `put_writes`, and `delete_thread` commit atomically and roll back on error.
|
|
138
|
+
- Factory-backed Savers create an independent connection per operation.
|
|
139
|
+
- A directly supplied dmPython connection remains owned by the caller and is restricted to its creating thread.
|
|
140
|
+
- `setup()` is idempotent and must be called before first use.
|
|
141
|
+
- DM `MERGE` source parameters are explicitly cast to their target SQL types. BLOB/write batches execute row-by-row inside one transaction because dmPython/DM otherwise cache the first inferred bind length and can raise `-70005 String truncated` for later values.
|
|
142
|
+
|
|
143
|
+
## Driver Limitations
|
|
144
|
+
|
|
145
|
+
- Async APIs use worker threads rather than a native async DM protocol.
|
|
146
|
+
- Async classes cannot accept a single direct dmPython connection.
|
|
147
|
+
- Store JSON filtering and namespace matching run in Python to avoid DM-version-specific JSON functions.
|
|
148
|
+
- Store semantic `query` and vector indexing are not implemented; the MySQL 2.0.17 Store also did not implement semantic indexing.
|
|
149
|
+
|
|
150
|
+
## Tests
|
|
151
|
+
|
|
152
|
+
Unit and LangGraph integration tests do not require a DM instance:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
uv run pytest -m "not integration"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Real DM integration requires `dmPython` and these environment variables:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
DM_HOST
|
|
162
|
+
DM_PORT
|
|
163
|
+
DM_USER
|
|
164
|
+
DM_PASSWORD
|
|
165
|
+
DM_SCHEMA
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Run it with:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
uv run pytest -m integration
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Design
|
|
175
|
+
|
|
176
|
+
The implementation plan and release gates are documented in [docs/dmdb-compatibility-plan.md](docs/dmdb-compatibility-plan.md). The complete MySQL-to-DMDB feature mapping is in [docs/mysql-feature-parity.md](docs/mysql-feature-parity.md).
|
|
177
|
+
|
|
178
|
+
This project derives serialization behavior and tests from `langgraph-checkpoint-mysql` 2.0.17. The original MIT copyright notice is retained in [LICENSE](LICENSE).
|