zulip-bots: Implement context manager.
The context manager is implemented based on a newly added storage called CachedStorage. It is a bufferred storage that doesn't sync with the database until it's flushed. With CachedStorage, we can implement the context manager easily by loading all the data on __enter__ and just flush all the modified (dirty) data on __exit__. This approach can help the user minimize the number of round-trips to the server almost invisibly (despite the fact that they need to use it with "with"). Fixes: #679
This commit is contained in:
parent
e0723c1db4
commit
86fa9f5e35
|
@ -8,7 +8,8 @@ import time
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
from typing import Any, Optional, List, Dict, IO, Text
|
from contextlib import contextmanager
|
||||||
|
from typing import Any, Iterator, Optional, List, Dict, IO, Set, Text
|
||||||
from typing_extensions import Protocol
|
from typing_extensions import Protocol
|
||||||
from zulip import Client, ZulipError
|
from zulip import Client, ZulipError
|
||||||
|
|
||||||
|
@ -83,6 +84,50 @@ class BotStorage(Protocol):
|
||||||
def contains(self, key: Text) -> bool:
|
def contains(self, key: Text) -> bool:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class CachedStorage:
|
||||||
|
def __init__(self, parent_storage: BotStorage, init_data: Dict[str, Any]) -> None:
|
||||||
|
# CachedStorage is implemented solely for the context manager of any BotHandler.
|
||||||
|
# It has a parent_storage that is responsible of communicating with the database
|
||||||
|
# 1. when certain data is not cached;
|
||||||
|
# 2. when the data need to be flushed to the database.
|
||||||
|
# It can be initialized with the given data.
|
||||||
|
self._parent_storage = parent_storage
|
||||||
|
self._cache = init_data
|
||||||
|
self._dirty_keys: Set[str] = set()
|
||||||
|
|
||||||
|
def put(self, key: Text, value: Any) -> None:
|
||||||
|
# In the cached storage, values being put to the storage is not flushed to the parent storage.
|
||||||
|
# It will be marked dirty until it get flushed.
|
||||||
|
self._cache[key] = value
|
||||||
|
self._dirty_keys.add(key)
|
||||||
|
|
||||||
|
def get(self, key: Text) -> Any:
|
||||||
|
# Unless the key is not found in the cache, the cached storage will not lookup the parent storage.
|
||||||
|
if key in self._cache:
|
||||||
|
return self._cache[key]
|
||||||
|
else:
|
||||||
|
value = self._parent_storage.get(key)
|
||||||
|
self._cache[key] = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
# Flush the data to the parent storage.
|
||||||
|
# Data that are not marked dirty will be omitted.
|
||||||
|
# This should be manually called when CachedStorage is not used with a context manager.
|
||||||
|
while len(self._dirty_keys) > 0:
|
||||||
|
key = self._dirty_keys.pop()
|
||||||
|
self._parent_storage.put(key, self._cache[key])
|
||||||
|
|
||||||
|
def flush_one(self, key: Text) -> None:
|
||||||
|
self._dirty_keys.remove(key)
|
||||||
|
self._parent_storage.put(key, self._cache[key])
|
||||||
|
|
||||||
|
def contains(self, key: Text) -> bool:
|
||||||
|
if key in self._cache:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return self._parent_storage.contains(key)
|
||||||
|
|
||||||
class StateHandler:
|
class StateHandler:
|
||||||
def __init__(self, client: Client) -> None:
|
def __init__(self, client: Client) -> None:
|
||||||
self._client = client
|
self._client = client
|
||||||
|
@ -111,6 +156,16 @@ class StateHandler:
|
||||||
def contains(self, key: Text) -> bool:
|
def contains(self, key: Text) -> bool:
|
||||||
return key in self.state_
|
return key in self.state_
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def use_storage(storage: BotStorage, keys: List[Text]) -> Iterator[BotStorage]:
|
||||||
|
# The context manager for StateHandler that minimizes the number of round-trips to the server.
|
||||||
|
# It will fetch all the data using the specified keys and store them to
|
||||||
|
# a CachedStorage that will not communicate with the server until manually
|
||||||
|
# calling flush or getting some values that are not previously fetched.
|
||||||
|
data = {key: storage.get(key) for key in keys}
|
||||||
|
cache = CachedStorage(storage, data)
|
||||||
|
yield storage
|
||||||
|
cache.flush()
|
||||||
|
|
||||||
class BotHandler(Protocol):
|
class BotHandler(Protocol):
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue