Persist Jupyter workspace bindings
This commit is contained in:
@@ -94,6 +94,71 @@ class JupyterRuntimeSession:
|
||||
self.bundle_digest = ''
|
||||
self.synced_at: float | None = None
|
||||
|
||||
@classmethod
|
||||
def from_persisted(cls, payload: dict[str, Any]) -> 'JupyterRuntimeSession':
|
||||
"""从持久化的 Jupyter cookie 和绑定信息恢复 runtime。"""
|
||||
|
||||
if requests is None:
|
||||
raise JupyterRuntimeError(
|
||||
'Missing dependency: requests. Please install project dependencies.'
|
||||
)
|
||||
binding_payload = payload.get('binding')
|
||||
if not isinstance(binding_payload, dict):
|
||||
raise JupyterRuntimeError('Invalid persisted Jupyter binding.')
|
||||
binding = JupyterWorkspaceBinding(
|
||||
account_id=str(binding_payload['account_id']),
|
||||
session_id=str(binding_payload['session_id']),
|
||||
base_url=normalize_jupyter_base_url(str(binding_payload['base_url'])),
|
||||
workspace_root=normalize_posix_path(str(binding_payload['workspace_root'])),
|
||||
workspace_cwd=normalize_posix_path(str(binding_payload['workspace_cwd'])),
|
||||
workspace_api_path=str(binding_payload['workspace_api_path']),
|
||||
jupyter_tree_url=str(binding_payload['jupyter_tree_url']),
|
||||
created_at=float(binding_payload.get('created_at') or time.time()),
|
||||
)
|
||||
http_session = requests.Session()
|
||||
cookies = payload.get('cookies')
|
||||
if isinstance(cookies, list):
|
||||
for cookie in cookies:
|
||||
if not isinstance(cookie, dict):
|
||||
continue
|
||||
name = cookie.get('name')
|
||||
value = cookie.get('value')
|
||||
if not isinstance(name, str) or not isinstance(value, str):
|
||||
continue
|
||||
http_session.cookies.set(
|
||||
name,
|
||||
value,
|
||||
domain=(
|
||||
cookie.get('domain')
|
||||
if isinstance(cookie.get('domain'), str)
|
||||
else None
|
||||
),
|
||||
path=(
|
||||
cookie.get('path')
|
||||
if isinstance(cookie.get('path'), str)
|
||||
else '/'
|
||||
),
|
||||
)
|
||||
xsrf_token = str(
|
||||
payload.get('xsrf_token') or http_session.cookies.get('_xsrf') or ''
|
||||
)
|
||||
if not xsrf_token:
|
||||
raise JupyterRuntimeError('Invalid persisted Jupyter _xsrf token.')
|
||||
runtime = cls(
|
||||
binding=binding,
|
||||
http_session=http_session,
|
||||
xsrf_token=xsrf_token,
|
||||
)
|
||||
if isinstance(payload.get('platform_root'), str):
|
||||
runtime.platform_root = str(payload['platform_root'])
|
||||
if isinstance(payload.get('skills_root'), str):
|
||||
runtime.skills_root = str(payload['skills_root'])
|
||||
if isinstance(payload.get('bundle_digest'), str):
|
||||
runtime.bundle_digest = str(payload['bundle_digest'])
|
||||
if isinstance(payload.get('synced_at'), (int, float)):
|
||||
runtime.synced_at = float(payload['synced_at'])
|
||||
return runtime
|
||||
|
||||
@classmethod
|
||||
def connect(
|
||||
cls,
|
||||
@@ -144,6 +209,30 @@ class JupyterRuntimeSession:
|
||||
)
|
||||
return runtime
|
||||
|
||||
def to_persisted_dict(self) -> dict[str, Any]:
|
||||
"""导出可持久化的连接信息,用于页面刷新或服务重启后恢复。"""
|
||||
|
||||
return {
|
||||
'binding': self.binding.to_dict(),
|
||||
'xsrf_token': self._xsrf_token,
|
||||
'cookies': [
|
||||
{
|
||||
'name': cookie.name,
|
||||
'value': cookie.value,
|
||||
'domain': cookie.domain,
|
||||
'path': cookie.path,
|
||||
'secure': bool(cookie.secure),
|
||||
'expires': cookie.expires,
|
||||
}
|
||||
for cookie in self._session.cookies
|
||||
],
|
||||
'platform_root': self.platform_root,
|
||||
'skills_root': self.skills_root,
|
||||
'bundle_digest': self.bundle_digest,
|
||||
'synced_at': self.synced_at,
|
||||
'persisted_at': time.time(),
|
||||
}
|
||||
|
||||
def bootstrap_workspace(
|
||||
self,
|
||||
*,
|
||||
@@ -824,6 +913,23 @@ class JupyterRuntimeManager:
|
||||
with self._lock:
|
||||
return self._sessions.get((account_id, session_id))
|
||||
|
||||
def restore_session(
|
||||
self,
|
||||
*,
|
||||
account_id: str,
|
||||
session_id: str,
|
||||
payload: dict[str, Any],
|
||||
) -> JupyterRuntimeSession:
|
||||
runtime = JupyterRuntimeSession.from_persisted(payload)
|
||||
if (
|
||||
runtime.binding.account_id != account_id
|
||||
or runtime.binding.session_id != session_id
|
||||
):
|
||||
raise JupyterRuntimeError('Persisted Jupyter binding does not match session.')
|
||||
with self._lock:
|
||||
self._sessions[(account_id, session_id)] = runtime
|
||||
return runtime
|
||||
|
||||
def session_payload(self, account_id: str, session_id: str) -> dict[str, Any]:
|
||||
runtime = self.get(account_id, session_id)
|
||||
if runtime is None:
|
||||
|
||||
Reference in New Issue
Block a user