policies.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. from abc import ABC, abstractmethod
  2. from typing import Optional
  3. from redis._parsers.commands import (
  4. CommandPolicies,
  5. CommandsParser,
  6. PolicyRecords,
  7. RequestPolicy,
  8. ResponsePolicy,
  9. )
  10. STATIC_POLICIES: PolicyRecords = {
  11. "ft": {
  12. "explaincli": CommandPolicies(
  13. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  14. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  15. ),
  16. "suglen": CommandPolicies(
  17. request_policy=RequestPolicy.DEFAULT_KEYED,
  18. response_policy=ResponsePolicy.DEFAULT_KEYED,
  19. ),
  20. "profile": CommandPolicies(
  21. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  22. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  23. ),
  24. "dropindex": CommandPolicies(
  25. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  26. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  27. ),
  28. "aliasupdate": CommandPolicies(
  29. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  30. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  31. ),
  32. "alter": CommandPolicies(
  33. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  34. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  35. ),
  36. "aggregate": CommandPolicies(
  37. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  38. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  39. ),
  40. "syndump": CommandPolicies(
  41. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  42. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  43. ),
  44. "create": CommandPolicies(
  45. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  46. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  47. ),
  48. "explain": CommandPolicies(
  49. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  50. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  51. ),
  52. "sugget": CommandPolicies(
  53. request_policy=RequestPolicy.DEFAULT_KEYED,
  54. response_policy=ResponsePolicy.DEFAULT_KEYED,
  55. ),
  56. "dictdel": CommandPolicies(
  57. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  58. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  59. ),
  60. "aliasadd": CommandPolicies(
  61. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  62. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  63. ),
  64. "dictadd": CommandPolicies(
  65. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  66. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  67. ),
  68. "synupdate": CommandPolicies(
  69. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  70. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  71. ),
  72. "drop": CommandPolicies(
  73. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  74. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  75. ),
  76. "info": CommandPolicies(
  77. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  78. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  79. ),
  80. "sugadd": CommandPolicies(
  81. request_policy=RequestPolicy.DEFAULT_KEYED,
  82. response_policy=ResponsePolicy.DEFAULT_KEYED,
  83. ),
  84. "dictdump": CommandPolicies(
  85. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  86. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  87. ),
  88. "cursor": CommandPolicies(
  89. request_policy=RequestPolicy.SPECIAL,
  90. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  91. ),
  92. "search": CommandPolicies(
  93. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  94. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  95. ),
  96. "tagvals": CommandPolicies(
  97. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  98. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  99. ),
  100. "aliasdel": CommandPolicies(
  101. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  102. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  103. ),
  104. "sugdel": CommandPolicies(
  105. request_policy=RequestPolicy.DEFAULT_KEYED,
  106. response_policy=ResponsePolicy.DEFAULT_KEYED,
  107. ),
  108. "spellcheck": CommandPolicies(
  109. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  110. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  111. ),
  112. },
  113. "core": {
  114. "command": CommandPolicies(
  115. request_policy=RequestPolicy.DEFAULT_KEYLESS,
  116. response_policy=ResponsePolicy.DEFAULT_KEYLESS,
  117. ),
  118. },
  119. }
  120. class PolicyResolver(ABC):
  121. @abstractmethod
  122. def resolve(self, command_name: str) -> Optional[CommandPolicies]:
  123. """
  124. Resolves the command name and determines the associated command policies.
  125. Args:
  126. command_name: The name of the command to resolve.
  127. Returns:
  128. CommandPolicies: The policies associated with the specified command.
  129. """
  130. pass
  131. @abstractmethod
  132. def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
  133. """
  134. Factory method to instantiate a policy resolver with a fallback resolver.
  135. Args:
  136. fallback: Fallback resolver
  137. Returns:
  138. PolicyResolver: Returns a new policy resolver with the specified fallback resolver.
  139. """
  140. pass
  141. class AsyncPolicyResolver(ABC):
  142. @abstractmethod
  143. async def resolve(self, command_name: str) -> Optional[CommandPolicies]:
  144. """
  145. Resolves the command name and determines the associated command policies.
  146. Args:
  147. command_name: The name of the command to resolve.
  148. Returns:
  149. CommandPolicies: The policies associated with the specified command.
  150. """
  151. pass
  152. @abstractmethod
  153. def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
  154. """
  155. Factory method to instantiate an async policy resolver with a fallback resolver.
  156. Args:
  157. fallback: Fallback resolver
  158. Returns:
  159. AsyncPolicyResolver: Returns a new policy resolver with the specified fallback resolver.
  160. """
  161. pass
  162. class BasePolicyResolver(PolicyResolver):
  163. """
  164. Base class for policy resolvers.
  165. """
  166. def __init__(
  167. self, policies: PolicyRecords, fallback: Optional[PolicyResolver] = None
  168. ) -> None:
  169. self._policies = policies
  170. self._fallback = fallback
  171. def resolve(self, command_name: str) -> Optional[CommandPolicies]:
  172. parts = command_name.split(".")
  173. if len(parts) > 2:
  174. raise ValueError(f"Wrong command or module name: {command_name}")
  175. module, command = parts if len(parts) == 2 else ("core", parts[0])
  176. if self._policies.get(module, None) is None:
  177. if self._fallback is not None:
  178. return self._fallback.resolve(command_name)
  179. else:
  180. return None
  181. if self._policies.get(module).get(command, None) is None:
  182. if self._fallback is not None:
  183. return self._fallback.resolve(command_name)
  184. else:
  185. return None
  186. return self._policies.get(module).get(command)
  187. @abstractmethod
  188. def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
  189. pass
  190. class AsyncBasePolicyResolver(AsyncPolicyResolver):
  191. """
  192. Async base class for policy resolvers.
  193. """
  194. def __init__(
  195. self, policies: PolicyRecords, fallback: Optional[AsyncPolicyResolver] = None
  196. ) -> None:
  197. self._policies = policies
  198. self._fallback = fallback
  199. async def resolve(self, command_name: str) -> Optional[CommandPolicies]:
  200. parts = command_name.split(".")
  201. if len(parts) > 2:
  202. raise ValueError(f"Wrong command or module name: {command_name}")
  203. module, command = parts if len(parts) == 2 else ("core", parts[0])
  204. if self._policies.get(module, None) is None:
  205. if self._fallback is not None:
  206. return await self._fallback.resolve(command_name)
  207. else:
  208. return None
  209. if self._policies.get(module).get(command, None) is None:
  210. if self._fallback is not None:
  211. return await self._fallback.resolve(command_name)
  212. else:
  213. return None
  214. return self._policies.get(module).get(command)
  215. @abstractmethod
  216. def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
  217. pass
  218. class DynamicPolicyResolver(BasePolicyResolver):
  219. """
  220. Resolves policy dynamically based on the COMMAND output.
  221. """
  222. def __init__(
  223. self, commands_parser: CommandsParser, fallback: Optional[PolicyResolver] = None
  224. ) -> None:
  225. """
  226. Parameters:
  227. commands_parser (CommandsParser): COMMAND output parser.
  228. fallback (Optional[PolicyResolver]): An optional resolver to be used when the
  229. primary policies cannot handle a specific request.
  230. """
  231. self._commands_parser = commands_parser
  232. super().__init__(commands_parser.get_command_policies(), fallback)
  233. def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
  234. return DynamicPolicyResolver(self._commands_parser, fallback)
  235. class StaticPolicyResolver(BasePolicyResolver):
  236. """
  237. Resolves policy from a static list of policy records.
  238. """
  239. def __init__(self, fallback: Optional[PolicyResolver] = None) -> None:
  240. """
  241. Parameters:
  242. fallback (Optional[PolicyResolver]): An optional fallback policy resolver
  243. used for resolving policies if static policies are inadequate.
  244. """
  245. super().__init__(STATIC_POLICIES, fallback)
  246. def with_fallback(self, fallback: "PolicyResolver") -> "PolicyResolver":
  247. return StaticPolicyResolver(fallback)
  248. class AsyncDynamicPolicyResolver(AsyncBasePolicyResolver):
  249. """
  250. Async version of DynamicPolicyResolver.
  251. """
  252. def __init__(
  253. self,
  254. policy_records: PolicyRecords,
  255. fallback: Optional[AsyncPolicyResolver] = None,
  256. ) -> None:
  257. """
  258. Parameters:
  259. policy_records (PolicyRecords): Policy records.
  260. fallback (Optional[AsyncPolicyResolver]): An optional resolver to be used when the
  261. primary policies cannot handle a specific request.
  262. """
  263. super().__init__(policy_records, fallback)
  264. def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
  265. return AsyncDynamicPolicyResolver(self._policies, fallback)
  266. class AsyncStaticPolicyResolver(AsyncBasePolicyResolver):
  267. """
  268. Async version of StaticPolicyResolver.
  269. """
  270. def __init__(self, fallback: Optional[AsyncPolicyResolver] = None) -> None:
  271. """
  272. Parameters:
  273. fallback (Optional[AsyncPolicyResolver]): An optional fallback policy resolver
  274. used for resolving policies if static policies are inadequate.
  275. """
  276. super().__init__(STATIC_POLICIES, fallback)
  277. def with_fallback(self, fallback: "AsyncPolicyResolver") -> "AsyncPolicyResolver":
  278. return AsyncStaticPolicyResolver(fallback)