第一次提交
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
import asyncio
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from models import Session
|
||||
|
||||
|
||||
class SessionNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SessionStorageError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class JsonSessionStorage:
|
||||
def __init__(self, data_dir: Path) -> None:
|
||||
self.data_dir = data_dir
|
||||
|
||||
async def create(self, system_prompt: str) -> Session:
|
||||
return await asyncio.to_thread(self._create, system_prompt)
|
||||
|
||||
async def read(self, session_id: str) -> Session:
|
||||
return await asyncio.to_thread(self._read, session_id)
|
||||
|
||||
async def write(self, session: Session) -> None:
|
||||
await asyncio.to_thread(self._write, session)
|
||||
|
||||
def _path(self, session_id: str) -> Path:
|
||||
try:
|
||||
normalized = str(UUID(session_id))
|
||||
except ValueError as exc:
|
||||
raise SessionNotFoundError(session_id) from exc
|
||||
return self.data_dir / f"{normalized}.json"
|
||||
|
||||
def _create(self, system_prompt: str) -> Session:
|
||||
try:
|
||||
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
while True:
|
||||
now = datetime.now(UTC)
|
||||
session = Session(
|
||||
session_id=str(uuid4()),
|
||||
system_prompt=system_prompt,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
path = self._path(session.session_id)
|
||||
try:
|
||||
with path.open("x", encoding="utf-8") as file:
|
||||
json.dump(
|
||||
session.model_dump(mode="json"),
|
||||
file,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
return session
|
||||
except FileExistsError:
|
||||
continue
|
||||
except OSError as exc:
|
||||
raise SessionStorageError("could not create session") from exc
|
||||
|
||||
def _read(self, session_id: str) -> Session:
|
||||
path = self._path(session_id)
|
||||
try:
|
||||
with path.open(encoding="utf-8") as file:
|
||||
return Session.model_validate(json.load(file))
|
||||
except FileNotFoundError as exc:
|
||||
raise SessionNotFoundError(session_id) from exc
|
||||
except (OSError, json.JSONDecodeError, ValidationError) as exc:
|
||||
raise SessionStorageError("could not read session") from exc
|
||||
|
||||
def _write(self, session: Session) -> None:
|
||||
path = self._path(session.session_id)
|
||||
if not path.exists():
|
||||
raise SessionNotFoundError(session.session_id)
|
||||
|
||||
temporary_path: str | None = None
|
||||
try:
|
||||
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
with NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
dir=self.data_dir,
|
||||
prefix=f".{session.session_id}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as file:
|
||||
temporary_path = file.name
|
||||
json.dump(
|
||||
session.model_dump(mode="json"),
|
||||
file,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
file.flush()
|
||||
os.fsync(file.fileno())
|
||||
os.replace(temporary_path, path)
|
||||
except OSError as exc:
|
||||
raise SessionStorageError("could not write session") from exc
|
||||
finally:
|
||||
if temporary_path:
|
||||
try:
|
||||
Path(temporary_path).unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
Reference in New Issue
Block a user