|
27 | 27 | import warnings |
28 | 28 | import orjson |
29 | 29 |
|
| 30 | +from . import OwnershipNotDeterminableException |
30 | 31 | from ..FileDictionaryReader import AsyncFileDict |
31 | 32 | from .appeal import Appeal, AppealViewInfo |
32 | 33 | from .base_problem import BaseProblem |
|
40 | 41 | from .quizzes import Quiz |
41 | 42 | from .user_data import UserData |
42 | 43 | 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 |
44 | 46 | MUST_IMPLEMENT_ERROR = NotImplementedError("Subclasses must implement this") |
45 | 47 | GuildID = typing.Optional[int] |
46 | 48 | T = typing.TypeVar('T', bound=IdentifiableDictConvertible) |
47 | 49 |
|
| 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 | + |
48 | 60 | class AbstractKVBasedCache(AbstractCache, ABC): |
49 | 61 | def __init__(self, *args, **kwargs) -> None: |
50 | 62 | self._async_file_dict = AsyncFileDict("config.json") |
51 | 63 |
|
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 |
58 | 64 |
|
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 |
76 | 65 |
|
77 | 66 | @abstractmethod |
78 | 67 | async def get_all_things(self) -> list[IdentifiableDictConvertible]: |
79 | 68 | """Return a list of EVERYTHING in the database""" |
80 | 69 | pass |
81 | 70 | @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 |
82 | 75 | async def add_thing(self, thing: IdentifiableDictConvertible) -> None: |
83 | 76 | """ |
84 | 77 | 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( |
120 | 113 | """ |
121 | 114 | pass |
122 | 115 |
|
| 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())) |
123 | 143 | @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())) |
124 | 163 | async def get_all_problems(self) -> List[BaseProblem]: |
125 | 164 | """Return a list of all problems! |
126 | 165 | Time complexity: O(N)""" |
127 | 166 | warnings.warn( |
128 | 167 | "There is a faster method to doing this, without a FULL scan of the database. Please override this method.", |
129 | 168 | 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 | + |
133 | 171 |
|
134 | | - @property |
135 | | - @abstractmethod |
136 | | - def is_locked(self) -> bool: |
137 | | - """Return whether the cache is locked""" |
138 | | - pass |
139 | 172 |
|
140 | 173 | async def get_problem(self, guild_id: GuildID, problem_id: int) -> BaseProblem: |
141 | 174 | """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: |
251 | 284 | async def get_all_by_user_id(self, user_id: int) -> list[dict]: |
252 | 285 | things = await self.get_all_things() |
253 | 286 | things_authored = [] |
254 | | - for key, value in things.items(): |
| 287 | + for thing in things: |
255 | 288 | 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 |
271 | 293 | return things_authored |
272 | 294 |
|
273 | 295 | async def bgsave( |
|
0 commit comments