langgraph-runtime-pg 0.11.1__py3-none-any.whl

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,39 @@
1
+ """DB retry helpers matching the inmem surface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import functools
7
+ from collections.abc import Awaitable, Callable
8
+ from typing import ParamSpec, TypeVar
9
+
10
+ P = ParamSpec("P")
11
+ T = TypeVar("T")
12
+
13
+
14
+ class RetryableException(Exception):
15
+ pass
16
+
17
+
18
+ RETRIABLE_EXCEPTIONS: tuple[type[BaseException], ...] = (RetryableException,)
19
+ OVERLOADED_EXCEPTIONS: tuple[type[BaseException], ...] = ()
20
+
21
+
22
+ def retry_db(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
23
+ """Retry ``RETRIABLE_EXCEPTIONS`` up to 3 times."""
24
+
25
+ @functools.wraps(func)
26
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
27
+ last: BaseException | None = None
28
+ for i in range(3):
29
+ try:
30
+ return await func(*args, **kwargs)
31
+ except RETRIABLE_EXCEPTIONS as exc:
32
+ last = exc
33
+ if i == 2:
34
+ raise
35
+ await asyncio.sleep(0.01)
36
+ assert last is not None # pragma: no cover
37
+ raise last # pragma: no cover
38
+
39
+ return wrapper
@@ -0,0 +1,9 @@
1
+ """Internal routes stub."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def get_internal_routes() -> list[Any]:
9
+ return []
@@ -0,0 +1,183 @@
1
+ """Store factory: AsyncPostgresStore for the API, PgStore for tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from datetime import UTC, datetime
7
+ from typing import Any, cast
8
+
9
+ from langgraph.store.postgres.aio import AsyncPostgresStore
10
+ from psycopg.rows import dict_row
11
+ from psycopg_pool import AsyncConnectionPool
12
+ from sqlalchemy import delete, select as sa_select
13
+ from sqlalchemy.dialects.postgresql import insert as pg_insert
14
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
15
+
16
+ from langgraph_runtime_pg.database import to_psycopg_uri
17
+ from langgraph_runtime_pg.models import StoreItemRow
18
+
19
+ _STORE_CONFIG: dict | None = None
20
+ _STORE: AsyncPostgresStore | None = None
21
+ _STORE_POOL: AsyncConnectionPool[Any] | None = None
22
+ _STORE_INSTANCE: PgStore | None = None
23
+ _SETUP_LOCK = asyncio.Lock()
24
+
25
+
26
+ def set_store_config(config: dict) -> None:
27
+ global _STORE_CONFIG
28
+ _STORE_CONFIG = config
29
+
30
+
31
+ async def setup_store() -> AsyncPostgresStore:
32
+ """Open AsyncPostgresStore and initialize its tables."""
33
+ global _STORE, _STORE_POOL
34
+ async with _SETUP_LOCK:
35
+ if _STORE is not None:
36
+ return _STORE
37
+ if _STORE_POOL is not None:
38
+ try:
39
+ await _STORE_POOL.close()
40
+ except Exception:
41
+ pass
42
+ _STORE_POOL = None
43
+
44
+ uri = to_psycopg_uri()
45
+ pool = AsyncConnectionPool(
46
+ conninfo=uri,
47
+ min_size=1,
48
+ max_size=10,
49
+ open=False,
50
+ kwargs={
51
+ "autocommit": True,
52
+ "prepare_threshold": 0,
53
+ "row_factory": dict_row,
54
+ },
55
+ )
56
+ try:
57
+ await pool.open()
58
+ store = AsyncPostgresStore(cast(Any, pool))
59
+ await store.setup()
60
+ except Exception:
61
+ try:
62
+ await pool.close()
63
+ except Exception:
64
+ pass
65
+ raise
66
+ _STORE_POOL = pool
67
+ _STORE = store
68
+ return _STORE
69
+
70
+
71
+ async def teardown_store() -> None:
72
+ global _STORE, _STORE_POOL
73
+ store, pool = _STORE, _STORE_POOL
74
+ _STORE = None
75
+ _STORE_POOL = None
76
+ if store is not None:
77
+ try:
78
+ stop = getattr(store, "stop_ttl_sweeper", None)
79
+ if stop is not None:
80
+ await asyncio.wait_for(stop(), timeout=2.0)
81
+ except Exception:
82
+ pass
83
+ if pool is not None:
84
+ try:
85
+ await asyncio.wait_for(pool.close(), timeout=5.0)
86
+ except Exception:
87
+ pass
88
+
89
+
90
+ class PgStore:
91
+ """Minimal prefix/key store over the store_items table (tests)."""
92
+
93
+ def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
94
+ self._sf = session_factory
95
+
96
+ async def start_ttl_sweeper(self) -> asyncio.Task:
97
+ return asyncio.create_task(asyncio.sleep(0))
98
+
99
+ async def aput(self, prefix: str, key: str, value: dict) -> None:
100
+ now = datetime.now(UTC)
101
+ async with self._sf() as session:
102
+ await session.execute(
103
+ pg_insert(StoreItemRow)
104
+ .values(
105
+ prefix=prefix,
106
+ key=key,
107
+ value=value,
108
+ created_at=now,
109
+ updated_at=now,
110
+ )
111
+ .on_conflict_do_update(
112
+ index_elements=["prefix", "key"],
113
+ set_={"value": value, "updated_at": now},
114
+ )
115
+ )
116
+ await session.commit()
117
+
118
+ async def aget(self, prefix: str, key: str) -> dict | None:
119
+ async with self._sf() as session:
120
+ row = (
121
+ await session.execute(
122
+ sa_select(StoreItemRow).where(
123
+ StoreItemRow.prefix == prefix,
124
+ StoreItemRow.key == key,
125
+ )
126
+ )
127
+ ).scalar_one_or_none()
128
+ return row.value if row else None
129
+
130
+ async def adelete(self, prefix: str, key: str) -> None:
131
+ async with self._sf() as session:
132
+ await session.execute(
133
+ delete(StoreItemRow).where(
134
+ StoreItemRow.prefix == prefix,
135
+ StoreItemRow.key == key,
136
+ )
137
+ )
138
+ await session.commit()
139
+
140
+ async def alist(self, prefix: str) -> list[dict]:
141
+ async with self._sf() as session:
142
+ rows = (
143
+ (
144
+ await session.execute(
145
+ sa_select(StoreItemRow).where(StoreItemRow.prefix == prefix)
146
+ )
147
+ )
148
+ .scalars()
149
+ .all()
150
+ )
151
+ return [{"key": r.key, "value": r.value} for r in rows]
152
+
153
+ def close(self) -> None:
154
+ pass
155
+
156
+
157
+ def Store(*args: Any, **kwargs: Any) -> AsyncPostgresStore:
158
+ """Return the process-wide AsyncPostgresStore (requires setup_store)."""
159
+ if _STORE is None:
160
+ raise RuntimeError(
161
+ "Store requires setup_store()/start_pool() first (DATABASE_URI required)"
162
+ )
163
+ return _STORE
164
+
165
+
166
+ def pg_store_for_tests() -> PgStore:
167
+ """Return the test PgStore singleton over store_items."""
168
+ global _STORE_INSTANCE
169
+ if _STORE_INSTANCE is None:
170
+ from langgraph_runtime_pg.database import _SESSION_FACTORY
171
+
172
+ if _SESSION_FACTORY is None:
173
+ raise RuntimeError("Call start_pool() first")
174
+ _STORE_INSTANCE = PgStore(_SESSION_FACTORY)
175
+ return _STORE_INSTANCE
176
+
177
+
178
+ def reset_store() -> None:
179
+ """Reset the test PgStore singleton."""
180
+ global _STORE_INSTANCE
181
+ if _STORE_INSTANCE is not None:
182
+ _STORE_INSTANCE.close()
183
+ _STORE_INSTANCE = None
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: langgraph-runtime-pg
3
+ Version: 0.11.1
4
+ Summary: Self-hosted LangSmith Deployments alternative: MIT Postgres+Redis runtime (edition=pg) for stock langgraph-api
5
+ Project-URL: Homepage, https://github.com/langhost/langhost
6
+ Project-URL: Repository, https://github.com/langhost/langhost
7
+ Project-URL: Issues, https://github.com/langhost/langhost/issues
8
+ Project-URL: Changelog, https://github.com/langhost/langhost/releases
9
+ Author-email: Mohankumar Ramachandran <mail@mohanram.dev>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-protocol,agent-server,agents,ai-agents,asgi,checkpoint,langchain,langgraph,langgraph-api,langgraph-checkpoint,langgraph-sdk,langsmith,langsmith-deployments,llm,multi-agent,open-source,postgres,postgresql,redis,runtime,self-hosted,uvicorn
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: alembic>=1.14
29
+ Requires-Dist: asyncpg>=0.29
30
+ Requires-Dist: croniter>=1.0
31
+ Requires-Dist: langgraph-api==0.11.1
32
+ Requires-Dist: langgraph-checkpoint-postgres==3.1.0
33
+ Requires-Dist: orjson>=3.9
34
+ Requires-Dist: psycopg[binary]>=3.3.4
35
+ Requires-Dist: redis>=5.0
36
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.51
37
+ Requires-Dist: starlette>=0.37
38
+ Requires-Dist: structlog>=24.0
39
+ Description-Content-Type: text/markdown
40
+
41
+ # langgraph-runtime-pg
42
+
43
+ MIT Postgres + Redis runtime (`LANGGRAPH_RUNTIME_EDITION=pg`) for stock
44
+ [`langgraph-api`](https://pypi.org/project/langgraph-api/).
45
+
46
+ See the [repository README](https://github.com/langhost/langhost)
47
+ for setup, CLI (`langhost`), and architecture.
@@ -0,0 +1,23 @@
1
+ langgraph_runtime_pg/__init__.py,sha256=ZUcjd1kcny4doy9RCGYC660IGUY4Kncym5kNOsoLCpo,697
2
+ langgraph_runtime_pg/checkpoint.py,sha256=CDbfl0rpiU5obyWN5844qchR3yUxuredYiQ04-X0SW4,2413
3
+ langgraph_runtime_pg/database.py,sha256=aKuaURsEZOH5MMwBBCDrSrHDgPedmOz1D-S4S9i1wbg,14389
4
+ langgraph_runtime_pg/lifespan.py,sha256=j7RIbIE-9ASfL60dG8AYF1DitLZOsOpw2Sgfirz4Xdg,6603
5
+ langgraph_runtime_pg/metrics.py,sha256=IUwlI2xAhwnYwiOR-ltg-aO655aEX0RouHKNJbRWlBc,128
6
+ langgraph_runtime_pg/migrate.py,sha256=eq0GcE6EhapEOYv6jeNyQlm--G7qcwvN5AJbXqDAJHs,3061
7
+ langgraph_runtime_pg/models.py,sha256=Fblz8JONqGpkPCaiU6kk4yPyrOUdh99MbkULhFcBons,8474
8
+ langgraph_runtime_pg/ops.py,sha256=JAtM02a-81ALZrMocftvvin_jawr2bUG7vHHgrDoAPM,133432
9
+ langgraph_runtime_pg/queue.py,sha256=MjDRKtEGUp9Sk-n6caQATiYnH7e-_7Dw_o8ofMVY9oo,10485
10
+ langgraph_runtime_pg/redis_stream.py,sha256=Z-UQNyNRHkdOlZSMP2EigE6CvNooUE8Kp-2SAc57l6E,31813
11
+ langgraph_runtime_pg/retry.py,sha256=uRx9QtuWKVdw4KGyfiI8NBklqVensGXYcTiun8joMYQ,1074
12
+ langgraph_runtime_pg/routes.py,sha256=r2hK0E1RKyi1D4PBF3-dsk7_otu0BZZZfNzaa3TPEwo,144
13
+ langgraph_runtime_pg/store.py,sha256=hFbQTVJLAPhFELwVdABAHvShENDztppD8VJ1v5QyaS4,5654
14
+ langgraph_runtime_pg/migrations/__init__.py,sha256=M-gt0gXAGxVsYXJgZouNInvcPmk291foMrYnhooVaS0,56
15
+ langgraph_runtime_pg/migrations/env.py,sha256=fWLULVAKqqmSvCpVHJ61WyJzuBYnqCSm8eCWgUbjHCY,1329
16
+ langgraph_runtime_pg/migrations/versions/001_initial_schema.py,sha256=MnzQ8mEl0xCMhT5RBC4NujyEKUIkC8rhxHM_HWoXXYc,11747
17
+ langgraph_runtime_pg/migrations/versions/__init__.py,sha256=4paf33VBK_l7fudrFVmQbL68tQNDGVNFUGgdjxBSq5s,32
18
+ langgraph_runtime_pg/migrations/script.py.mako,sha256=kyVbIw4oMjKYXbLX7wA29--0PK_UU--SdXx-my0Znls,645
19
+ langgraph_runtime_pg-0.11.1.dist-info/METADATA,sha256=y7Ysm6w5tXlDcsGiZFDD3pQg-C41X7lQINCUBLdzY5k,2194
20
+ langgraph_runtime_pg-0.11.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
21
+ langgraph_runtime_pg-0.11.1.dist-info/entry_points.txt,sha256=8S_Q8wEvQtqbTxqgbHazjiCnmY2NDOEH2dLDVIHd3WA,83
22
+ langgraph_runtime_pg-0.11.1.dist-info/licenses/LICENSE,sha256=VOzjTuqsyEVtwd7ylP1eoLjPAAvdB6iC9a7JBGFJUgE,1080
23
+ langgraph_runtime_pg-0.11.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ langgraph-runtime-pg-migrate = langgraph_runtime_pg.migrate:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohankumar Ramachandran
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.