@@ -77,7 +77,9 @@ def __init__(
7777 checkpoint_prefix = checkpoint_prefix ,
7878 checkpoint_write_prefix = checkpoint_write_prefix ,
7979 )
80- self .loop = asyncio .get_running_loop ()
80+ # Deferred: the event loop is captured in asetup() so that the saver can
81+ # be constructed outside an async context (Issue #179).
82+ self .loop : Optional [asyncio .AbstractEventLoop ] = None
8183
8284 # Instance-level cache for frequently used keys (limited size to prevent memory issues)
8385 self ._key_cache : Dict [str , str ] = {}
@@ -139,6 +141,13 @@ async def from_conn_string(
139141
140142 async def asetup (self ) -> None :
141143 """Initialize Redis indexes asynchronously."""
144+ # Capture the running event loop here so that sync wrapper methods
145+ # (get_tuple, put, put_writes, …) can dispatch coroutines to it via
146+ # asyncio.run_coroutine_threadsafe. Deferring this to asetup() instead
147+ # of __init__ lets callers construct the saver outside an async context
148+ # (Issue #179).
149+ self .loop = asyncio .get_running_loop ()
150+
142151 await self .checkpoints_index .create (overwrite = False )
143152 await self .checkpoint_writes_index .create (overwrite = False )
144153
@@ -725,6 +734,11 @@ def create_indexes(self) -> None:
725734
726735 def get_tuple (self , config : RunnableConfig ) -> Optional [CheckpointTuple ]:
727736 """Retrieve a checkpoint tuple from Redis synchronously."""
737+ if self .loop is None :
738+ raise RuntimeError (
739+ "AsyncShallowRedisSaver must be set up before calling synchronous methods. "
740+ "Call `await saver.asetup()` or use `async with saver:` first."
741+ )
728742 try :
729743 if asyncio .get_running_loop () is self .loop :
730744 raise asyncio .InvalidStateError (
@@ -747,6 +761,11 @@ def put(
747761 new_versions : ChannelVersions ,
748762 ) -> RunnableConfig :
749763 """Store only the latest checkpoint synchronously."""
764+ if self .loop is None :
765+ raise RuntimeError (
766+ "AsyncShallowRedisSaver must be set up before calling synchronous methods. "
767+ "Call `await saver.asetup()` or use `async with saver:` first."
768+ )
750769 return asyncio .run_coroutine_threadsafe (
751770 self .aput (config , checkpoint , metadata , new_versions ), self .loop
752771 ).result ()
@@ -759,6 +778,11 @@ def put_writes(
759778 task_path : str = "" ,
760779 ) -> None :
761780 """Store intermediate writes synchronously."""
781+ if self .loop is None :
782+ raise RuntimeError (
783+ "AsyncShallowRedisSaver must be set up before calling synchronous methods. "
784+ "Call `await saver.asetup()` or use `async with saver:` first."
785+ )
762786 return asyncio .run_coroutine_threadsafe (
763787 self .aput_writes (config , writes , task_id ), self .loop
764788 ).result ()
@@ -771,6 +795,11 @@ def get_channel_values(
771795 channel_versions : Optional [Dict [str , Any ]] = None ,
772796 ) -> dict [str , Any ]:
773797 """Retrieve channel_values dictionary with properly constructed message objects (sync wrapper)."""
798+ if self .loop is None :
799+ raise RuntimeError (
800+ "AsyncShallowRedisSaver must be set up before calling synchronous methods. "
801+ "Call `await saver.asetup()` or use `async with saver:` first."
802+ )
774803 try :
775804 if asyncio .get_running_loop () is self .loop :
776805 raise asyncio .InvalidStateError (
0 commit comments