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.
Files changed (49) hide show
  1. langgraph_checkpoint_dmdb-0.1.0/.gitignore +142 -0
  2. langgraph_checkpoint_dmdb-0.1.0/LICENSE +21 -0
  3. langgraph_checkpoint_dmdb-0.1.0/Makefile +27 -0
  4. langgraph_checkpoint_dmdb-0.1.0/PKG-INFO +192 -0
  5. langgraph_checkpoint_dmdb-0.1.0/README.md +178 -0
  6. langgraph_checkpoint_dmdb-0.1.0/docs/dmdb-compatibility-plan.md +1383 -0
  7. langgraph_checkpoint_dmdb-0.1.0/docs/mysql-feature-parity.md +69 -0
  8. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/__init__.py +12 -0
  9. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/_internal.py +173 -0
  10. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/aio.py +182 -0
  11. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/base.py +461 -0
  12. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/dmpython.py +796 -0
  13. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/py.typed +0 -0
  14. langgraph_checkpoint_dmdb-0.1.0/langgraph/checkpoint/dmdb/shallow.py +171 -0
  15. langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/__init__.py +6 -0
  16. langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/aio.py +103 -0
  17. langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/base.py +246 -0
  18. langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/dmpython.py +380 -0
  19. langgraph_checkpoint_dmdb-0.1.0/langgraph/store/dmdb/py.typed +0 -0
  20. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/__init__.py +0 -0
  21. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/agents.py +43 -0
  22. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/any_int.py +6 -0
  23. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/any_str.py +92 -0
  24. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest.py +66 -0
  25. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest_checkpointer.py +30 -0
  26. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/conftest_store.py +30 -0
  27. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/dmdb_fakes.py +18 -0
  28. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/fake_tracer.py +91 -0
  29. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/messages.py +50 -0
  30. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_checkpoint_migration.py +1733 -0
  31. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_interruption.py +92 -0
  32. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_large_cases.py +6512 -0
  33. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_large_cases_async.py +3475 -0
  34. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_pregel.py +5439 -0
  35. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_pregel_async.py +6894 -0
  36. langgraph_checkpoint_dmdb-0.1.0/langgraph-tests/tests/test_react_agent.py +168 -0
  37. langgraph_checkpoint_dmdb-0.1.0/pyproject.toml +77 -0
  38. langgraph_checkpoint_dmdb-0.1.0/tests/__init__.py +0 -0
  39. langgraph_checkpoint_dmdb-0.1.0/tests/conftest.py +8 -0
  40. langgraph_checkpoint_dmdb-0.1.0/tests/fakes.py +491 -0
  41. langgraph_checkpoint_dmdb-0.1.0/tests/test_async.py +84 -0
  42. langgraph_checkpoint_dmdb-0.1.0/tests/test_async_store.py +46 -0
  43. langgraph_checkpoint_dmdb-0.1.0/tests/test_graph_integration.py +113 -0
  44. langgraph_checkpoint_dmdb-0.1.0/tests/test_integration.py +313 -0
  45. langgraph_checkpoint_dmdb-0.1.0/tests/test_internal.py +149 -0
  46. langgraph_checkpoint_dmdb-0.1.0/tests/test_shallow.py +91 -0
  47. langgraph_checkpoint_dmdb-0.1.0/tests/test_store.py +163 -0
  48. langgraph_checkpoint_dmdb-0.1.0/tests/test_sync.py +435 -0
  49. 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).