langgraph-oracledb 1.0.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.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: langgraph-oracledb
3
+ Version: 1.0.0
4
+ Summary: An integration package connecting Oracle Database and LangGraph
5
+ License-Expression: UPL-1.0
6
+ Requires-Python: >=3.10,<4.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: langgraph-checkpoint (>=2.1.2,<5.0.0)
14
+ Requires-Dist: oracledb (>=2.2.0)
15
+ Requires-Dist: orjson (>=3.11.5)
16
+ Project-URL: Repository, https://github.com/oracle/langchain-oracle
17
+ Project-URL: Source Code, https://github.com/oracle/langchain-oracle/tree/main/libs/langgraph-oracledb
18
+ Description-Content-Type: text/markdown
19
+
20
+ # LangGraph Oracle Persistence (Checkpoint + Store)
21
+
22
+ Oracle-backed implementations for:
23
+ - Checkpoints: OracleSaver (sync) and AsyncOracleSaver (async)
24
+ - Key/Value Store with optional vector search: OracleStore (sync) and AsyncOracleStore (async)
25
+
26
+ Supports:
27
+ - Oracle Database for AI Vector Search
28
+ - Python 3.10+ and `oracledb` driver
29
+
30
+ ## Quickstart
31
+
32
+ ### Checkpoints (Async)
33
+
34
+ ```python
35
+ import os
36
+ import asyncio
37
+ from dotenv import load_dotenv
38
+ from langgraph_oracledb.checkpoint.oracle import AsyncOracleSaver
39
+
40
+ load_dotenv()
41
+
42
+ async def main():
43
+ conn_string = f"{os.environ['ORACLE_USERNAME']}/{os.environ['ORACLE_PASSWORD']}@{os.environ['ORACLE_DSN']}"
44
+ async with AsyncOracleSaver.from_conn_string(conn_string) as checkpointer:
45
+ await checkpointer.setup() # Create tables & apply migrations once
46
+
47
+ # Then pass to your graph compile (example)
48
+ # graph = app.compile(checkpointer=checkpointer)
49
+ # await graph.ainvoke(...)
50
+
51
+ if __name__ == "__main__":
52
+ asyncio.run(main())
53
+ ```
54
+
55
+ Sync variant:
56
+
57
+ ```python
58
+ from langgraph_oracledb.checkpoint.oracle import OracleSaver
59
+
60
+ conn_string = "user/password@localhost:1521/FREEPDB1"
61
+ with OracleSaver.from_conn_string(conn_string) as checkpointer:
62
+ checkpointer.setup()
63
+ # graph = app.compile(checkpointer=checkpointer)
64
+ # graph.invoke(...)
65
+ ```
66
+
67
+ ### Store (Async, basic key/value)
68
+
69
+ ```python
70
+ import asyncio
71
+ from langgraph_oracledb.store.oracle import AsyncOracleStore
72
+
73
+ async def main():
74
+ conn_string = "user/password@localhost:1521/FREEPDB1"
75
+ async with AsyncOracleStore.from_conn_string(conn_string) as store:
76
+ await store.setup() # Create tables & apply migrations once
77
+
78
+ ns = ("readme", "example")
79
+ await store.aput(ns, "doc1", {"text": "hello"})
80
+ item = await store.aget(ns, "doc1")
81
+ print(item.value) # {"text": "hello"}
82
+
83
+ # Non-vector search (lists items by namespace)
84
+ results = await store.asearch(ns, limit=10)
85
+ print(len(results) >= 1)
86
+
87
+ if __name__ == "__main__":
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ Sync variant:
92
+
93
+ ```python
94
+ from langgraph_oracledb.store.oracle import OracleStore
95
+
96
+ conn_string = "user/password@localhost:1521/FREEPDB1"
97
+ with OracleStore.from_conn_string(conn_string) as store:
98
+ store.setup()
99
+ ns = ("readme", "example")
100
+ store.put(ns, "doc1", {"text": "hello"})
101
+ item = store.get(ns, "doc1")
102
+ print(item.value)
103
+ results = store.search(ns, limit=10)
104
+ ```
105
+
106
+ ### Vector Search (optional)
107
+
108
+ Vector search is enabled by passing an `index` configuration with:
109
+ - `dims`: embedding dimension
110
+ - `embed`: a LangChain Embeddings implementation (e.g., OpenAI, HF, or your own)
111
+ - optional `fields`: which JSON fields to embed (default: whole document)
112
+ - optional `index_type`: HNSW/IVF and parameters
113
+
114
+ Example (async):
115
+
116
+ ```python
117
+ # Pseudo-embeddings for illustration; use any LangChain Embeddings implementation
118
+ from langchain_core.embeddings import Embeddings
119
+ from langgraph_oracledb.store.oracle import AsyncOracleStore
120
+
121
+ class FakeEmbeddings(Embeddings):
122
+ def embed_documents(self, texts): return [[0.0]*8 for _ in texts]
123
+ def embed_query(self, text): return [0.0]*8
124
+
125
+ async with AsyncOracleStore.from_conn_string(
126
+ "user/password@localhost:1521/FREEPDB1",
127
+ index={
128
+ "dims": 8,
129
+ "embed": FakeEmbeddings(),
130
+ "fields": ["text"], # embed only the 'text' field
131
+ "index_type": {"type": "hnsw", "neighbors": 16, "efconstruction": 200, "distance_metric": "COSINE"},
132
+ },
133
+ ) as store:
134
+ await store.setup()
135
+ ns = ("docs",)
136
+ await store.aput(ns, "a", {"text": "alpha"})
137
+ await store.aput(ns, "b", {"text": "beta"})
138
+ results = await store.asearch(ns, query="alphabet", limit=2)
139
+ ```
140
+
141
+ Notes:
142
+ - Call `setup()` once per database/schema to create/upgrade tables.
143
+ - Vector search requires Oracle 23c/23ai+ with AI Vector Search enabled.
144
+
145
+ ## Testing the Examples
146
+
147
+ The repository's tests will skip automatically if an Oracle instance is not reachable.
148
+ - Place your Oracle credentials in `.env` (see Configuration)
149
+ - Run: `pytest -q`
150
+
151
+ This repository includes tests that validate the examples above:
152
+ - Async checkpoint setup works
153
+ - Async store put/get/search works
154
+
@@ -0,0 +1,134 @@
1
+ # LangGraph Oracle Persistence (Checkpoint + Store)
2
+
3
+ Oracle-backed implementations for:
4
+ - Checkpoints: OracleSaver (sync) and AsyncOracleSaver (async)
5
+ - Key/Value Store with optional vector search: OracleStore (sync) and AsyncOracleStore (async)
6
+
7
+ Supports:
8
+ - Oracle Database for AI Vector Search
9
+ - Python 3.10+ and `oracledb` driver
10
+
11
+ ## Quickstart
12
+
13
+ ### Checkpoints (Async)
14
+
15
+ ```python
16
+ import os
17
+ import asyncio
18
+ from dotenv import load_dotenv
19
+ from langgraph_oracledb.checkpoint.oracle import AsyncOracleSaver
20
+
21
+ load_dotenv()
22
+
23
+ async def main():
24
+ conn_string = f"{os.environ['ORACLE_USERNAME']}/{os.environ['ORACLE_PASSWORD']}@{os.environ['ORACLE_DSN']}"
25
+ async with AsyncOracleSaver.from_conn_string(conn_string) as checkpointer:
26
+ await checkpointer.setup() # Create tables & apply migrations once
27
+
28
+ # Then pass to your graph compile (example)
29
+ # graph = app.compile(checkpointer=checkpointer)
30
+ # await graph.ainvoke(...)
31
+
32
+ if __name__ == "__main__":
33
+ asyncio.run(main())
34
+ ```
35
+
36
+ Sync variant:
37
+
38
+ ```python
39
+ from langgraph_oracledb.checkpoint.oracle import OracleSaver
40
+
41
+ conn_string = "user/password@localhost:1521/FREEPDB1"
42
+ with OracleSaver.from_conn_string(conn_string) as checkpointer:
43
+ checkpointer.setup()
44
+ # graph = app.compile(checkpointer=checkpointer)
45
+ # graph.invoke(...)
46
+ ```
47
+
48
+ ### Store (Async, basic key/value)
49
+
50
+ ```python
51
+ import asyncio
52
+ from langgraph_oracledb.store.oracle import AsyncOracleStore
53
+
54
+ async def main():
55
+ conn_string = "user/password@localhost:1521/FREEPDB1"
56
+ async with AsyncOracleStore.from_conn_string(conn_string) as store:
57
+ await store.setup() # Create tables & apply migrations once
58
+
59
+ ns = ("readme", "example")
60
+ await store.aput(ns, "doc1", {"text": "hello"})
61
+ item = await store.aget(ns, "doc1")
62
+ print(item.value) # {"text": "hello"}
63
+
64
+ # Non-vector search (lists items by namespace)
65
+ results = await store.asearch(ns, limit=10)
66
+ print(len(results) >= 1)
67
+
68
+ if __name__ == "__main__":
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ Sync variant:
73
+
74
+ ```python
75
+ from langgraph_oracledb.store.oracle import OracleStore
76
+
77
+ conn_string = "user/password@localhost:1521/FREEPDB1"
78
+ with OracleStore.from_conn_string(conn_string) as store:
79
+ store.setup()
80
+ ns = ("readme", "example")
81
+ store.put(ns, "doc1", {"text": "hello"})
82
+ item = store.get(ns, "doc1")
83
+ print(item.value)
84
+ results = store.search(ns, limit=10)
85
+ ```
86
+
87
+ ### Vector Search (optional)
88
+
89
+ Vector search is enabled by passing an `index` configuration with:
90
+ - `dims`: embedding dimension
91
+ - `embed`: a LangChain Embeddings implementation (e.g., OpenAI, HF, or your own)
92
+ - optional `fields`: which JSON fields to embed (default: whole document)
93
+ - optional `index_type`: HNSW/IVF and parameters
94
+
95
+ Example (async):
96
+
97
+ ```python
98
+ # Pseudo-embeddings for illustration; use any LangChain Embeddings implementation
99
+ from langchain_core.embeddings import Embeddings
100
+ from langgraph_oracledb.store.oracle import AsyncOracleStore
101
+
102
+ class FakeEmbeddings(Embeddings):
103
+ def embed_documents(self, texts): return [[0.0]*8 for _ in texts]
104
+ def embed_query(self, text): return [0.0]*8
105
+
106
+ async with AsyncOracleStore.from_conn_string(
107
+ "user/password@localhost:1521/FREEPDB1",
108
+ index={
109
+ "dims": 8,
110
+ "embed": FakeEmbeddings(),
111
+ "fields": ["text"], # embed only the 'text' field
112
+ "index_type": {"type": "hnsw", "neighbors": 16, "efconstruction": 200, "distance_metric": "COSINE"},
113
+ },
114
+ ) as store:
115
+ await store.setup()
116
+ ns = ("docs",)
117
+ await store.aput(ns, "a", {"text": "alpha"})
118
+ await store.aput(ns, "b", {"text": "beta"})
119
+ results = await store.asearch(ns, query="alphabet", limit=2)
120
+ ```
121
+
122
+ Notes:
123
+ - Call `setup()` once per database/schema to create/upgrade tables.
124
+ - Vector search requires Oracle 23c/23ai+ with AI Vector Search enabled.
125
+
126
+ ## Testing the Examples
127
+
128
+ The repository's tests will skip automatically if an Oracle instance is not reachable.
129
+ - Place your Oracle credentials in `.env` (see Configuration)
130
+ - Run: `pytest -q`
131
+
132
+ This repository includes tests that validate the examples above:
133
+ - Async checkpoint setup works
134
+ - Async store put/get/search works
@@ -0,0 +1,6 @@
1
+ """Oracle checkpoint implementation for LangGraph."""
2
+
3
+ from langgraph_oracledb.checkpoint.oracle.aio import AsyncOracleSaver
4
+ from langgraph_oracledb.checkpoint.oracle.sync import OracleSaver
5
+
6
+ __all__ = ["AsyncOracleSaver", "OracleSaver"]
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator
4
+ from contextlib import asynccontextmanager
5
+ from typing import Union
6
+
7
+ import oracledb
8
+ from oracledb import AsyncConnection, AsyncConnectionPool
9
+
10
+ # Type alias for connection types
11
+ Conn = Union[oracledb.AsyncConnection, AsyncConnectionPool]
12
+
13
+
14
+ @asynccontextmanager
15
+ async def get_connection(conn: Conn) -> AsyncIterator[oracledb.AsyncConnection]:
16
+ """Get a connection from either a single connection or connection pool."""
17
+
18
+ if isinstance(conn, AsyncConnection):
19
+ yield conn
20
+ elif isinstance(conn, AsyncConnectionPool):
21
+ async with conn.acquire() as connection:
22
+ yield connection
23
+ else:
24
+ raise TypeError(f"Invalid connection type: {type(conn)}")
@@ -0,0 +1,21 @@
1
+ """Shared utility functions for the Oracle checkpoint & storage classes."""
2
+
3
+ from collections.abc import Iterator
4
+ from contextlib import contextmanager
5
+ from typing import Union
6
+
7
+ from oracledb import Connection, ConnectionPool
8
+
9
+ Conn = Union[Connection, ConnectionPool]
10
+
11
+
12
+ @contextmanager
13
+ def get_connection(conn: Conn) -> Iterator[Connection]:
14
+ """Get a connection from either a single connection or connection pool."""
15
+ if isinstance(conn, Connection):
16
+ yield conn
17
+ elif isinstance(conn, ConnectionPool):
18
+ with conn.acquire() as connection:
19
+ yield connection
20
+ else:
21
+ raise TypeError(f"Invalid connection type: {type(conn)}")