You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1090 lines
28KB

  1. # util/_collections.py
  2. # Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Collection classes and helpers."""
  8. from __future__ import absolute_import
  9. import operator
  10. import types
  11. import weakref
  12. from .compat import binary_types
  13. from .compat import collections_abc
  14. from .compat import itertools_filterfalse
  15. from .compat import py2k
  16. from .compat import py37
  17. from .compat import string_types
  18. from .compat import threading
  19. EMPTY_SET = frozenset()
  20. class ImmutableContainer(object):
  21. def _immutable(self, *arg, **kw):
  22. raise TypeError("%s object is immutable" % self.__class__.__name__)
  23. __delitem__ = __setitem__ = __setattr__ = _immutable
  24. def _immutabledict_py_fallback():
  25. class immutabledict(ImmutableContainer, dict):
  26. clear = (
  27. pop
  28. ) = popitem = setdefault = update = ImmutableContainer._immutable
  29. def __new__(cls, *args):
  30. new = dict.__new__(cls)
  31. dict.__init__(new, *args)
  32. return new
  33. def __init__(self, *args):
  34. pass
  35. def __reduce__(self):
  36. return _immutabledict_reconstructor, (dict(self),)
  37. def union(self, __d=None):
  38. if not __d:
  39. return self
  40. new = dict.__new__(self.__class__)
  41. dict.__init__(new, self)
  42. dict.update(new, __d)
  43. return new
  44. def _union_w_kw(self, __d=None, **kw):
  45. # not sure if C version works correctly w/ this yet
  46. if not __d and not kw:
  47. return self
  48. new = dict.__new__(self.__class__)
  49. dict.__init__(new, self)
  50. if __d:
  51. dict.update(new, __d)
  52. dict.update(new, kw)
  53. return new
  54. def merge_with(self, *dicts):
  55. new = None
  56. for d in dicts:
  57. if d:
  58. if new is None:
  59. new = dict.__new__(self.__class__)
  60. dict.__init__(new, self)
  61. dict.update(new, d)
  62. if new is None:
  63. return self
  64. return new
  65. def __repr__(self):
  66. return "immutabledict(%s)" % dict.__repr__(self)
  67. return immutabledict
  68. try:
  69. from sqlalchemy.cimmutabledict import immutabledict
  70. collections_abc.Mapping.register(immutabledict)
  71. except ImportError:
  72. immutabledict = _immutabledict_py_fallback()
  73. def _immutabledict_reconstructor(*arg):
  74. """do the pickle dance"""
  75. return immutabledict(*arg)
  76. def coerce_to_immutabledict(d):
  77. if not d:
  78. return EMPTY_DICT
  79. elif isinstance(d, immutabledict):
  80. return d
  81. else:
  82. return immutabledict(d)
  83. EMPTY_DICT = immutabledict()
  84. class FacadeDict(ImmutableContainer, dict):
  85. """A dictionary that is not publicly mutable."""
  86. clear = pop = popitem = setdefault = update = ImmutableContainer._immutable
  87. def __new__(cls, *args):
  88. new = dict.__new__(cls)
  89. return new
  90. def copy(self):
  91. raise NotImplementedError(
  92. "an immutabledict shouldn't need to be copied. use dict(d) "
  93. "if you need a mutable dictionary."
  94. )
  95. def __reduce__(self):
  96. return FacadeDict, (dict(self),)
  97. def _insert_item(self, key, value):
  98. """insert an item into the dictionary directly."""
  99. dict.__setitem__(self, key, value)
  100. def __repr__(self):
  101. return "FacadeDict(%s)" % dict.__repr__(self)
  102. class Properties(object):
  103. """Provide a __getattr__/__setattr__ interface over a dict."""
  104. __slots__ = ("_data",)
  105. def __init__(self, data):
  106. object.__setattr__(self, "_data", data)
  107. def __len__(self):
  108. return len(self._data)
  109. def __iter__(self):
  110. return iter(list(self._data.values()))
  111. def __dir__(self):
  112. return dir(super(Properties, self)) + [
  113. str(k) for k in self._data.keys()
  114. ]
  115. def __add__(self, other):
  116. return list(self) + list(other)
  117. def __setitem__(self, key, obj):
  118. self._data[key] = obj
  119. def __getitem__(self, key):
  120. return self._data[key]
  121. def __delitem__(self, key):
  122. del self._data[key]
  123. def __setattr__(self, key, obj):
  124. self._data[key] = obj
  125. def __getstate__(self):
  126. return {"_data": self._data}
  127. def __setstate__(self, state):
  128. object.__setattr__(self, "_data", state["_data"])
  129. def __getattr__(self, key):
  130. try:
  131. return self._data[key]
  132. except KeyError:
  133. raise AttributeError(key)
  134. def __contains__(self, key):
  135. return key in self._data
  136. def as_immutable(self):
  137. """Return an immutable proxy for this :class:`.Properties`."""
  138. return ImmutableProperties(self._data)
  139. def update(self, value):
  140. self._data.update(value)
  141. def get(self, key, default=None):
  142. if key in self:
  143. return self[key]
  144. else:
  145. return default
  146. def keys(self):
  147. return list(self._data)
  148. def values(self):
  149. return list(self._data.values())
  150. def items(self):
  151. return list(self._data.items())
  152. def has_key(self, key):
  153. return key in self._data
  154. def clear(self):
  155. self._data.clear()
  156. class OrderedProperties(Properties):
  157. """Provide a __getattr__/__setattr__ interface with an OrderedDict
  158. as backing store."""
  159. __slots__ = ()
  160. def __init__(self):
  161. Properties.__init__(self, OrderedDict())
  162. class ImmutableProperties(ImmutableContainer, Properties):
  163. """Provide immutable dict/object attribute to an underlying dictionary."""
  164. __slots__ = ()
  165. def _ordered_dictionary_sort(d, key=None):
  166. """Sort an OrderedDict in-place."""
  167. items = [(k, d[k]) for k in sorted(d, key=key)]
  168. d.clear()
  169. d.update(items)
  170. if py37:
  171. OrderedDict = dict
  172. sort_dictionary = _ordered_dictionary_sort
  173. else:
  174. # prevent sort_dictionary from being used against a plain dictionary
  175. # for Python < 3.7
  176. def sort_dictionary(d, key=None):
  177. """Sort an OrderedDict in place."""
  178. d._ordered_dictionary_sort(key=key)
  179. class OrderedDict(dict):
  180. """Dictionary that maintains insertion order.
  181. Superseded by Python dict as of Python 3.7
  182. """
  183. __slots__ = ("_list",)
  184. def _ordered_dictionary_sort(self, key=None):
  185. _ordered_dictionary_sort(self, key=key)
  186. def __reduce__(self):
  187. return OrderedDict, (self.items(),)
  188. def __init__(self, ____sequence=None, **kwargs):
  189. self._list = []
  190. if ____sequence is None:
  191. if kwargs:
  192. self.update(**kwargs)
  193. else:
  194. self.update(____sequence, **kwargs)
  195. def clear(self):
  196. self._list = []
  197. dict.clear(self)
  198. def copy(self):
  199. return self.__copy__()
  200. def __copy__(self):
  201. return OrderedDict(self)
  202. def update(self, ____sequence=None, **kwargs):
  203. if ____sequence is not None:
  204. if hasattr(____sequence, "keys"):
  205. for key in ____sequence.keys():
  206. self.__setitem__(key, ____sequence[key])
  207. else:
  208. for key, value in ____sequence:
  209. self[key] = value
  210. if kwargs:
  211. self.update(kwargs)
  212. def setdefault(self, key, value):
  213. if key not in self:
  214. self.__setitem__(key, value)
  215. return value
  216. else:
  217. return self.__getitem__(key)
  218. def __iter__(self):
  219. return iter(self._list)
  220. def keys(self):
  221. return list(self)
  222. def values(self):
  223. return [self[key] for key in self._list]
  224. def items(self):
  225. return [(key, self[key]) for key in self._list]
  226. if py2k:
  227. def itervalues(self):
  228. return iter(self.values())
  229. def iterkeys(self):
  230. return iter(self)
  231. def iteritems(self):
  232. return iter(self.items())
  233. def __setitem__(self, key, obj):
  234. if key not in self:
  235. try:
  236. self._list.append(key)
  237. except AttributeError:
  238. # work around Python pickle loads() with
  239. # dict subclass (seems to ignore __setstate__?)
  240. self._list = [key]
  241. dict.__setitem__(self, key, obj)
  242. def __delitem__(self, key):
  243. dict.__delitem__(self, key)
  244. self._list.remove(key)
  245. def pop(self, key, *default):
  246. present = key in self
  247. value = dict.pop(self, key, *default)
  248. if present:
  249. self._list.remove(key)
  250. return value
  251. def popitem(self):
  252. item = dict.popitem(self)
  253. self._list.remove(item[0])
  254. return item
  255. class OrderedSet(set):
  256. def __init__(self, d=None):
  257. set.__init__(self)
  258. if d is not None:
  259. self._list = unique_list(d)
  260. set.update(self, self._list)
  261. else:
  262. self._list = []
  263. def add(self, element):
  264. if element not in self:
  265. self._list.append(element)
  266. set.add(self, element)
  267. def remove(self, element):
  268. set.remove(self, element)
  269. self._list.remove(element)
  270. def insert(self, pos, element):
  271. if element not in self:
  272. self._list.insert(pos, element)
  273. set.add(self, element)
  274. def discard(self, element):
  275. if element in self:
  276. self._list.remove(element)
  277. set.remove(self, element)
  278. def clear(self):
  279. set.clear(self)
  280. self._list = []
  281. def __getitem__(self, key):
  282. return self._list[key]
  283. def __iter__(self):
  284. return iter(self._list)
  285. def __add__(self, other):
  286. return self.union(other)
  287. def __repr__(self):
  288. return "%s(%r)" % (self.__class__.__name__, self._list)
  289. __str__ = __repr__
  290. def update(self, iterable):
  291. for e in iterable:
  292. if e not in self:
  293. self._list.append(e)
  294. set.add(self, e)
  295. return self
  296. __ior__ = update
  297. def union(self, other):
  298. result = self.__class__(self)
  299. result.update(other)
  300. return result
  301. __or__ = union
  302. def intersection(self, other):
  303. other = set(other)
  304. return self.__class__(a for a in self if a in other)
  305. __and__ = intersection
  306. def symmetric_difference(self, other):
  307. other = set(other)
  308. result = self.__class__(a for a in self if a not in other)
  309. result.update(a for a in other if a not in self)
  310. return result
  311. __xor__ = symmetric_difference
  312. def difference(self, other):
  313. other = set(other)
  314. return self.__class__(a for a in self if a not in other)
  315. __sub__ = difference
  316. def intersection_update(self, other):
  317. other = set(other)
  318. set.intersection_update(self, other)
  319. self._list = [a for a in self._list if a in other]
  320. return self
  321. __iand__ = intersection_update
  322. def symmetric_difference_update(self, other):
  323. set.symmetric_difference_update(self, other)
  324. self._list = [a for a in self._list if a in self]
  325. self._list += [a for a in other._list if a in self]
  326. return self
  327. __ixor__ = symmetric_difference_update
  328. def difference_update(self, other):
  329. set.difference_update(self, other)
  330. self._list = [a for a in self._list if a in self]
  331. return self
  332. __isub__ = difference_update
  333. class IdentitySet(object):
  334. """A set that considers only object id() for uniqueness.
  335. This strategy has edge cases for builtin types- it's possible to have
  336. two 'foo' strings in one of these sets, for example. Use sparingly.
  337. """
  338. def __init__(self, iterable=None):
  339. self._members = dict()
  340. if iterable:
  341. self.update(iterable)
  342. def add(self, value):
  343. self._members[id(value)] = value
  344. def __contains__(self, value):
  345. return id(value) in self._members
  346. def remove(self, value):
  347. del self._members[id(value)]
  348. def discard(self, value):
  349. try:
  350. self.remove(value)
  351. except KeyError:
  352. pass
  353. def pop(self):
  354. try:
  355. pair = self._members.popitem()
  356. return pair[1]
  357. except KeyError:
  358. raise KeyError("pop from an empty set")
  359. def clear(self):
  360. self._members.clear()
  361. def __cmp__(self, other):
  362. raise TypeError("cannot compare sets using cmp()")
  363. def __eq__(self, other):
  364. if isinstance(other, IdentitySet):
  365. return self._members == other._members
  366. else:
  367. return False
  368. def __ne__(self, other):
  369. if isinstance(other, IdentitySet):
  370. return self._members != other._members
  371. else:
  372. return True
  373. def issubset(self, iterable):
  374. if isinstance(iterable, self.__class__):
  375. other = iterable
  376. else:
  377. other = self.__class__(iterable)
  378. if len(self) > len(other):
  379. return False
  380. for m in itertools_filterfalse(
  381. other._members.__contains__, iter(self._members.keys())
  382. ):
  383. return False
  384. return True
  385. def __le__(self, other):
  386. if not isinstance(other, IdentitySet):
  387. return NotImplemented
  388. return self.issubset(other)
  389. def __lt__(self, other):
  390. if not isinstance(other, IdentitySet):
  391. return NotImplemented
  392. return len(self) < len(other) and self.issubset(other)
  393. def issuperset(self, iterable):
  394. if isinstance(iterable, self.__class__):
  395. other = iterable
  396. else:
  397. other = self.__class__(iterable)
  398. if len(self) < len(other):
  399. return False
  400. for m in itertools_filterfalse(
  401. self._members.__contains__, iter(other._members.keys())
  402. ):
  403. return False
  404. return True
  405. def __ge__(self, other):
  406. if not isinstance(other, IdentitySet):
  407. return NotImplemented
  408. return self.issuperset(other)
  409. def __gt__(self, other):
  410. if not isinstance(other, IdentitySet):
  411. return NotImplemented
  412. return len(self) > len(other) and self.issuperset(other)
  413. def union(self, iterable):
  414. result = self.__class__()
  415. members = self._members
  416. result._members.update(members)
  417. result._members.update((id(obj), obj) for obj in iterable)
  418. return result
  419. def __or__(self, other):
  420. if not isinstance(other, IdentitySet):
  421. return NotImplemented
  422. return self.union(other)
  423. def update(self, iterable):
  424. self._members.update((id(obj), obj) for obj in iterable)
  425. def __ior__(self, other):
  426. if not isinstance(other, IdentitySet):
  427. return NotImplemented
  428. self.update(other)
  429. return self
  430. def difference(self, iterable):
  431. result = self.__class__()
  432. members = self._members
  433. if isinstance(iterable, self.__class__):
  434. other = set(iterable._members.keys())
  435. else:
  436. other = {id(obj) for obj in iterable}
  437. result._members.update(
  438. ((k, v) for k, v in members.items() if k not in other)
  439. )
  440. return result
  441. def __sub__(self, other):
  442. if not isinstance(other, IdentitySet):
  443. return NotImplemented
  444. return self.difference(other)
  445. def difference_update(self, iterable):
  446. self._members = self.difference(iterable)._members
  447. def __isub__(self, other):
  448. if not isinstance(other, IdentitySet):
  449. return NotImplemented
  450. self.difference_update(other)
  451. return self
  452. def intersection(self, iterable):
  453. result = self.__class__()
  454. members = self._members
  455. if isinstance(iterable, self.__class__):
  456. other = set(iterable._members.keys())
  457. else:
  458. other = {id(obj) for obj in iterable}
  459. result._members.update(
  460. (k, v) for k, v in members.items() if k in other
  461. )
  462. return result
  463. def __and__(self, other):
  464. if not isinstance(other, IdentitySet):
  465. return NotImplemented
  466. return self.intersection(other)
  467. def intersection_update(self, iterable):
  468. self._members = self.intersection(iterable)._members
  469. def __iand__(self, other):
  470. if not isinstance(other, IdentitySet):
  471. return NotImplemented
  472. self.intersection_update(other)
  473. return self
  474. def symmetric_difference(self, iterable):
  475. result = self.__class__()
  476. members = self._members
  477. if isinstance(iterable, self.__class__):
  478. other = iterable._members
  479. else:
  480. other = {id(obj): obj for obj in iterable}
  481. result._members.update(
  482. ((k, v) for k, v in members.items() if k not in other)
  483. )
  484. result._members.update(
  485. ((k, v) for k, v in other.items() if k not in members)
  486. )
  487. return result
  488. def __xor__(self, other):
  489. if not isinstance(other, IdentitySet):
  490. return NotImplemented
  491. return self.symmetric_difference(other)
  492. def symmetric_difference_update(self, iterable):
  493. self._members = self.symmetric_difference(iterable)._members
  494. def __ixor__(self, other):
  495. if not isinstance(other, IdentitySet):
  496. return NotImplemented
  497. self.symmetric_difference(other)
  498. return self
  499. def copy(self):
  500. return type(self)(iter(self._members.values()))
  501. __copy__ = copy
  502. def __len__(self):
  503. return len(self._members)
  504. def __iter__(self):
  505. return iter(self._members.values())
  506. def __hash__(self):
  507. raise TypeError("set objects are unhashable")
  508. def __repr__(self):
  509. return "%s(%r)" % (type(self).__name__, list(self._members.values()))
  510. class WeakSequence(object):
  511. def __init__(self, __elements=()):
  512. # adapted from weakref.WeakKeyDictionary, prevent reference
  513. # cycles in the collection itself
  514. def _remove(item, selfref=weakref.ref(self)):
  515. self = selfref()
  516. if self is not None:
  517. self._storage.remove(item)
  518. self._remove = _remove
  519. self._storage = [
  520. weakref.ref(element, _remove) for element in __elements
  521. ]
  522. def append(self, item):
  523. self._storage.append(weakref.ref(item, self._remove))
  524. def __len__(self):
  525. return len(self._storage)
  526. def __iter__(self):
  527. return (
  528. obj for obj in (ref() for ref in self._storage) if obj is not None
  529. )
  530. def __getitem__(self, index):
  531. try:
  532. obj = self._storage[index]
  533. except KeyError:
  534. raise IndexError("Index %s out of range" % index)
  535. else:
  536. return obj()
  537. class OrderedIdentitySet(IdentitySet):
  538. def __init__(self, iterable=None):
  539. IdentitySet.__init__(self)
  540. self._members = OrderedDict()
  541. if iterable:
  542. for o in iterable:
  543. self.add(o)
  544. class PopulateDict(dict):
  545. """A dict which populates missing values via a creation function.
  546. Note the creation function takes a key, unlike
  547. collections.defaultdict.
  548. """
  549. def __init__(self, creator):
  550. self.creator = creator
  551. def __missing__(self, key):
  552. self[key] = val = self.creator(key)
  553. return val
  554. class WeakPopulateDict(dict):
  555. """Like PopulateDict, but assumes a self + a method and does not create
  556. a reference cycle.
  557. """
  558. def __init__(self, creator_method):
  559. self.creator = creator_method.__func__
  560. weakself = creator_method.__self__
  561. self.weakself = weakref.ref(weakself)
  562. def __missing__(self, key):
  563. self[key] = val = self.creator(self.weakself(), key)
  564. return val
  565. # Define collections that are capable of storing
  566. # ColumnElement objects as hashable keys/elements.
  567. # At this point, these are mostly historical, things
  568. # used to be more complicated.
  569. column_set = set
  570. column_dict = dict
  571. ordered_column_set = OrderedSet
  572. _getters = PopulateDict(operator.itemgetter)
  573. _property_getters = PopulateDict(
  574. lambda idx: property(operator.itemgetter(idx))
  575. )
  576. def unique_list(seq, hashfunc=None):
  577. seen = set()
  578. seen_add = seen.add
  579. if not hashfunc:
  580. return [x for x in seq if x not in seen and not seen_add(x)]
  581. else:
  582. return [
  583. x
  584. for x in seq
  585. if hashfunc(x) not in seen and not seen_add(hashfunc(x))
  586. ]
  587. class UniqueAppender(object):
  588. """Appends items to a collection ensuring uniqueness.
  589. Additional appends() of the same object are ignored. Membership is
  590. determined by identity (``is a``) not equality (``==``).
  591. """
  592. def __init__(self, data, via=None):
  593. self.data = data
  594. self._unique = {}
  595. if via:
  596. self._data_appender = getattr(data, via)
  597. elif hasattr(data, "append"):
  598. self._data_appender = data.append
  599. elif hasattr(data, "add"):
  600. self._data_appender = data.add
  601. def append(self, item):
  602. id_ = id(item)
  603. if id_ not in self._unique:
  604. self._data_appender(item)
  605. self._unique[id_] = True
  606. def __iter__(self):
  607. return iter(self.data)
  608. def coerce_generator_arg(arg):
  609. if len(arg) == 1 and isinstance(arg[0], types.GeneratorType):
  610. return list(arg[0])
  611. else:
  612. return arg
  613. def to_list(x, default=None):
  614. if x is None:
  615. return default
  616. if not isinstance(x, collections_abc.Iterable) or isinstance(
  617. x, string_types + binary_types
  618. ):
  619. return [x]
  620. elif isinstance(x, list):
  621. return x
  622. else:
  623. return list(x)
  624. def has_intersection(set_, iterable):
  625. r"""return True if any items of set\_ are present in iterable.
  626. Goes through special effort to ensure __hash__ is not called
  627. on items in iterable that don't support it.
  628. """
  629. # TODO: optimize, write in C, etc.
  630. return bool(set_.intersection([i for i in iterable if i.__hash__]))
  631. def to_set(x):
  632. if x is None:
  633. return set()
  634. if not isinstance(x, set):
  635. return set(to_list(x))
  636. else:
  637. return x
  638. def to_column_set(x):
  639. if x is None:
  640. return column_set()
  641. if not isinstance(x, column_set):
  642. return column_set(to_list(x))
  643. else:
  644. return x
  645. def update_copy(d, _new=None, **kw):
  646. """Copy the given dict and update with the given values."""
  647. d = d.copy()
  648. if _new:
  649. d.update(_new)
  650. d.update(**kw)
  651. return d
  652. def flatten_iterator(x):
  653. """Given an iterator of which further sub-elements may also be
  654. iterators, flatten the sub-elements into a single iterator.
  655. """
  656. for elem in x:
  657. if not isinstance(elem, str) and hasattr(elem, "__iter__"):
  658. for y in flatten_iterator(elem):
  659. yield y
  660. else:
  661. yield elem
  662. class LRUCache(dict):
  663. """Dictionary with 'squishy' removal of least
  664. recently used items.
  665. Note that either get() or [] should be used here, but
  666. generally its not safe to do an "in" check first as the dictionary
  667. can change subsequent to that call.
  668. """
  669. __slots__ = "capacity", "threshold", "size_alert", "_counter", "_mutex"
  670. def __init__(self, capacity=100, threshold=0.5, size_alert=None):
  671. self.capacity = capacity
  672. self.threshold = threshold
  673. self.size_alert = size_alert
  674. self._counter = 0
  675. self._mutex = threading.Lock()
  676. def _inc_counter(self):
  677. self._counter += 1
  678. return self._counter
  679. def get(self, key, default=None):
  680. item = dict.get(self, key, default)
  681. if item is not default:
  682. item[2] = self._inc_counter()
  683. return item[1]
  684. else:
  685. return default
  686. def __getitem__(self, key):
  687. item = dict.__getitem__(self, key)
  688. item[2] = self._inc_counter()
  689. return item[1]
  690. def values(self):
  691. return [i[1] for i in dict.values(self)]
  692. def setdefault(self, key, value):
  693. if key in self:
  694. return self[key]
  695. else:
  696. self[key] = value
  697. return value
  698. def __setitem__(self, key, value):
  699. item = dict.get(self, key)
  700. if item is None:
  701. item = [key, value, self._inc_counter()]
  702. dict.__setitem__(self, key, item)
  703. else:
  704. item[1] = value
  705. self._manage_size()
  706. @property
  707. def size_threshold(self):
  708. return self.capacity + self.capacity * self.threshold
  709. def _manage_size(self):
  710. if not self._mutex.acquire(False):
  711. return
  712. try:
  713. size_alert = bool(self.size_alert)
  714. while len(self) > self.capacity + self.capacity * self.threshold:
  715. if size_alert:
  716. size_alert = False
  717. self.size_alert(self)
  718. by_counter = sorted(
  719. dict.values(self), key=operator.itemgetter(2), reverse=True
  720. )
  721. for item in by_counter[self.capacity :]:
  722. try:
  723. del self[item[0]]
  724. except KeyError:
  725. # deleted elsewhere; skip
  726. continue
  727. finally:
  728. self._mutex.release()
  729. class ScopedRegistry(object):
  730. """A Registry that can store one or multiple instances of a single
  731. class on the basis of a "scope" function.
  732. The object implements ``__call__`` as the "getter", so by
  733. calling ``myregistry()`` the contained object is returned
  734. for the current scope.
  735. :param createfunc:
  736. a callable that returns a new object to be placed in the registry
  737. :param scopefunc:
  738. a callable that will return a key to store/retrieve an object.
  739. """
  740. def __init__(self, createfunc, scopefunc):
  741. """Construct a new :class:`.ScopedRegistry`.
  742. :param createfunc: A creation function that will generate
  743. a new value for the current scope, if none is present.
  744. :param scopefunc: A function that returns a hashable
  745. token representing the current scope (such as, current
  746. thread identifier).
  747. """
  748. self.createfunc = createfunc
  749. self.scopefunc = scopefunc
  750. self.registry = {}
  751. def __call__(self):
  752. key = self.scopefunc()
  753. try:
  754. return self.registry[key]
  755. except KeyError:
  756. return self.registry.setdefault(key, self.createfunc())
  757. def has(self):
  758. """Return True if an object is present in the current scope."""
  759. return self.scopefunc() in self.registry
  760. def set(self, obj):
  761. """Set the value for the current scope."""
  762. self.registry[self.scopefunc()] = obj
  763. def clear(self):
  764. """Clear the current scope, if any."""
  765. try:
  766. del self.registry[self.scopefunc()]
  767. except KeyError:
  768. pass
  769. class ThreadLocalRegistry(ScopedRegistry):
  770. """A :class:`.ScopedRegistry` that uses a ``threading.local()``
  771. variable for storage.
  772. """
  773. def __init__(self, createfunc):
  774. self.createfunc = createfunc
  775. self.registry = threading.local()
  776. def __call__(self):
  777. try:
  778. return self.registry.value
  779. except AttributeError:
  780. val = self.registry.value = self.createfunc()
  781. return val
  782. def has(self):
  783. return hasattr(self.registry, "value")
  784. def set(self, obj):
  785. self.registry.value = obj
  786. def clear(self):
  787. try:
  788. del self.registry.value
  789. except AttributeError:
  790. pass
  791. def has_dupes(sequence, target):
  792. """Given a sequence and search object, return True if there's more
  793. than one, False if zero or one of them.
  794. """
  795. # compare to .index version below, this version introduces less function
  796. # overhead and is usually the same speed. At 15000 items (way bigger than
  797. # a relationship-bound collection in memory usually is) it begins to
  798. # fall behind the other version only by microseconds.
  799. c = 0
  800. for item in sequence:
  801. if item is target:
  802. c += 1
  803. if c > 1:
  804. return True
  805. return False
  806. # .index version. the two __contains__ calls as well
  807. # as .index() and isinstance() slow this down.
  808. # def has_dupes(sequence, target):
  809. # if target not in sequence:
  810. # return False
  811. # elif not isinstance(sequence, collections_abc.Sequence):
  812. # return False
  813. #
  814. # idx = sequence.index(target)
  815. # return target in sequence[idx + 1:]