langgraph-checkpoint-postgres 2.0.7__tar.gz → 2.0.8__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 (13) hide show
  1. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/PKG-INFO +1 -1
  2. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/aio.py +1 -1
  3. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/base.py +11 -0
  4. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/aio.py +81 -16
  5. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/base.py +66 -18
  6. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/pyproject.toml +1 -1
  7. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/README.md +0 -0
  8. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/__init__.py +0 -0
  9. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/_ainternal.py +0 -0
  10. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/_internal.py +0 -0
  11. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/checkpoint/postgres/py.typed +0 -0
  12. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/__init__.py +0 -0
  13. {langgraph_checkpoint_postgres-2.0.7 → langgraph_checkpoint_postgres-2.0.8}/langgraph/store/postgres/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 2.0.7
3
+ Version: 2.0.8
4
4
  Summary: Library with a Postgres implementation of LangGraph checkpoint saver.
5
5
  Home-page: https://www.github.com/langchain-ai/langgraph
6
6
  License: MIT
@@ -54,7 +54,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
54
54
  pipeline: bool = False,
55
55
  serde: Optional[SerializerProtocol] = None,
56
56
  ) -> AsyncIterator["AsyncPostgresSaver"]:
57
- """Create a new PostgresSaver instance from a connection string.
57
+ """Create a new AsyncPostgresSaver instance from a connection string.
58
58
 
59
59
  Args:
60
60
  conn_string (str): The Postgres connection info string.
@@ -57,6 +57,17 @@ MIGRATIONS = [
57
57
  PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
58
58
  );""",
59
59
  "ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;",
60
+ """
61
+ """,
62
+ """
63
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id);
64
+ """,
65
+ """
66
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_blobs_thread_id_idx ON checkpoint_blobs(thread_id);
67
+ """,
68
+ """
69
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_writes_thread_id_idx ON checkpoint_writes(thread_id);
70
+ """,
60
71
  ]
61
72
 
62
73
  SELECT_SQL = f"""
@@ -6,7 +6,6 @@ from typing import Any, Callable, Optional, Union, cast
6
6
 
7
7
  import orjson
8
8
  from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
9
- from psycopg.errors import UndefinedTable
10
9
  from psycopg.rows import DictRow, dict_row
11
10
  from psycopg_pool import AsyncConnectionPool
12
11
 
@@ -37,6 +36,75 @@ logger = logging.getLogger(__name__)
37
36
 
38
37
 
39
38
  class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Conn]):
39
+ """Asynchronous Postgres-backed store with optional vector search using pgvector.
40
+
41
+ !!! example "Examples"
42
+ Basic setup and key-value storage:
43
+ ```python
44
+ from langgraph.store.postgres import AsyncPostgresStore
45
+
46
+ async with AsyncPostgresStore.from_conn_string(
47
+ "postgresql://user:pass@localhost:5432/dbname"
48
+ ) as store:
49
+ await store.setup()
50
+
51
+ # Store and retrieve data
52
+ await store.aput(("users", "123"), "prefs", {"theme": "dark"})
53
+ item = await store.aget(("users", "123"), "prefs")
54
+ ```
55
+
56
+ Vector search using LangChain embeddings:
57
+ ```python
58
+ from langchain.embeddings import init_embeddings
59
+ from langgraph.store.postgres import AsyncPostgresStore
60
+
61
+ async with AsyncPostgresStore.from_conn_string(
62
+ "postgresql://user:pass@localhost:5432/dbname",
63
+ index={
64
+ "dims": 1536,
65
+ "embed": init_embeddings("openai:text-embedding-3-small"),
66
+ "fields": ["text"] # specify which fields to embed. Default is the whole serialized value
67
+ }
68
+ ) as store:
69
+ await store.setup() # Do this once to run migrations
70
+
71
+ # Store documents
72
+ await store.aput(("docs",), "doc1", {"text": "Python tutorial"})
73
+ await store.aput(("docs",), "doc2", {"text": "TypeScript guide"})
74
+ # Don't index the following
75
+ await store.aput(("docs",), "doc3", {"text": "Other guide"}, index=False)
76
+
77
+ # Search by similarity
78
+ results = await store.asearch(("docs",), query="python programming")
79
+ ```
80
+
81
+ Using connection pooling for better performance:
82
+ ```python
83
+ from langgraph.store.postgres import AsyncPostgresStore, PoolConfig
84
+
85
+ async with AsyncPostgresStore.from_conn_string(
86
+ "postgresql://user:pass@localhost:5432/dbname",
87
+ pool_config=PoolConfig(
88
+ min_size=5,
89
+ max_size=20
90
+ )
91
+ ) as store:
92
+ await store.setup()
93
+ # Use store with connection pooling...
94
+ ```
95
+
96
+ Warning:
97
+ Make sure to:
98
+ 1. Call `setup()` before first use to create necessary tables and indexes
99
+ 2. Have the pgvector extension available to use vector search
100
+ 3. Use Python 3.10+ for async functionality
101
+
102
+ Note:
103
+ Semantic search is disabled by default. You can enable it by providing an `index` configuration
104
+ when creating the store. Without this configuration, all `index` arguments passed to
105
+ `put` or `aput`will have no effect.
106
+ """
107
+
40
108
  __slots__ = (
41
109
  "_deserializer",
42
110
  "pipe",
@@ -150,22 +218,19 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
150
218
  """
151
219
 
152
220
  async def _get_version(cur: AsyncCursor[DictRow], table: str) -> int:
153
- try:
154
- await cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
155
- row = await cur.fetchone()
156
- if row is None:
157
- version = -1
158
- else:
159
- version = row["v"]
160
- except UndefinedTable:
161
- version = -1
162
- await cur.execute(
163
- f"""
164
- CREATE TABLE IF NOT EXISTS {table} (
165
- v INTEGER PRIMARY KEY
166
- )
167
- """
221
+ await cur.execute(
222
+ f"""
223
+ CREATE TABLE IF NOT EXISTS {table} (
224
+ v INTEGER PRIMARY KEY
168
225
  )
226
+ """
227
+ )
228
+ await cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
229
+ row = cast(dict, await cur.fetchone())
230
+ if row is None:
231
+ version = -1
232
+ else:
233
+ version = row["v"]
169
234
  return version
170
235
 
171
236
  async with self._cursor() as cur:
@@ -21,7 +21,6 @@ from typing import (
21
21
 
22
22
  import orjson
23
23
  from psycopg import Capabilities, Connection, Cursor, Pipeline
24
- from psycopg.errors import UndefinedTable
25
24
  from psycopg.rows import DictRow, dict_row
26
25
  from psycopg.types.json import Jsonb
27
26
  from psycopg_pool import ConnectionPool
@@ -73,7 +72,7 @@ CREATE TABLE IF NOT EXISTS store (
73
72
  """,
74
73
  """
75
74
  -- For faster lookups by prefix
76
- CREATE INDEX IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops);
75
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops);
77
76
  """,
78
77
  ]
79
78
 
@@ -107,7 +106,7 @@ CREATE TABLE IF NOT EXISTS store_vectors (
107
106
  ),
108
107
  Migration(
109
108
  """
110
- CREATE INDEX IF NOT EXISTS store_vectors_embedding_idx ON store_vectors
109
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS store_vectors_embedding_idx ON store_vectors
111
110
  USING %(index_type)s (embedding %(ops)s)%(index_params)s;
112
111
  """,
113
112
  condition=lambda store: bool(
@@ -534,6 +533,58 @@ class BasePostgresStore(Generic[C]):
534
533
 
535
534
 
536
535
  class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
536
+ """Postgres-backed store with optional vector search using pgvector.
537
+
538
+ !!! example "Examples"
539
+ Basic setup and key-value storage:
540
+ ```python
541
+ from langgraph.store.postgres import PostgresStore
542
+
543
+ store = PostgresStore(
544
+ connection_string="postgresql://user:pass@localhost:5432/dbname"
545
+ )
546
+ store.setup()
547
+
548
+ # Store and retrieve data
549
+ store.put(("users", "123"), "prefs", {"theme": "dark"})
550
+ item = store.get(("users", "123"), "prefs")
551
+ ```
552
+
553
+ Vector search using LangChain embeddings:
554
+ ```python
555
+ from langchain.embeddings import init_embeddings
556
+ from langgraph.store.postgres import PostgresStore
557
+
558
+ store = PostgresStore(
559
+ connection_string="postgresql://user:pass@localhost:5432/dbname",
560
+ index={
561
+ "dims": 1536,
562
+ "embed": init_embeddings("openai:text-embedding-3-small"),
563
+ "fields": ["text"] # specify which fields to embed. Default is the whole serialized value
564
+ }
565
+ )
566
+ store.setup() # Do this once to run migrations
567
+
568
+ # Store documents
569
+ store.put(("docs",), "doc1", {"text": "Python tutorial"})
570
+ store.put(("docs",), "doc2", {"text": "TypeScript guide"})
571
+ store.put(("docs",), "doc2", {"text": "Other guide"}, index=False) # don't index
572
+
573
+ # Search by similarity
574
+ results = store.search(("docs",), query="python programming")
575
+ ```
576
+
577
+ Note:
578
+ Semantic search is disabled by default. You can enable it by providing an `index` configuration
579
+ when creating the store. Without this configuration, all `index` arguments passed to
580
+ `put` or `aput`will have no effect.
581
+
582
+ Warning:
583
+ Make sure to call `setup()` before first use to create necessary tables and indexes.
584
+ The pgvector extension must be available to use vector search.
585
+
586
+ """
587
+
537
588
  __slots__ = (
538
589
  "_deserializer",
539
590
  "pipe",
@@ -795,22 +846,19 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
795
846
  """
796
847
 
797
848
  def _get_version(cur: Cursor[dict[str, Any]], table: str) -> int:
798
- try:
799
- cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
800
- row = cast(dict, cur.fetchone())
801
- if row is None:
802
- version = -1
803
- else:
804
- version = row["v"]
805
- except UndefinedTable:
806
- version = -1
807
- cur.execute(
808
- f"""
809
- CREATE TABLE IF NOT EXISTS {table} (
810
- v INTEGER PRIMARY KEY
811
- )
812
- """
849
+ cur.execute(
850
+ f"""
851
+ CREATE TABLE IF NOT EXISTS {table} (
852
+ v INTEGER PRIMARY KEY
813
853
  )
854
+ """
855
+ )
856
+ cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1")
857
+ row = cast(dict, cur.fetchone())
858
+ if row is None:
859
+ version = -1
860
+ else:
861
+ version = row["v"]
814
862
  return version
815
863
 
816
864
  with self._cursor() as cur:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-postgres"
3
- version = "2.0.7"
3
+ version = "2.0.8"
4
4
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"