Skip to content

Commit e92a062

Browse files
committed
Implmeent RAM Cache (partially)
1 parent e6c9562 commit e92a062

12 files changed

Lines changed: 561 additions & 424 deletions

File tree

helpful_modules/problems_module/AbstractKVCache.py

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import warnings
2828
import orjson
2929

30+
from . import OwnershipNotDeterminableException
3031
from ..FileDictionaryReader import AsyncFileDict
3132
from .appeal import Appeal, AppealViewInfo
3233
from .base_problem import BaseProblem
@@ -40,45 +41,37 @@
4041
from .quizzes import Quiz
4142
from .user_data import UserData
4243
from .verification_code_info import VerificationCodeInfo
43-
from .cache_ABC import AbstractCache
44+
from .cache_ABC import AbstractCache, TYPE_ERROR_NOT_FOUND
45+
from .parse_problem import convert_dict_to_problem
4446
MUST_IMPLEMENT_ERROR = NotImplementedError("Subclasses must implement this")
4547
GuildID = typing.Optional[int]
4648
T = typing.TypeVar('T', bound=IdentifiableDictConvertible)
4749

50+
PREFIX_REGISTRY = {
51+
"Quiz": Quiz,
52+
"UserData": UserData,
53+
"GuildData": GuildData,
54+
"VerificationCodeInfo": VerificationCodeInfo,
55+
"Appeal": Appeal,
56+
"AppealViewInfo": AppealViewInfo,
57+
"BaseProblem": convert_dict_to_problem, # this is because there are different types of problems that have to be parsed appropriately
58+
}
59+
4860
class AbstractKVBasedCache(AbstractCache, ABC):
4961
def __init__(self, *args, **kwargs) -> None:
5062
self._async_file_dict = AsyncFileDict("config.json")
5163

52-
@abstractmethod
53-
async def del_all_by_user_id(self, user_id: int) -> None:
54-
pass
55-
@abstractmethod
56-
async def delete_all_by_guild_id(self, guild_id: int) -> None:
57-
pass
5864

59-
@abstractmethod
60-
async def get_appeal_view_infos(self):
61-
"""
62-
Retrieve all appeal view information stored in Redis.
63-
64-
Yields:
65-
- AppealViewInfo: Each retrieved AppealViewInfo object.
66-
67-
Raises:
68-
- AppealViewInfoNotFound: If no appeal view information is found in Redis.
69-
- BaseExceptionGroup: If there are formatting exceptions during result processing.
70-
"""
71-
pass
72-
@abstractmethod
73-
async def get_all_appeals(self) -> list[Appeal]:
74-
"""Fetch all appeals from the database."""
75-
pass
7665

7766
@abstractmethod
7867
async def get_all_things(self) -> list[IdentifiableDictConvertible]:
7968
"""Return a list of EVERYTHING in the database"""
8069
pass
8170
@abstractmethod
71+
async def items(self) -> list[tuple[str, IdentifiableDictConvertible]]:
72+
"""Return a list of EVERYTHING in the database (and their keys)"""
73+
pass
74+
@abstractmethod
8275
async def add_thing(self, thing: IdentifiableDictConvertible) -> None:
8376
"""
8477
Adds a dictionary convertible object to the cache. If it is already in the cache, it will replace whatever is in there.
@@ -120,22 +113,62 @@ async def get_thing(
120113
"""
121114
pass
122115

116+
@property
117+
@abstractmethod
118+
def is_locked(self) -> bool:
119+
"""Return whether the cache is locked"""
120+
pass
121+
async def del_all_by_user_id(self, user_id: int) -> None:
122+
all_items = await self.items()
123+
for key, value in all_items:
124+
try:
125+
belongs = value.belongs_to_user(user_id)
126+
except OwnershipNotDeterminableException:
127+
continue
128+
if belongs:
129+
await self.remove_thing(key)
130+
@abstractmethod
131+
async def delete_all_by_guild_id(self, guild_id: int) -> None:
132+
all_items = await self.items()
133+
for key, value in all_items:
134+
try:
135+
belongs = value.belongs_to_guild(guild_id)
136+
except OwnershipNotDeterminableException:
137+
continue
138+
if belongs:
139+
await self.remove_thing(key)
140+
@abstractmethod
141+
async def get_all_items_starting_with(self, thing_start: str) -> list[tuple[str, IdentifiableDictConvertible]]:
142+
return list(filter(lambda tu: tu[0].startswith(thing_start), await self.items()))
123143
@abstractmethod
144+
async def get_appeal_view_infos(self) -> list[AppealViewInfo]:
145+
"""
146+
Retrieve all appeal view information stored in Redis.
147+
148+
Yields:
149+
- AppealViewInfo: Each retrieved AppealViewInfo object.
150+
151+
Raises:
152+
- AppealViewInfoNotFound: If no appeal view information is found in Redis.
153+
- BaseExceptionGroup: If there are formatting exceptions during result processing.
154+
"""
155+
warnings.warn("This is a slow method. Please consider overriding it.", category=FutureWarning)
156+
return [obj[1] for obj in await self.get_all_items_starting_with("AppealViewInfo") if isinstance(obj[1], AppealViewInfo)] # type: ignore
157+
async def get_all_appeals(self) -> list[Appeal]:
158+
"""Fetch all appeals from the database."""
159+
warnings.warn("This is a slow method. Please consider overriding it.", category=FutureWarning)
160+
return [obj[1] for obj in await self.get_all_items_starting_with("Appeal") if isinstance(obj[1], Appeal)] # type: ignore
161+
async def get_all_things_for_func(self, func: typing.Callable[[IdentifiableDictConvertible], bool]) -> List[IdentifiableDictConvertible]:
162+
return list(filter(func, await self.get_all_things()))
124163
async def get_all_problems(self) -> List[BaseProblem]:
125164
"""Return a list of all problems!
126165
Time complexity: O(N)"""
127166
warnings.warn(
128167
"There is a faster method to doing this, without a FULL scan of the database. Please override this method.",
129168
category=RuntimeWarning)
130-
ALL_OBJECTS = await self.get_all_things() # type: ignore
131-
ALL_PROBLEMS = list(filter(lambda problem: isinstance(problem, BaseProblem), ALL_OBJECTS)) # type: ignore
132-
return ALL_PROBLEMS
169+
return [convert_dict_to_problem(obj) for obj in await self.get_all_items_starting_with("BaseProblem")] # type: ignore
170+
133171

134-
@property
135-
@abstractmethod
136-
def is_locked(self) -> bool:
137-
"""Return whether the cache is locked"""
138-
pass
139172

140173
async def get_problem(self, guild_id: GuildID, problem_id: int) -> BaseProblem:
141174
"""Attempt to return the problem with guild_id and problem_id =problem_id
@@ -251,23 +284,12 @@ async def get_guild_data(self, guild_id: GuildID) -> GuildData:
251284
async def get_all_by_user_id(self, user_id: int) -> list[dict]:
252285
things = await self.get_all_things()
253286
things_authored = []
254-
for key, value in things.items():
287+
for thing in things:
255288
try:
256-
dictionarified = orjson.loads(value) # type: ignore
257-
except orjson.JSONDecodeError:
258-
raise FormatException("Something in the redis is not a dictionary..")
259-
if dictionarified is None:
260-
raise FormatException("No dictionary found")
261-
if dictionarified.get("author", None) == user_id:
262-
things_authored.append(value)
263-
continue
264-
elif user_id in dictionarified.get("authors", []):
265-
things_authored.append(value)
266-
continue
267-
elif dictionarified.get("user_id", None) == user_id:
268-
things_authored.append(value)
269-
continue
270-
289+
if thing.belongs_to_user(user_id):
290+
things_authored.append(thing)
291+
except OwnershipNotDeterminableException:
292+
pass
271293
return things_authored
272294

273295
async def bgsave(

helpful_modules/problems_module/GuildData/guild_data.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import disnake
2828

29+
from .. import OwnershipNotDeterminableException
2930
from ..dict_convertible import IdentifiableDictConvertible
3031
from ..errors import InvalidDictionaryInDatabaseException
3132
from .the_basic_check import CheckForUserPassage
@@ -199,4 +200,10 @@ def key(self) -> str:
199200
return self.key_of(guild_id=self.guild_id)
200201
@classmethod
201202
def key_of(cls, guild_id: int) -> str:
202-
return f"GuildData_{self.guild_id}"
203+
return f"GuildData_{guild_id}"
204+
def belongs_to_user(self, user_id: int):
205+
raise OwnershipNotDeterminableException("GuildDatas belong to guilds only")
206+
def belongs_to_guild(self, guild_id: int | None) -> bool:
207+
if not isinstance(guild_id, int):
208+
raise TypeError("guild_id is not an integer")
209+
return self.guild_id == guild_id

0 commit comments

Comments
 (0)