langgraph-checkpoint-postgres 1.0.10__tar.gz → 1.0.11__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-checkpoint-postgres
3
- Version: 1.0.10
3
+ Version: 1.0.11
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
@@ -1,8 +1,18 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from contextlib import asynccontextmanager
4
- from typing import Any, AsyncIterator, Iterable, Sequence, cast
4
+ from typing import (
5
+ Any,
6
+ AsyncIterator,
7
+ Callable,
8
+ Iterable,
9
+ Optional,
10
+ Sequence,
11
+ Union,
12
+ cast,
13
+ )
5
14
 
15
+ import orjson
6
16
  from psycopg import AsyncConnection, AsyncCursor
7
17
  from psycopg.errors import UndefinedTable
8
18
  from psycopg.rows import dict_row
@@ -19,7 +29,16 @@ logger = logging.getLogger(__name__)
19
29
 
20
30
 
21
31
  class AsyncPostgresStore(BasePostgresStore[AsyncConnection]):
22
- def __init__(self, conn: AsyncConnection[Any]) -> None:
32
+ def __init__(
33
+ self,
34
+ conn: AsyncConnection[Any],
35
+ *,
36
+ deserializer: Optional[
37
+ Callable[[Union[bytes, orjson.Fragment]], dict[str, Any]]
38
+ ] = None,
39
+ ) -> None:
40
+ super().__init__(deserializer=deserializer)
41
+ self.conn = conn
23
42
  self.conn = conn
24
43
  self.loop = asyncio.get_running_loop()
25
44
 
@@ -87,7 +106,9 @@ class AsyncPostgresStore(BasePostgresStore[AsyncConnection]):
87
106
  for idx, key in items:
88
107
  row = key_to_row.get(key)
89
108
  if row:
90
- results[idx] = _row_to_item(namespace, row)
109
+ results[idx] = _row_to_item(
110
+ namespace, row, loader=self._deserializer
111
+ )
91
112
  else:
92
113
  results[idx] = None
93
114
 
@@ -115,7 +136,10 @@ class AsyncPostgresStore(BasePostgresStore[AsyncConnection]):
115
136
 
116
137
  for cur, idx, op in cursors:
117
138
  rows = cast(list[Row], await cur.fetchall())
118
- items = [_row_to_item(op.namespace_prefix, row) for row in rows]
139
+ items = [
140
+ _row_to_item(op.namespace_prefix, row, loader=self._deserializer)
141
+ for row in rows
142
+ ]
119
143
  results[idx] = items
120
144
 
121
145
  async def _batch_list_namespaces_ops(
@@ -4,7 +4,18 @@ import logging
4
4
  from collections import defaultdict
5
5
  from contextlib import contextmanager
6
6
  from datetime import datetime
7
- from typing import Any, Generic, Iterable, Iterator, Sequence, TypeVar, Union, cast
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Generic,
11
+ Iterable,
12
+ Iterator,
13
+ Optional,
14
+ Sequence,
15
+ TypeVar,
16
+ Union,
17
+ cast,
18
+ )
8
19
 
9
20
  import orjson
10
21
  from psycopg import BaseConnection, Connection, Cursor
@@ -54,6 +65,17 @@ C = TypeVar("C", bound=BaseConnection)
54
65
  class BasePostgresStore(BaseStore, Generic[C]):
55
66
  MIGRATIONS = MIGRATIONS
56
67
  conn: C
68
+ __slots__ = ("_deserializer",)
69
+
70
+ def __init__(
71
+ self,
72
+ *,
73
+ deserializer: Optional[
74
+ Callable[[Union[bytes, orjson.Fragment]], dict[str, Any]]
75
+ ] = None,
76
+ ) -> None:
77
+ super().__init__()
78
+ self._deserializer = deserializer
57
79
 
58
80
  def _get_batch_GET_ops_queries(
59
81
  self,
@@ -191,7 +213,15 @@ class BasePostgresStore(BaseStore, Generic[C]):
191
213
 
192
214
 
193
215
  class PostgresStore(BasePostgresStore[Connection]):
194
- def __init__(self, conn: Connection[Any]) -> None:
216
+ def __init__(
217
+ self,
218
+ conn: Connection[Any],
219
+ *,
220
+ deserializer: Optional[
221
+ Callable[[Union[bytes, orjson.Fragment]], dict[str, Any]]
222
+ ] = None,
223
+ ) -> None:
224
+ super().__init__(deserializer=deserializer)
195
225
  self.conn = conn
196
226
 
197
227
  def batch(self, ops: Iterable[Op]) -> list[Result]:
@@ -246,7 +276,9 @@ class PostgresStore(BasePostgresStore[Connection]):
246
276
  for idx, key in items:
247
277
  row = key_to_row.get(key)
248
278
  if row:
249
- results[idx] = _row_to_item(namespace, row)
279
+ results[idx] = _row_to_item(
280
+ namespace, row, loader=self._deserializer
281
+ )
250
282
  else:
251
283
  results[idx] = None
252
284
 
@@ -274,7 +306,10 @@ class PostgresStore(BasePostgresStore[Connection]):
274
306
 
275
307
  for cur, idx, op in cursors:
276
308
  rows = cast(list[Row], cur.fetchall())
277
- items = [_row_to_item(op.namespace_prefix, row) for row in rows]
309
+ items = [
310
+ _row_to_item(op.namespace_prefix, row, loader=self._deserializer)
311
+ for row in rows
312
+ ]
278
313
  results[idx] = items
279
314
 
280
315
  def _batch_list_namespaces_ops(
@@ -361,11 +396,17 @@ def _namespace_to_ltree(namespace: tuple[str, ...]) -> str:
361
396
  return ".".join(namespace)
362
397
 
363
398
 
364
- def _row_to_item(namespace: tuple[str, ...], row: Row) -> Item:
399
+ def _row_to_item(
400
+ namespace: tuple[str, ...],
401
+ row: Row,
402
+ *,
403
+ loader: Optional[Callable[[Union[bytes, orjson.Fragment]], dict[str, Any]]] = None,
404
+ ) -> Item:
365
405
  """Convert a row from the database into an Item."""
406
+ loader = loader or _json_loads
366
407
  val = row["value"]
367
408
  return Item(
368
- value=val if isinstance(val, dict) else _json_loads(val),
409
+ value=val if isinstance(val, dict) else loader(val),
369
410
  key=row["key"],
370
411
  namespace=namespace,
371
412
  created_at=row["created_at"],
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-checkpoint-postgres"
3
- version = "1.0.10"
3
+ version = "1.0.11"
4
4
  description = "Library with a Postgres implementation of LangGraph checkpoint saver."
5
5
  authors = []
6
6
  license = "MIT"