@@ -709,9 +709,9 @@ class Sandbox(SandboxHandleBase[SandboxRuntimeSession]):
709709 """Control an asynchronous Vercel Sandbox.
710710
711711 A sandbox has at most one active current session. Process and filesystem
712- operations target the session recorded by this handle. Use ``session`` to
713- resolve that session, resuming the sandbox into a replacement session when
714- needed , and ``destroy`` to permanently remove the sandbox.
712+ operations target the session recorded by this handle. Use
713+ ``sandbox.resume_sandbox`` to ensure the sandbox has an active session,
714+ ``stop`` to stop it , and ``destroy`` to permanently remove the sandbox.
715715 """
716716
717717 __slots__ = ("_service" , "fs" )
@@ -728,25 +728,6 @@ def __init__(self, *, payload: SandboxState, service: SandboxService) -> None:
728728 write_files_cwd = self ._write_files_cwd ,
729729 )
730730
731- def session (self ) -> "CreateRuntimeSessionOperation" :
732- """Prepare a single-use current-session operation.
733-
734- Awaiting returns the sandbox's active current session. If the current
735- session is stopped or otherwise unusable, the backend resumes the
736- sandbox from its latest snapshot and returns the replacement session.
737- Using the operation as an async context manager stops the returned
738- session on exit.
739-
740- The returned session handle is independent. If resuming replaces the
741- backend's current session, this existing ``Sandbox`` handle is not
742- refreshed automatically.
743- """
744- return CreateRuntimeSessionOperation (
745- service = self ._service ,
746- sandbox_name = self .name ,
747- project_id = self .project_id ,
748- )
749-
750731 async def run_process (
751732 self ,
752733 command : str ,
@@ -901,6 +882,12 @@ async def snapshot(self, *, expiration: SnapshotExpirationInput = None) -> Snaps
901882 self ._apply_current_session_payload (result .session )
902883 return Snapshot (payload = result .snapshot , service = self ._service )
903884
885+ async def stop (self ) -> Self :
886+ """Stop the current session and return this sandbox handle."""
887+ payload = await self ._service .stop_runtime_session (session_id = self .current_session_id )
888+ self ._apply_current_session_payload (payload )
889+ return self
890+
904891 async def destroy (self ) -> Self :
905892 """Permanently destroy the sandbox and refresh this handle."""
906893 payload = await self ._service .destroy_sandbox (name = self .name , project_id = self .project_id )
@@ -974,14 +961,21 @@ class CreateSandboxOperation:
974961 """Manage one asynchronous sandbox creation request.
975962
976963 Await the operation to create a sandbox that remains alive, or use it as an
977- async context manager to destroy the created sandbox on exit. An operation
978- can be consumed only once. Exiting the context raises
979- ``SandboxCleanupError`` if destroying the sandbox fails.
964+ async context manager to stop the created sandbox and optionally destroy it
965+ on exit. An operation can be consumed only once. Exiting the context raises
966+ ``SandboxCleanupError`` if cleanup fails.
980967 """
981968
982- def __init__ (self , * , service : SandboxService , params : _CreateSandboxParams ) -> None :
969+ def __init__ (
970+ self ,
971+ * ,
972+ service : SandboxService ,
973+ params : _CreateSandboxParams ,
974+ destroy : bool ,
975+ ) -> None :
983976 self ._service = service
984977 self ._params = params
978+ self ._destroy = destroy
985979 self ._consumed = False
986980 self ._handle : Sandbox | None = None
987981
@@ -1025,18 +1019,7 @@ async def __aexit__(
10251019 ) -> None :
10261020 if self ._handle is None :
10271021 return None
1028- try :
1029- payload = await self ._service .destroy_sandbox (
1030- name = self ._handle .name , project_id = self ._handle .project_id
1031- )
1032- self ._handle ._apply_payload (payload )
1033- except Exception as cleanup_exc :
1034- raise SandboxCleanupError (
1035- f"Failed to clean up sandbox { self ._handle .name !r} " ,
1036- resource_type = "sandbox" ,
1037- resource_id = self ._handle .name ,
1038- cause = cleanup_exc ,
1039- ) from cleanup_exc
1022+ await _cleanup_managed_sandbox (self ._handle , destroy = self ._destroy )
10401023 return None
10411024
10421025 def __del__ (self ) -> None :
@@ -1049,41 +1032,46 @@ def __del__(self) -> None:
10491032 )
10501033
10511034
1052- class CreateRuntimeSessionOperation :
1053- """Manage one asynchronous current-session request.
1035+ @dataclass (frozen = True , slots = True )
1036+ class _ResumeSandboxParams :
1037+ name : str
1038+ project_id : str | None = None
1039+ include_system_routes : bool | None = None
1040+
10541041
1055- Await the operation to return the active current session, resuming the
1056- sandbox into a replacement session when necessary. Use it as an async
1057- context manager to stop the returned session on exit. An operation can be
1058- consumed only once. Exiting the context raises ``SandboxCleanupError`` if
1059- stopping the session fails.
1042+ class ResumeSandboxOperation :
1043+ """Manage one asynchronous sandbox resume request.
1044+
1045+ Await the operation to return a sandbox with an active current session, or
1046+ use it as an async context manager to stop that session on exit. An
1047+ operation can be consumed only once. Exiting the context raises
1048+ ``SandboxCleanupError`` if stopping the sandbox fails.
10601049 """
10611050
1062- def __init__ (
1063- self , * , service : SandboxService , sandbox_name : str , project_id : str | None
1064- ) -> None :
1051+ def __init__ (self , * , service : SandboxService , params : _ResumeSandboxParams ) -> None :
10651052 self ._service = service
1066- self ._sandbox_name = sandbox_name
1067- self ._project_id = project_id
1053+ self ._params = params
10681054 self ._consumed = False
1069- self ._handle : SandboxRuntimeSession | None = None
1055+ self ._handle : Sandbox | None = None
10701056
10711057 def _mark_consumed (self ) -> None :
10721058 if self ._consumed :
1073- raise RuntimeError ("sandbox runtime-session operations can only be used once" )
1059+ raise RuntimeError ("sandbox.resume_sandbox(...) operations can only be used once" )
10741060 self ._consumed = True
10751061
1076- async def _run_once (self ) -> SandboxRuntimeSession :
1062+ async def _run_once (self ) -> Sandbox :
10771063 self ._mark_consumed ()
1078- payload = await self ._service .create_runtime_session (
1079- name = self ._sandbox_name , project_id = self ._project_id
1064+ return await resume_sandbox (
1065+ self ._service ,
1066+ name = self ._params .name ,
1067+ project_id = self ._params .project_id ,
1068+ include_system_routes = self ._params .include_system_routes ,
10801069 )
1081- return SandboxRuntimeSession (payload = payload , service = self ._service )
10821070
1083- def __await__ (self ) -> Generator [Any , None , SandboxRuntimeSession ]:
1071+ def __await__ (self ) -> Generator [Any , None , Sandbox ]:
10841072 return self ._run_once ().__await__ ()
10851073
1086- async def __aenter__ (self ) -> SandboxRuntimeSession :
1074+ async def __aenter__ (self ) -> Sandbox :
10871075 handle = await self ._run_once ()
10881076 self ._handle = handle
10891077 return handle
@@ -1096,18 +1084,41 @@ async def __aexit__(
10961084 ) -> None :
10971085 if self ._handle is None :
10981086 return None
1099- try :
1100- payload = await self ._service .stop_runtime_session (session_id = self ._handle .id )
1101- self ._handle ._apply_payload (payload )
1102- except Exception as cleanup_exc :
1103- raise SandboxCleanupError (
1104- f"Failed to clean up sandbox runtime session { self ._handle .id !r} " ,
1105- resource_type = "sandbox_runtime_session" ,
1106- resource_id = self ._handle .id ,
1107- cause = cleanup_exc ,
1108- ) from cleanup_exc
1087+ await _cleanup_managed_sandbox (self ._handle , destroy = False )
11091088 return None
11101089
1090+ def __del__ (self ) -> None :
1091+ if self ._consumed :
1092+ return
1093+ warnings .warn (
1094+ "sandbox.resume_sandbox(...) operation was never awaited or entered" ,
1095+ RuntimeWarning ,
1096+ stacklevel = 2 ,
1097+ )
1098+
1099+
1100+ async def _cleanup_managed_sandbox (handle : Sandbox , * , destroy : bool ) -> None :
1101+ cleanup_error : Exception | None = None
1102+ try :
1103+ await handle .stop ()
1104+ except Exception as exc :
1105+ cleanup_error = exc
1106+
1107+ if destroy :
1108+ try :
1109+ await handle .destroy ()
1110+ except Exception as exc :
1111+ if cleanup_error is None :
1112+ cleanup_error = exc
1113+
1114+ if cleanup_error is not None :
1115+ raise SandboxCleanupError (
1116+ f"Failed to clean up sandbox { handle .name !r} " ,
1117+ resource_type = "sandbox" ,
1118+ resource_id = handle .name ,
1119+ cause = cleanup_error ,
1120+ ) from cleanup_error
1121+
11111122
11121123async def _create_sandbox (service : SandboxService , ** kwargs : Any ) -> Sandbox :
11131124 try :
@@ -1132,6 +1143,7 @@ def create_sandbox_operation(
11321143 tags : Mapping [str , str ] | None = None ,
11331144 snapshot_expiration : SnapshotExpirationInput = None ,
11341145 snapshot_retention : SnapshotRetention | None = None ,
1146+ destroy : bool = True ,
11351147) -> CreateSandboxOperation :
11361148 return CreateSandboxOperation (
11371149 service = service ,
@@ -1150,13 +1162,35 @@ def create_sandbox_operation(
11501162 snapshot_expiration = _parse_snapshot_expiration (snapshot_expiration ),
11511163 snapshot_retention = snapshot_retention ,
11521164 ),
1165+ destroy = destroy ,
11531166 )
11541167
11551168
11561169async def get_sandbox (service : SandboxService , ** kwargs : Any ) -> Sandbox :
11571170 return Sandbox (payload = await service .get_sandbox (** kwargs ), service = service )
11581171
11591172
1173+ async def resume_sandbox (service : SandboxService , ** kwargs : Any ) -> Sandbox :
1174+ return Sandbox (payload = await service .resume_sandbox (** kwargs ), service = service )
1175+
1176+
1177+ def resume_sandbox_operation (
1178+ service : SandboxService ,
1179+ * ,
1180+ name : str ,
1181+ project_id : str | None = None ,
1182+ include_system_routes : bool | None = None ,
1183+ ) -> ResumeSandboxOperation :
1184+ return ResumeSandboxOperation (
1185+ service = service ,
1186+ params = _ResumeSandboxParams (
1187+ name = name ,
1188+ project_id = project_id ,
1189+ include_system_routes = include_system_routes ,
1190+ ),
1191+ )
1192+
1193+
11601194async def query_sandboxes_page (
11611195 service : SandboxService , ** kwargs : Any
11621196) -> QuerySandboxesPage [Sandbox ]:
0 commit comments