datastructures.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. # -*- coding: utf-8 -*-
  2. """
  3. ``celery.datastructures``
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~
  5. Custom types and data-structures.
  6. """
  7. from __future__ import absolute_import, print_function, unicode_literals
  8. import sys
  9. import time
  10. from collections import (
  11. Callable, Mapping, MutableMapping, MutableSet, defaultdict,
  12. )
  13. from heapq import heapify, heappush, heappop
  14. from itertools import chain
  15. from billiard.einfo import ExceptionInfo # noqa
  16. from kombu.utils.encoding import safe_str, bytes_to_str
  17. from kombu.utils.limits import TokenBucket # noqa
  18. from celery.five import items, python_2_unicode_compatible, values
  19. from celery.utils.functional import LRUCache, first, uniq # noqa
  20. from celery.utils.text import match_case
  21. try:
  22. from django.utils.functional import LazyObject, LazySettings
  23. except ImportError:
  24. class LazyObject(object): # noqa
  25. pass
  26. LazySettings = LazyObject # noqa
  27. __all__ = [
  28. 'GraphFormatter', 'CycleError', 'DependencyGraph',
  29. 'AttributeDictMixin', 'AttributeDict', 'DictAttribute',
  30. 'ConfigurationView', 'LimitedSet',
  31. ]
  32. DOT_HEAD = """
  33. {IN}{type} {id} {{
  34. {INp}graph [{attrs}]
  35. """
  36. DOT_ATTR = '{name}={value}'
  37. DOT_NODE = '{INp}"{0}" [{attrs}]'
  38. DOT_EDGE = '{INp}"{0}" {dir} "{1}" [{attrs}]'
  39. DOT_ATTRSEP = ', '
  40. DOT_DIRS = {'graph': '--', 'digraph': '->'}
  41. DOT_TAIL = '{IN}}}'
  42. REPR_LIMITED_SET = """\
  43. <{name}({size}): maxlen={0.maxlen}, expires={0.expires}, minlen={0.minlen}>\
  44. """
  45. def force_mapping(m):
  46. if isinstance(m, (LazyObject, LazySettings)):
  47. m = m._wrapped
  48. return DictAttribute(m) if not isinstance(m, Mapping) else m
  49. class GraphFormatter(object):
  50. _attr = DOT_ATTR.strip()
  51. _node = DOT_NODE.strip()
  52. _edge = DOT_EDGE.strip()
  53. _head = DOT_HEAD.strip()
  54. _tail = DOT_TAIL.strip()
  55. _attrsep = DOT_ATTRSEP
  56. _dirs = dict(DOT_DIRS)
  57. scheme = {
  58. 'shape': 'box',
  59. 'arrowhead': 'vee',
  60. 'style': 'filled',
  61. 'fontname': 'HelveticaNeue',
  62. }
  63. edge_scheme = {
  64. 'color': 'darkseagreen4',
  65. 'arrowcolor': 'black',
  66. 'arrowsize': 0.7,
  67. }
  68. node_scheme = {'fillcolor': 'palegreen3', 'color': 'palegreen4'}
  69. term_scheme = {'fillcolor': 'palegreen1', 'color': 'palegreen2'}
  70. graph_scheme = {'bgcolor': 'mintcream'}
  71. def __init__(self, root=None, type=None, id=None,
  72. indent=0, inw=' ' * 4, **scheme):
  73. self.id = id or 'dependencies'
  74. self.root = root
  75. self.type = type or 'digraph'
  76. self.direction = self._dirs[self.type]
  77. self.IN = inw * (indent or 0)
  78. self.INp = self.IN + inw
  79. self.scheme = dict(self.scheme, **scheme)
  80. self.graph_scheme = dict(self.graph_scheme, root=self.label(self.root))
  81. def attr(self, name, value):
  82. value = '"{0}"'.format(value)
  83. return self.FMT(self._attr, name=name, value=value)
  84. def attrs(self, d, scheme=None):
  85. d = dict(self.scheme, **dict(scheme, **d or {}) if scheme else d)
  86. return self._attrsep.join(
  87. safe_str(self.attr(k, v)) for k, v in items(d)
  88. )
  89. def head(self, **attrs):
  90. return self.FMT(
  91. self._head, id=self.id, type=self.type,
  92. attrs=self.attrs(attrs, self.graph_scheme),
  93. )
  94. def tail(self):
  95. return self.FMT(self._tail)
  96. def label(self, obj):
  97. return obj
  98. def node(self, obj, **attrs):
  99. return self.draw_node(obj, self.node_scheme, attrs)
  100. def terminal_node(self, obj, **attrs):
  101. return self.draw_node(obj, self.term_scheme, attrs)
  102. def edge(self, a, b, **attrs):
  103. return self.draw_edge(a, b, **attrs)
  104. def _enc(self, s):
  105. return s.encode('utf-8', 'ignore')
  106. def FMT(self, fmt, *args, **kwargs):
  107. return self._enc(fmt.format(
  108. *args, **dict(kwargs, IN=self.IN, INp=self.INp)
  109. ))
  110. def draw_edge(self, a, b, scheme=None, attrs=None):
  111. return self.FMT(
  112. self._edge, self.label(a), self.label(b),
  113. dir=self.direction, attrs=self.attrs(attrs, self.edge_scheme),
  114. )
  115. def draw_node(self, obj, scheme=None, attrs=None):
  116. return self.FMT(
  117. self._node, self.label(obj), attrs=self.attrs(attrs, scheme),
  118. )
  119. class CycleError(Exception):
  120. """A cycle was detected in an acyclic graph."""
  121. @python_2_unicode_compatible
  122. class DependencyGraph(object):
  123. """A directed acyclic graph of objects and their dependencies.
  124. Supports a robust topological sort
  125. to detect the order in which they must be handled.
  126. Takes an optional iterator of ``(obj, dependencies)``
  127. tuples to build the graph from.
  128. .. warning::
  129. Does not support cycle detection.
  130. """
  131. def __init__(self, it=None, formatter=None):
  132. self.formatter = formatter or GraphFormatter()
  133. self.adjacent = {}
  134. if it is not None:
  135. self.update(it)
  136. def add_arc(self, obj):
  137. """Add an object to the graph."""
  138. self.adjacent.setdefault(obj, [])
  139. def add_edge(self, A, B):
  140. """Add an edge from object ``A`` to object ``B``
  141. (``A`` depends on ``B``)."""
  142. self[A].append(B)
  143. def connect(self, graph):
  144. """Add nodes from another graph."""
  145. self.adjacent.update(graph.adjacent)
  146. def topsort(self):
  147. """Sort the graph topologically.
  148. :returns: a list of objects in the order
  149. in which they must be handled.
  150. """
  151. graph = DependencyGraph()
  152. components = self._tarjan72()
  153. NC = {
  154. node: component for component in components for node in component
  155. }
  156. for component in components:
  157. graph.add_arc(component)
  158. for node in self:
  159. node_c = NC[node]
  160. for successor in self[node]:
  161. successor_c = NC[successor]
  162. if node_c != successor_c:
  163. graph.add_edge(node_c, successor_c)
  164. return [t[0] for t in graph._khan62()]
  165. def valency_of(self, obj):
  166. """Return the valency (degree) of a vertex in the graph."""
  167. try:
  168. l = [len(self[obj])]
  169. except KeyError:
  170. return 0
  171. for node in self[obj]:
  172. l.append(self.valency_of(node))
  173. return sum(l)
  174. def update(self, it):
  175. """Update the graph with data from a list
  176. of ``(obj, dependencies)`` tuples."""
  177. tups = list(it)
  178. for obj, _ in tups:
  179. self.add_arc(obj)
  180. for obj, deps in tups:
  181. for dep in deps:
  182. self.add_edge(obj, dep)
  183. def edges(self):
  184. """Return generator that yields for all edges in the graph."""
  185. return (obj for obj, adj in items(self) if adj)
  186. def _khan62(self):
  187. """Khans simple topological sort algorithm from '62
  188. See https://en.wikipedia.org/wiki/Topological_sorting
  189. """
  190. count = defaultdict(lambda: 0)
  191. result = []
  192. for node in self:
  193. for successor in self[node]:
  194. count[successor] += 1
  195. ready = [node for node in self if not count[node]]
  196. while ready:
  197. node = ready.pop()
  198. result.append(node)
  199. for successor in self[node]:
  200. count[successor] -= 1
  201. if count[successor] == 0:
  202. ready.append(successor)
  203. result.reverse()
  204. return result
  205. def _tarjan72(self):
  206. """Tarjan's algorithm to find strongly connected components.
  207. See http://bit.ly/vIMv3h.
  208. """
  209. result, stack, low = [], [], {}
  210. def visit(node):
  211. if node in low:
  212. return
  213. num = len(low)
  214. low[node] = num
  215. stack_pos = len(stack)
  216. stack.append(node)
  217. for successor in self[node]:
  218. visit(successor)
  219. low[node] = min(low[node], low[successor])
  220. if num == low[node]:
  221. component = tuple(stack[stack_pos:])
  222. stack[stack_pos:] = []
  223. result.append(component)
  224. for item in component:
  225. low[item] = len(self)
  226. for node in self:
  227. visit(node)
  228. return result
  229. def to_dot(self, fh, formatter=None):
  230. """Convert the graph to DOT format.
  231. :param fh: A file, or a file-like object to write the graph to.
  232. """
  233. seen = set()
  234. draw = formatter or self.formatter
  235. def P(s):
  236. print(bytes_to_str(s), file=fh)
  237. def if_not_seen(fun, obj):
  238. if draw.label(obj) not in seen:
  239. P(fun(obj))
  240. seen.add(draw.label(obj))
  241. P(draw.head())
  242. for obj, adjacent in items(self):
  243. if not adjacent:
  244. if_not_seen(draw.terminal_node, obj)
  245. for req in adjacent:
  246. if_not_seen(draw.node, obj)
  247. P(draw.edge(obj, req))
  248. P(draw.tail())
  249. def format(self, obj):
  250. return self.formatter(obj) if self.formatter else obj
  251. def __iter__(self):
  252. return iter(self.adjacent)
  253. def __getitem__(self, node):
  254. return self.adjacent[node]
  255. def __len__(self):
  256. return len(self.adjacent)
  257. def __contains__(self, obj):
  258. return obj in self.adjacent
  259. def _iterate_items(self):
  260. return items(self.adjacent)
  261. items = iteritems = _iterate_items
  262. def __repr__(self):
  263. return '\n'.join(self.repr_node(N) for N in self)
  264. def repr_node(self, obj, level=1, fmt='{0}({1})'):
  265. output = [fmt.format(obj, self.valency_of(obj))]
  266. if obj in self:
  267. for other in self[obj]:
  268. d = fmt.format(other, self.valency_of(other))
  269. output.append(' ' * level + d)
  270. output.extend(self.repr_node(other, level + 1).split('\n')[1:])
  271. return '\n'.join(output)
  272. class AttributeDictMixin(object):
  273. """Augment classes with a Mapping interface by adding attribute access.
  274. I.e. `d.key -> d[key]`.
  275. """
  276. def __getattr__(self, k):
  277. """`d.key -> d[key]`"""
  278. try:
  279. return self[k]
  280. except KeyError:
  281. raise AttributeError(
  282. '{0!r} object has no attribute {1!r}'.format(
  283. type(self).__name__, k))
  284. def __setattr__(self, key, value):
  285. """`d[key] = value -> d.key = value`"""
  286. self[key] = value
  287. class AttributeDict(dict, AttributeDictMixin):
  288. """Dict subclass with attribute access."""
  289. pass
  290. class DictAttribute(object):
  291. """Dict interface to attributes.
  292. `obj[k] -> obj.k`
  293. `obj[k] = val -> obj.k = val`
  294. """
  295. obj = None
  296. def __init__(self, obj):
  297. object.__setattr__(self, 'obj', obj)
  298. def __getattr__(self, key):
  299. return getattr(self.obj, key)
  300. def __setattr__(self, key, value):
  301. return setattr(self.obj, key, value)
  302. def get(self, key, default=None):
  303. try:
  304. return self[key]
  305. except KeyError:
  306. return default
  307. def setdefault(self, key, default):
  308. if key not in self:
  309. self[key] = default
  310. def __getitem__(self, key):
  311. try:
  312. return getattr(self.obj, key)
  313. except AttributeError:
  314. raise KeyError(key)
  315. def __setitem__(self, key, value):
  316. setattr(self.obj, key, value)
  317. def __contains__(self, key):
  318. return hasattr(self.obj, key)
  319. def _iterate_keys(self):
  320. return iter(dir(self.obj))
  321. iterkeys = _iterate_keys
  322. def __iter__(self):
  323. return self._iterate_keys()
  324. def _iterate_items(self):
  325. for key in self._iterate_keys():
  326. yield key, getattr(self.obj, key)
  327. iteritems = _iterate_items
  328. def _iterate_values(self):
  329. for key in self._iterate_keys():
  330. yield getattr(self.obj, key)
  331. itervalues = _iterate_values
  332. if sys.version_info[0] == 3: # pragma: no cover
  333. items = _iterate_items
  334. keys = _iterate_keys
  335. values = _iterate_values
  336. else:
  337. def keys(self):
  338. return list(self)
  339. def items(self):
  340. return list(self._iterate_items())
  341. def values(self):
  342. return list(self._iterate_values())
  343. MutableMapping.register(DictAttribute)
  344. @python_2_unicode_compatible
  345. class ConfigurationView(AttributeDictMixin):
  346. """A view over an applications configuration dictionaries.
  347. Custom (but older) version of :class:`collections.ChainMap`.
  348. If the key does not exist in ``changes``, the ``defaults``
  349. dictionaries are consulted.
  350. :param changes: Dict containing changes to the configuration.
  351. :param defaults: List of dictionaries containing the default
  352. configuration.
  353. """
  354. key_t = None
  355. changes = None
  356. defaults = None
  357. _order = None
  358. def __init__(self, changes, defaults=None, key_t=None, prefix=None):
  359. defaults = [] if defaults is None else defaults
  360. self.__dict__.update(
  361. changes=changes,
  362. defaults=defaults,
  363. key_t=key_t,
  364. _order=[changes] + defaults,
  365. prefix=prefix.rstrip('_') + '_' if prefix else prefix,
  366. )
  367. def _to_keys(self, key):
  368. prefix = self.prefix
  369. if prefix:
  370. pkey = prefix + key if not key.startswith(prefix) else key
  371. return match_case(pkey, prefix), self._key(key)
  372. return self._key(key),
  373. def _key(self, key):
  374. return self.key_t(key) if self.key_t is not None else key
  375. def add_defaults(self, d):
  376. d = force_mapping(d)
  377. self.defaults.insert(0, d)
  378. self._order.insert(1, d)
  379. def __getitem__(self, key):
  380. keys = self._to_keys(key)
  381. for k in keys:
  382. for d in self._order:
  383. try:
  384. return d[k]
  385. except KeyError:
  386. pass
  387. if len(keys) > 1:
  388. raise KeyError(
  389. 'Key not found: {0!r} (with prefix: {0!r})'.format(*keys))
  390. raise KeyError(key)
  391. def __setitem__(self, key, value):
  392. self.changes[self._key(key)] = value
  393. def first(self, *keys):
  394. return first(None, (self.get(key) for key in keys))
  395. def get(self, key, default=None):
  396. try:
  397. return self[key]
  398. except KeyError:
  399. return default
  400. def clear(self):
  401. """Remove all changes, but keep defaults."""
  402. self.changes.clear()
  403. def setdefault(self, key, default):
  404. key = self._key(key)
  405. if key not in self:
  406. self[key] = default
  407. def update(self, *args, **kwargs):
  408. return self.changes.update(*args, **kwargs)
  409. def __contains__(self, key):
  410. keys = self._to_keys(key)
  411. return any(any(k in m for k in keys) for m in self._order)
  412. def __bool__(self):
  413. return any(self._order)
  414. __nonzero__ = __bool__ # Py2
  415. def __repr__(self):
  416. return repr(dict(items(self)))
  417. def __iter__(self):
  418. return self._iterate_keys()
  419. def __len__(self):
  420. # The logic for iterating keys includes uniq(),
  421. # so to be safe we count by explicitly iterating
  422. return len(set().union(*self._order))
  423. def _iter(self, op):
  424. # defaults must be first in the stream, so values in
  425. # changes takes precedence.
  426. return chain(*[op(d) for d in reversed(self._order)])
  427. def swap_with(self, other):
  428. changes = other.__dict__['changes']
  429. defaults = other.__dict__['defaults']
  430. self.__dict__.update(
  431. changes=changes,
  432. defaults=defaults,
  433. key_t=other.__dict__['key_t'],
  434. prefix=other.__dict__['prefix'],
  435. _order=[changes] + defaults
  436. )
  437. def _iterate_keys(self):
  438. return uniq(self._iter(lambda d: d.keys()))
  439. iterkeys = _iterate_keys
  440. def _iterate_items(self):
  441. return ((key, self[key]) for key in self)
  442. iteritems = _iterate_items
  443. def _iterate_values(self):
  444. return (self[key] for key in self)
  445. itervalues = _iterate_values
  446. if sys.version_info[0] == 3: # pragma: no cover
  447. keys = _iterate_keys
  448. items = _iterate_items
  449. values = _iterate_values
  450. else: # noqa
  451. def keys(self):
  452. return list(self._iterate_keys())
  453. def items(self):
  454. return list(self._iterate_items())
  455. def values(self):
  456. return list(self._iterate_values())
  457. MutableMapping.register(ConfigurationView)
  458. @python_2_unicode_compatible
  459. class LimitedSet(object):
  460. """Kind-of Set (or priority queue) with limitations.
  461. Good for when you need to test for membership (`a in set`),
  462. but the set should not grow unbounded.
  463. ``maxlen`` is enforced at all times, so if the limit is reached
  464. we will also remove non-expired items.
  465. You can also configure ``minlen``, which is the minimal residual size
  466. of the set.
  467. All arguments are optional, and no limits are enabled by default.
  468. :keyword maxlen: Optional max number of items.
  469. Adding more items than ``maxlen`` will result in immediate
  470. removal of items sorted by oldest insertion time.
  471. :keyword expires: TTL for all items.
  472. Expired items are purged as keys are inserted.
  473. :keyword minlen: Minimal residual size of this set.
  474. .. versionadded:: 4.0
  475. Value must be less than ``maxlen`` if both are configured.
  476. Older expired items will be deleted, only after the set
  477. exceeds ``minlen`` number of items.
  478. :keyword data: Initial data to initialize set with.
  479. Can be an iterable of ``(key, value)`` pairs,
  480. a dict (``{key: insertion_time}``), or another instance
  481. of :class:`LimitedSet`.
  482. Example:
  483. .. code-block:: pycon
  484. >>> s = LimitedSet(maxlen=50000, expires=3600, minlen=4000)
  485. >>> for i in range(60000):
  486. ... s.add(i)
  487. ... s.add(str(i))
  488. ...
  489. >>> 57000 in s # last 50k inserted values are kept
  490. True
  491. >>> '10' in s # '10' did expire and was purged from set.
  492. False
  493. >>> len(s) # maxlen is reached
  494. 50000
  495. >>> s.purge(now=time.time() + 7200) # clock + 2 hours
  496. >>> len(s) # now only minlen items are cached
  497. 4000
  498. >>>> 57000 in s # even this item is gone now
  499. False
  500. """
  501. max_heap_percent_overload = 15
  502. def __init__(self, maxlen=0, expires=0, data=None, minlen=0):
  503. self.maxlen = 0 if maxlen is None else maxlen
  504. self.minlen = 0 if minlen is None else minlen
  505. self.expires = 0 if expires is None else expires
  506. self._data = {}
  507. self._heap = []
  508. # make shortcuts
  509. self.__len__ = self._data.__len__
  510. self.__contains__ = self._data.__contains__
  511. if data:
  512. # import items from data
  513. self.update(data)
  514. if not self.maxlen >= self.minlen >= 0:
  515. raise ValueError(
  516. 'minlen must be a positive number, less or equal to maxlen.')
  517. if self.expires < 0:
  518. raise ValueError('expires cannot be negative!')
  519. def _refresh_heap(self):
  520. """Time consuming recreating of heap. Do not run this too often."""
  521. self._heap[:] = [entry for entry in values(self._data)]
  522. heapify(self._heap)
  523. def _maybe_refresh_heap(self):
  524. if self._heap_overload >= self.max_heap_percent_overload:
  525. self._refresh_heap()
  526. def clear(self):
  527. """Clear all data, start from scratch again."""
  528. self._data.clear()
  529. self._heap[:] = []
  530. def add(self, item, now=None):
  531. """Add a new item, or reset the expiry time of an existing item."""
  532. now = now or time.time()
  533. if item in self._data:
  534. self.discard(item)
  535. entry = (now, item)
  536. self._data[item] = entry
  537. heappush(self._heap, entry)
  538. if self.maxlen and len(self._data) >= self.maxlen:
  539. self.purge()
  540. def update(self, other):
  541. """Update this set from other LimitedSet, dict or iterable."""
  542. if not other:
  543. return
  544. if isinstance(other, LimitedSet):
  545. self._data.update(other._data)
  546. self._refresh_heap()
  547. self.purge()
  548. elif isinstance(other, dict):
  549. # revokes are sent as a dict
  550. for key, inserted in items(other):
  551. if isinstance(inserted, (tuple, list)):
  552. # in case someone uses ._data directly for sending update
  553. inserted = inserted[0]
  554. if not isinstance(inserted, float):
  555. raise ValueError(
  556. 'Expecting float timestamp, got type '
  557. '{0!r} with value: {1}'.format(
  558. type(inserted), inserted))
  559. self.add(key, inserted)
  560. else:
  561. # XXX AVOID THIS, it could keep old data if more parties
  562. # exchange them all over and over again
  563. for obj in other:
  564. self.add(obj)
  565. def discard(self, item):
  566. # mark an existing item as removed. If KeyError is not found, pass.
  567. self._data.pop(item, None)
  568. self._maybe_refresh_heap()
  569. pop_value = discard
  570. def purge(self, now=None):
  571. """Check oldest items and remove them if needed.
  572. :keyword now: Time of purging -- by default right now.
  573. This can be useful for unit testing.
  574. """
  575. now = now or time.time()
  576. now = now() if isinstance(now, Callable) else now
  577. if self.maxlen:
  578. while len(self._data) > self.maxlen:
  579. self.pop()
  580. # time based expiring:
  581. if self.expires:
  582. while len(self._data) > self.minlen >= 0:
  583. inserted_time, _ = self._heap[0]
  584. if inserted_time + self.expires > now:
  585. break # oldest item has not expired yet
  586. self.pop()
  587. def pop(self, default=None):
  588. """Remove and return the oldest item, or :const:`None` when empty."""
  589. while self._heap:
  590. _, item = heappop(self._heap)
  591. try:
  592. self._data.pop(item)
  593. except KeyError:
  594. pass
  595. else:
  596. return item
  597. return default
  598. def as_dict(self):
  599. """Whole set as serializable dictionary.
  600. Example::
  601. >>> s = LimitedSet(maxlen=200)
  602. >>> r = LimitedSet(maxlen=200)
  603. >>> for i in range(500):
  604. ... s.add(i)
  605. ...
  606. >>> r.update(s.as_dict())
  607. >>> r == s
  608. True
  609. """
  610. return {key: inserted for inserted, key in values(self._data)}
  611. def __eq__(self, other):
  612. return self._data == other._data
  613. def __ne__(self, other):
  614. return not self.__eq__(other)
  615. def __repr__(self):
  616. return REPR_LIMITED_SET.format(
  617. self, name=type(self).__name__, size=len(self),
  618. )
  619. def __iter__(self):
  620. return (i for _, i in sorted(values(self._data)))
  621. def __len__(self):
  622. return len(self._data)
  623. def __contains__(self, key):
  624. return key in self._data
  625. def __reduce__(self):
  626. return self.__class__, (
  627. self.maxlen, self.expires, self.as_dict(), self.minlen)
  628. def __bool__(self):
  629. return bool(self._data)
  630. __nonzero__ = __bool__ # Py2
  631. @property
  632. def _heap_overload(self):
  633. """Compute how much is heap bigger than data [percents]."""
  634. return len(self._heap) * 100 / max(len(self._data), 1) - 100
  635. MutableSet.register(LimitedSet)