More docs

This commit is contained in:
Alex Root Junior 2021-10-12 01:11:53 +03:00
parent 3931253a88
commit f97367b3ee
16 changed files with 415 additions and 100 deletions

View file

@ -20,28 +20,76 @@ class StorageKey:
class BaseStorage(ABC):
"""
Base class for all FSM storages
"""
@abstractmethod
@asynccontextmanager
async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]:
"""
Isolate events with lock.
Will be used as context manager
:param bot: instance of the current bot
:param key: storage key
:return: An async generator
"""
yield None
@abstractmethod
async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None:
"""
Set state for specified key
:param bot: instance of the current bot
:param key: storage key
:param state: new state
"""
pass
@abstractmethod
async def get_state(self, bot: Bot, key: StorageKey) -> Optional[str]:
"""
Get key state
:param bot: instance of the current bot
:param key: storage key
:return: current state
"""
pass
@abstractmethod
async def set_data(self, bot: Bot, key: StorageKey, data: Dict[str, Any]) -> None:
"""
Write data (replace)
:param bot: instance of the current bot
:param key: storage key
:param data: new data
"""
pass
@abstractmethod
async def get_data(self, bot: Bot, key: StorageKey) -> Dict[str, Any]:
"""
Get current data for key
:param bot: instance of the current bot
:param key: storage key
:return: current data
"""
pass
async def update_data(self, bot: Bot, key: StorageKey, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Update date in the storage for key (like dict.update)
:param bot: instance of the current bot
:param key: storage key
:param data: partial data
:return: new data
"""
current_data = await self.get_data(bot=bot, key=key)
current_data.update(data)
await self.set_data(bot=bot, key=key, data=current_data)
@ -49,4 +97,7 @@ class BaseStorage(ABC):
@abstractmethod
async def close(self) -> None: # pragma: no cover
"""
Close storage (database connection, file or etc.)
"""
pass

View file

@ -17,6 +17,15 @@ class MemoryStorageRecord:
class MemoryStorage(BaseStorage):
"""
Default FSM storage, stores all data in :class:`dict` and loss everything on shutdown
.. warning::
Is not recommended using in production in due to you will lose all data
when your bot restarts
"""
def __init__(self) -> None:
self.storage: DefaultDict[StorageKey, MemoryStorageRecord] = defaultdict(
MemoryStorageRecord

View file

@ -18,6 +18,13 @@ class KeyBuilder(ABC):
@abstractmethod
def build(self, key: StorageKey, part: Literal["data", "state", "lock"]) -> str:
"""
This method should be implemented in subclasses
:param key: contextual key
:param part: part of the record
:return: key to be used in Redis queries
"""
pass
@ -30,9 +37,21 @@ class DefaultKeyBuilder(KeyBuilder):
"""
def __init__(
self, prefix: str = "fsm", with_bot_id: bool = False, with_destiny: bool = False
self,
*,
prefix: str = "fsm",
separator: str = ":",
with_bot_id: bool = False,
with_destiny: bool = False,
) -> None:
"""
:param prefix: prefix for all records
:param separator: separator
:param with_bot_id: include Bot id in the key
:param with_destiny: include destiny key
"""
self.prefix = prefix
self.separator = separator
self.with_bot_id = with_bot_id
self.with_destiny = with_destiny
@ -44,10 +63,14 @@ class DefaultKeyBuilder(KeyBuilder):
if self.with_destiny:
parts.append(key.destiny)
parts.append(part)
return ":".join(parts)
return self.separator.join(parts)
class RedisStorage(BaseStorage):
"""
Redis storage required :code:`aioredis` package installed (:code:`pip install aioredis`)
"""
def __init__(
self,
redis: Redis,
@ -56,6 +79,13 @@ class RedisStorage(BaseStorage):
data_ttl: Optional[int] = None,
lock_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
"""
:param redis: Instance of Redis connection
:param key_builder: builder that helps to convert contextual key to string
:param state_ttl: TTL for state records
:param data_ttl: TTL for data records
:param lock_kwargs: Custom arguments for Redis lock
"""
if key_builder is None:
key_builder = DefaultKeyBuilder()
if lock_kwargs is None:
@ -70,6 +100,14 @@ class RedisStorage(BaseStorage):
def from_url(
cls, url: str, connection_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
) -> "RedisStorage":
"""
Create an instance of :class:`RedisStorage` with specifying the connection string
:param url: for example :code:`redis://user:password@host:port/db`
:param connection_kwargs: see :code:`aioredis` docs
:param kwargs: arguments to be passed to :class:`RedisStorage`
:return: an instance of :class:`RedisStorage`
"""
if connection_kwargs is None:
connection_kwargs = {}
pool = ConnectionPool.from_url(url, **connection_kwargs)

View file

@ -36,6 +36,12 @@ MAX_BUTTONS = 100
class KeyboardBuilder(Generic[ButtonType]):
"""
Generic keyboard builder that helps to adjust your markup with defined shape of lines.
Works both of InlineKeyboardMarkup and ReplyKeyboardMarkup.
"""
def __init__(
self, button_type: Type[ButtonType], markup: Optional[List[List[ButtonType]]] = None
) -> None:
@ -257,6 +263,10 @@ def repeat_last(items: Iterable[T]) -> Generator[T, None, None]:
class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
"""
Inline keyboard builder inherits all methods from generic builder
"""
if TYPE_CHECKING:
@no_type_check
@ -275,6 +285,7 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
...
def as_markup(self, **kwargs: Any) -> InlineKeyboardMarkup:
"""Construct an InlineKeyboardMarkup"""
...
def __init__(self, markup: Optional[List[List[InlineKeyboardButton]]] = None) -> None:
@ -290,6 +301,10 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]):
"""
Reply keyboard builder inherits all methods from generic builder
"""
if TYPE_CHECKING:
@no_type_check