Experimental Discord bot written in Python
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. """
  2. Subclasses of list, set, and dict with special behaviors.
  3. """
  4. from abc import ABCMeta, abstractmethod
  5. from typing import Callable, Generic, Optional, TypeVar
  6. # Abstract collections
  7. # Dictionary key
  8. K = TypeVar('K')
  9. # Collection value
  10. V = TypeVar('V')
  11. # Element age
  12. A = TypeVar('A')
  13. # Age delta
  14. D = TypeVar('D')
  15. class AbstractMutableList(list[V], Generic[V], metaclass=ABCMeta):
  16. """
  17. Abstract list with hooks for custom logic before and after mutation
  18. operations.
  19. """
  20. @abstractmethod
  21. def pre_mutate(self):
  22. """
  23. Called just prior to any mutation operation.
  24. """
  25. raise NotImplementedError('subclass must implement pre_mutate')
  26. @abstractmethod
  27. def post_mutate(self):
  28. """
  29. Called just after any mutating operation before returning the result
  30. to the caller.
  31. """
  32. raise NotImplementedError('subclass must implement post_mutate')
  33. # Mutating method hooks
  34. def __delitem__(self, *args):
  35. self.pre_mutate()
  36. ret_val = super().__delitem__(*args)
  37. self.post_mutate()
  38. return ret_val
  39. def __iadd__(self, *args):
  40. self.pre_mutate()
  41. ret_val = super().__iadd__(*args)
  42. self.post_mutate()
  43. return ret_val
  44. def __imul__(self, *args):
  45. self.pre_mutate()
  46. ret_val = super().__imul__(*args)
  47. self.post_mutate()
  48. return ret_val
  49. def __setitem__(self, *args):
  50. self.pre_mutate()
  51. ret_val = super().__setitem__(*args)
  52. self.post_mutate()
  53. return ret_val
  54. def append(self, *args):
  55. self.pre_mutate()
  56. ret_val = super().append(*args)
  57. self.post_mutate()
  58. return ret_val
  59. def clear(self, *args):
  60. self.pre_mutate()
  61. ret_val = super().clear(*args)
  62. self.post_mutate()
  63. return ret_val
  64. def extend(self, *args):
  65. self.pre_mutate()
  66. ret_val = super().extend(*args)
  67. self.post_mutate()
  68. return ret_val
  69. def insert(self, *args):
  70. self.pre_mutate()
  71. ret_val = super().insert(*args)
  72. self.post_mutate()
  73. return ret_val
  74. def pop(self, *args):
  75. self.pre_mutate()
  76. ret_val = super().pop(*args)
  77. self.post_mutate()
  78. return ret_val
  79. def remove(self, *args):
  80. self.pre_mutate()
  81. ret_val = super().remove(*args)
  82. self.post_mutate()
  83. return ret_val
  84. def reverse(self, *args):
  85. self.pre_mutate()
  86. ret_val = super().reverse(*args)
  87. self.post_mutate()
  88. return ret_val
  89. def sort(self, *args):
  90. self.pre_mutate()
  91. ret_val = super().sort(*args)
  92. self.post_mutate()
  93. return ret_val
  94. class AbstractMutableSet(set[V], Generic[V], metaclass=ABCMeta):
  95. """
  96. Abstract set with hooks for custom logic before and after mutation
  97. operations.
  98. """
  99. @abstractmethod
  100. def pre_mutate(self):
  101. """
  102. Called just prior to any mutation operation.
  103. """
  104. raise NotImplementedError('subclass must implement pre_mutate')
  105. @abstractmethod
  106. def post_mutate(self):
  107. """
  108. Called just after any mutating operation before returning the result
  109. to the caller.
  110. """
  111. raise NotImplementedError('subclass must implement post_mutate')
  112. # Mutating method hooks
  113. def __iand__(self, *args):
  114. self.pre_mutate()
  115. ret_val = super().__iand__(*args)
  116. self.post_mutate()
  117. return ret_val
  118. def __ior__(self, *args):
  119. self.pre_mutate()
  120. ret_val = super().__ior__(*args)
  121. self.post_mutate()
  122. return ret_val
  123. def __isub__(self, *args):
  124. self.pre_mutate()
  125. ret_val = super().__isub__(*args)
  126. self.post_mutate()
  127. return ret_val
  128. def __ixor__(self, *args):
  129. self.pre_mutate()
  130. ret_val = super().__ixor__(*args)
  131. self.post_mutate()
  132. return ret_val
  133. def add(self, *args):
  134. self.pre_mutate()
  135. ret_val = super().add(*args)
  136. self.post_mutate()
  137. return ret_val
  138. def clear(self, *args):
  139. self.pre_mutate()
  140. ret_val = super().clear(*args)
  141. self.post_mutate()
  142. return ret_val
  143. def difference_update(self, *args):
  144. self.pre_mutate()
  145. ret_val = super().difference_update(*args)
  146. self.post_mutate()
  147. return ret_val
  148. def discard(self, *args):
  149. self.pre_mutate()
  150. ret_val = super().discard(*args)
  151. self.post_mutate()
  152. return ret_val
  153. def intersection_update(self, *args):
  154. self.pre_mutate()
  155. ret_val = super().intersection_update(*args)
  156. self.post_mutate()
  157. return ret_val
  158. def pop(self, *args):
  159. self.pre_mutate()
  160. ret_val = super().pop(*args)
  161. self.post_mutate()
  162. return ret_val
  163. def remove(self, *args):
  164. self.pre_mutate()
  165. ret_val = super().remove(*args)
  166. self.post_mutate()
  167. return ret_val
  168. def symmetric_difference_update(self, *args):
  169. self.pre_mutate()
  170. ret_val = super().symmetric_difference_update(*args)
  171. self.post_mutate()
  172. return ret_val
  173. def update(self, *args):
  174. self.pre_mutate()
  175. ret_val = super().update(*args)
  176. self.post_mutate()
  177. return ret_val
  178. class AbstractMutableDict(dict[K, V], Generic[K, V], metaclass=ABCMeta):
  179. """
  180. Abstract dict with hooks for custom logic before and after mutation
  181. operations.
  182. """
  183. @abstractmethod
  184. def pre_mutate(self):
  185. """
  186. Called just prior to any mutation operation.
  187. """
  188. raise NotImplementedError('subclass must implement pre_mutate')
  189. @abstractmethod
  190. def post_mutate(self):
  191. """
  192. Called just after any mutating operation before returning the result
  193. to the caller.
  194. """
  195. raise NotImplementedError('subclass must implement post_mutate')
  196. # Mutating method hooks
  197. def __delitem__(self, *args):
  198. self.pre_mutate()
  199. ret_val = super().__delitem__(*args)
  200. self.post_mutate()
  201. return ret_val
  202. def __ior__(self, *args):
  203. self.pre_mutate()
  204. ret_val = super().__ior__(*args)
  205. self.post_mutate()
  206. return ret_val
  207. def __setitem__(self, *args):
  208. self.pre_mutate()
  209. ret_val = super().__setitem__(*args)
  210. self.post_mutate()
  211. return ret_val
  212. def clear(self, *args):
  213. self.pre_mutate()
  214. ret_val = super().clear(*args)
  215. self.post_mutate()
  216. return ret_val
  217. def pop(self, *args):
  218. self.pre_mutate()
  219. ret_val = super().pop(*args)
  220. self.post_mutate()
  221. return ret_val
  222. def popitem(self, *args):
  223. self.pre_mutate()
  224. ret_val = super().popitem(*args)
  225. self.post_mutate()
  226. return ret_val
  227. def update(self, *args):
  228. self.pre_mutate()
  229. ret_val = super().update(*args)
  230. self.post_mutate()
  231. return ret_val
  232. # Collections with limited number of elements
  233. class SizeBoundList(AbstractMutableList[V], Generic[V, A]):
  234. """
  235. Subclass of `list` that enforces a maximum number of elements.
  236. If the number of elements following a mutating operation exceeds
  237. `self.max_element_count`, then each element will be tested for its "age,"
  238. and the "oldest" elements will be removed until the total size is back
  239. within the limit. "Age" is determined via a provided lambda function. It is
  240. a value with arbitrary numeric value that is used comparatively to find
  241. the element with the smallest value.
  242. The `element_age` lambda takes two arguments: the element index and the
  243. element value. It must return values that can be compared to one another
  244. (e.g. int, float, datetime).
  245. `self.element_age` and `self.max_element_count` can be modified at runtime,
  246. however elements will only be discarded following the next mutating
  247. operation. Call `self.purge_old_elements()` to force resizing.
  248. """
  249. def __init__(self,
  250. max_element_count: int,
  251. element_age: Callable[[int, V], A],
  252. *args, **kwargs):
  253. super().__init__(*args, **kwargs)
  254. self.element_age = element_age
  255. self.max_element_count = max_element_count
  256. self.is_culling = False
  257. self.post_mutate()
  258. def purge_old_elements(self):
  259. """
  260. Manually purges elements. Purging usually occurs automatically, but
  261. this can be called if `self.max_element_count` or `self.element_age`
  262. are modified at runtime.
  263. """
  264. self.post_mutate()
  265. def pre_mutate(self):
  266. pass
  267. def post_mutate(self):
  268. if self.is_culling:
  269. return
  270. self.is_culling = True
  271. while len(self) > self.max_element_count:
  272. oldest_age = None
  273. oldest_index = -1
  274. for i, elem in enumerate(self):
  275. age = self.element_age(i, elem)
  276. if oldest_age is None or age < oldest_age:
  277. oldest_age = age
  278. oldest_index = i
  279. self.pop(oldest_index)
  280. self.is_culling = False
  281. def copy(self):
  282. return SizeBoundList(self.max_element_count, self.element_age, super())
  283. class SizeBoundSet(AbstractMutableSet[V], Generic[V, A]):
  284. """
  285. Subclass of `set` that enforces a maximum number of elements.
  286. If the number of elements following a mutating operation exceeds
  287. `self.max_element_count`, then each element will be tested for its "age,"
  288. and the "oldest" elements will be removed until the total size is back
  289. within the limit. "Age" is determined via a provided lambda function. It is
  290. a value with arbitrary numeric value that is used comparatively to find
  291. the element with the smallest value.
  292. The `element_age` lambda takes one argument: the element value. It must
  293. return values that can be compared to one another (e.g. int, float,
  294. datetime).
  295. `self.element_age` and `self.max_element_count` can be modified at runtime,
  296. however elements will only be discarded following the next mutating
  297. operation. Call `self.purge_old_elements()` to force resizing.
  298. """
  299. def __init__(self,
  300. max_element_count: int,
  301. element_age: Callable[[int, V], A],
  302. *args, **kwargs):
  303. super().__init__(*args, **kwargs)
  304. self.element_age = element_age
  305. self.max_element_count = max_element_count
  306. self.is_culling = False
  307. self.post_mutate()
  308. def purge_old_elements(self):
  309. """
  310. Manually purges elements. Purging usually occurs automatically, but
  311. this can be called if `self.max_element_count` or `self.element_age`
  312. are modified at runtime.
  313. """
  314. self.post_mutate()
  315. def pre_mutate(self):
  316. pass
  317. def post_mutate(self):
  318. if self.is_culling:
  319. return
  320. self.is_culling = True
  321. while len(self) > self.max_element_count:
  322. oldest_age = None
  323. oldest_elem = None
  324. for elem in self:
  325. age = self.element_age(elem)
  326. if oldest_age is None or age < oldest_age:
  327. oldest_age = age
  328. oldest_elem = elem
  329. self.remove(oldest_elem)
  330. self.is_culling = False
  331. def copy(self):
  332. return SizeBoundSet(self.max_element_count, self.element_age, super())
  333. class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A]):
  334. """
  335. Subclass of `dict` that enforces a maximum number of elements.
  336. If the number of elements following a mutating operation exceeds
  337. `self.max_element_count`, then each element will be tested for its "age,"
  338. and the "oldest" elements will be removed until the total size is back
  339. within the limit. "Age" is determined via a provided lambda function. It is
  340. a value with arbitrary numeric value that is used comparatively to find
  341. the element with the smallest value.
  342. The `element_age` lambda takes two arguments: the key and the value of a
  343. dict pair. It must return values that can be compared to one another (e.g.
  344. int, float, datetime).
  345. `self.element_age` and `self.max_element_count` can be modified at runtime,
  346. however elements will only be discarded following the next mutating
  347. operation. Call `self.purge_old_elements()` to force resizing.
  348. """
  349. def __init__(self,
  350. max_element_count: int,
  351. element_age: Callable[[K, V], A],
  352. *args, **kwargs):
  353. super().__init__(*args, **kwargs)
  354. self.element_age = element_age
  355. self.max_element_count = max_element_count
  356. self.is_culling = False
  357. self.post_mutate()
  358. def purge_old_elements(self):
  359. """
  360. Manually purges elements. Purging usually occurs automatically, but
  361. this can be called if `self.max_element_count` or `self.element_age`
  362. are modified at runtime.
  363. """
  364. self.post_mutate()
  365. def pre_mutate(self):
  366. pass
  367. def post_mutate(self):
  368. if self.is_culling:
  369. return
  370. self.is_culling = True
  371. while len(self) > self.max_element_count:
  372. oldest_age = None
  373. oldest_key = None
  374. for key, value in self.items():
  375. age = self.element_age(key, value)
  376. if oldest_age is None or age < oldest_age:
  377. oldest_age = age
  378. oldest_key = key
  379. del self[oldest_key]
  380. self.is_culling = False
  381. def copy(self):
  382. return SizeBoundDict(self.max_element_count, self.element_age, super())
  383. # Collections with limited age of elements
  384. class AgeBoundList(AbstractMutableList[V], Generic[V, A, D]):
  385. """
  386. Subclass of `list` that enforces a maximum "age" of elements.
  387. After each mutating operation, the minimum and maximum "age" of the elements
  388. are determined. If the span between the newest and oldest exceeds
  389. `self.max_age` then the oldest elements will be purged until that is
  390. no longer the case. "Age" is determined via a provided lambda function. It
  391. is a value with arbitrary numeric value that is used comparatively.
  392. The `element_age` lambda takes two arguments: the element index and the
  393. element value. It must return values that can be compared to one another
  394. and be added and subtracted (e.g. int, float, datetime). If the lambda
  395. returns `datetime`s, the `max_age` should be a `timedelta`.
  396. `self.element_age` and `self.max_age` can be modified at runtime,
  397. however elements will only be discarded following the next mutating
  398. operation. Call `self.purge_old_elements()` to force resizing.
  399. """
  400. def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
  401. super().__init__(*args, **kwargs)
  402. self.max_age = max_age
  403. self.element_age = element_age
  404. self.is_culling = False
  405. def purge_old_elements(self):
  406. """
  407. Manually purges elements. Purging usually occurs automatically, but
  408. this can be called if `self.max_age` or `self.element_age` are modified
  409. at runtime.
  410. """
  411. self.post_mutate()
  412. def pre_mutate(self):
  413. pass
  414. def post_mutate(self):
  415. if self.is_culling or len(self) <= 1:
  416. return
  417. self.is_culling = True
  418. min_age: Optional[A] = None
  419. max_age: Optional[A] = None
  420. ages: dict[int, A] = {}
  421. for i, elem in enumerate(self):
  422. age: A = self.element_age(i, elem)
  423. ages[i] = age
  424. if min_age is None or age < min_age:
  425. min_age = age
  426. if max_age is None or age > max_age:
  427. max_age = age
  428. cutoff: A = max_age - self.max_age
  429. if min_age >= cutoff:
  430. self.is_culling = False
  431. return
  432. for i in reversed(range(len(self))):
  433. if ages[i] < cutoff:
  434. del self[i]
  435. self.is_culling = False
  436. def copy(self):
  437. return AgeBoundList(self.max_age, self.element_age, super())
  438. class AgeBoundSet(AbstractMutableSet[V], Generic[V, A, D]):
  439. """
  440. Subclass of `set` that enforces a maximum "age" of elements.
  441. After each mutating operation, the minimum and maximum "age" of the elements
  442. are determined. If the span between the newest and oldest exceeds
  443. `self.max_age` then the oldest elements will be purged until that is
  444. no longer the case. "Age" is determined via a provided lambda function. It
  445. is a value with arbitrary numeric value that is used comparatively.
  446. The `element_age` lambda takes one argument: the element value. It must
  447. return values that can be compared to one another and be added and
  448. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  449. the `max_age` should be a `timedelta`.
  450. `self.element_age` and `self.max_age` can be modified at runtime,
  451. however elements will only be discarded following the next mutating
  452. operation. Call `self.purge_old_elements()` to force resizing.
  453. """
  454. def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
  455. super().__init__(*args, **kwargs)
  456. self.max_age = max_age
  457. self.element_age = element_age
  458. self.is_culling = False
  459. def purge_old_elements(self):
  460. """
  461. Manually purges elements. Purging usually occurs automatically, but
  462. this can be called if `self.max_age` or `self.element_age` are modified
  463. at runtime.
  464. """
  465. self.post_mutate()
  466. def pre_mutate(self):
  467. pass
  468. def post_mutate(self):
  469. if self.is_culling or len(self) <= 1:
  470. return
  471. self.is_culling = True
  472. min_age = None
  473. max_age = None
  474. ages = {}
  475. for elem in self:
  476. age = self.element_age(elem)
  477. ages[elem] = age
  478. if min_age is None or age < min_age:
  479. min_age = age
  480. if max_age is None or age > max_age:
  481. max_age = age
  482. cutoff = max_age - self.max_age
  483. if min_age >= cutoff:
  484. self.is_culling = False
  485. return
  486. for elem, age in ages.items():
  487. if age < cutoff:
  488. self.remove(elem)
  489. self.is_culling = False
  490. def copy(self):
  491. return AgeBoundSet(self.max_age, self.element_age, super())
  492. class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A, D]):
  493. """
  494. Subclass of `dict` that enforces a maximum "age" of elements.
  495. After each mutating operation, the minimum and maximum "age" of the elements
  496. are determined. If the span between the newest and oldest exceeds
  497. `self.max_age` then the oldest elements will be purged until that is
  498. no longer the case. "Age" is determined via a provided lambda function. It
  499. is a value with arbitrary numeric value that is used comparatively.
  500. The `element_age` lambda takes two arguments: the key and value of a pair.
  501. It must return values that can be compared to one another and be added and
  502. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  503. the `max_age` should be a `timedelta`.
  504. `self.element_age` and `self.max_age` can be modified at runtime,
  505. however elements will only be discarded following the next mutating
  506. operation. Call `self.purge_old_elements()` to force resizing.
  507. """
  508. def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
  509. super().__init__(*args, **kwargs)
  510. self.max_age = max_age
  511. self.element_age = element_age
  512. self.is_culling = False
  513. def purge_old_elements(self):
  514. """
  515. Manually purges elements. Purging usually occurs automatically, but
  516. this can be called if `self.max_age` or `self.element_age` are modified
  517. at runtime.
  518. """
  519. self.post_mutate()
  520. def pre_mutate(self):
  521. pass
  522. def post_mutate(self):
  523. if self.is_culling or len(self) <= 1:
  524. return
  525. self.is_culling = True
  526. min_age = None
  527. max_age = None
  528. ages = {}
  529. for key, value in self.items():
  530. age = self.element_age(key, value)
  531. ages[key] = age
  532. if min_age is None or age < min_age:
  533. min_age = age
  534. if max_age is None or age > max_age:
  535. max_age = age
  536. cutoff = max_age - self.max_age
  537. if min_age >= cutoff:
  538. self.is_culling = False
  539. return
  540. for key, age in ages.items():
  541. if age < cutoff:
  542. del self[key]
  543. self.is_culling = False
  544. def copy(self):
  545. return AgeBoundDict(self.max_age, self.element_age, super())