Исходный код raito.utils.storages.json
from __future__ import annotations
import json
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from aiogram.fsm.state import State
from aiogram.fsm.storage.base import BaseStorage, StateType, StorageKey
from typing_extensions import override
from raito.utils import loggers
__all__ = ("JSONStorage",)
[документация]
class JSONStorage(BaseStorage):
"""JSON-based FSM storage for development and testing.
Stores FSM state and data in a local JSON file as a flat key-value mapping.
"""
def __init__(self, path: str | Path, *, key_separator: str = ":") -> None:
"""Initialize JSONStorage.
:param path: Path to the JSON file to be used for persistent storage
:param key_separator: Delimiter used when constructing keys
"""
self.path = Path(path)
self.key_separator = key_separator
self._data: dict[str, dict[str, Any]] = {}
self._load()
def _load(self) -> None:
"""Load JSON file contents into memory."""
if self.path.exists():
try:
content = self.path.read_text(encoding="utf-8")
self._data = json.loads(content)
except json.JSONDecodeError as exc:
loggers.storages.warning("JSON decode error: %s — file will be ignored", exc)
self._data = {}
except UnicodeDecodeError as exc:
loggers.storages.warning("Invalid encoding in %s: %s", self.path, exc)
self._data = {}
except OSError as exc:
loggers.storages.warning("Failed to read JSON storage file: %s", exc)
self._data = {}
def _save(self) -> None:
"""Write current memory state to JSON file."""
self.path.write_text(
json.dumps(self._data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def _build_key(self, key: StorageKey) -> str:
"""Construct a unique key string from StorageKey.
:param key: FSM storage key
:return: String key
"""
parts = [str(key.bot_id), str(key.chat_id), str(key.user_id)]
if key.thread_id:
parts.append(str(key.thread_id))
if key.business_connection_id:
parts.append(str(key.business_connection_id))
if key.destiny:
parts.append(key.destiny)
return self.key_separator.join(parts)
[документация]
@override
async def get_state(self, key: StorageKey) -> str | None:
"""Retrieve the current state for a key.
:param key: FSM storage key
:return: Current state or None
"""
return self._data.get(self._build_key(key), {}).get("state")
[документация]
@override
async def set_state(self, key: StorageKey, state: StateType | None = None) -> None:
"""Set a new state for the given key.
:param key: FSM storage key
:param state: New state to store
"""
if isinstance(state, State):
state = state.state
str_key = self._build_key(key)
self._data.setdefault(str_key, {})["state"] = state
self._save()
[документация]
@override
async def get_data(self, key: StorageKey) -> dict[str, Any]:
"""Retrieve data dictionary for the key.
:param key: FSM storage key
:return: Stored data or empty dict
"""
return self._data.get(self._build_key(key), {}).get("data", {})
[документация]
@override
async def set_data(self, key: StorageKey, data: Mapping[str, Any]) -> None:
"""Set data dictionary for the key.
:param key: FSM storage key
:param data: Data to store
"""
str_key = self._build_key(key)
self._data.setdefault(str_key, {})["data"] = data
self._save()
[документация]
@override
async def update_data(self, key: StorageKey, data: Mapping[str, Any]) -> dict[str, Any]:
"""Update the current data for the key.
:param key: FSM storage key
:param data: New data to merge with existing
:return: Updated data
"""
current = await self.get_data(key)
current.update(data)
await self.set_data(key, current)
return current
[документация]
async def clear(self) -> None:
"""Clear all states and data from storage."""
self._data.clear()
self._save()
[документация]
@override
async def close(self) -> None:
"""Close the storage (optional flush)"""
self._save()