Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/util/_collections.py: 36%

656 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# util/_collections.py 

2# Copyright (C) 2005-2023 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: https://www.opensource.org/licenses/mit-license.php 

7 

8"""Collection classes and helpers.""" 

9 

10from __future__ import absolute_import 

11 

12import operator 

13import types 

14import weakref 

15 

16from .compat import binary_types 

17from .compat import collections_abc 

18from .compat import itertools_filterfalse 

19from .compat import py2k 

20from .compat import py37 

21from .compat import string_types 

22from .compat import threading 

23 

24 

25EMPTY_SET = frozenset() 

26 

27 

28class ImmutableContainer(object): 

29 def _immutable(self, *arg, **kw): 

30 raise TypeError("%s object is immutable" % self.__class__.__name__) 

31 

32 __delitem__ = __setitem__ = __setattr__ = _immutable 

33 

34 

35def _immutabledict_py_fallback(): 

36 class immutabledict(ImmutableContainer, dict): 

37 

38 clear = ( 

39 pop 

40 ) = popitem = setdefault = update = ImmutableContainer._immutable 

41 

42 def __new__(cls, *args): 

43 new = dict.__new__(cls) 

44 dict.__init__(new, *args) 

45 return new 

46 

47 def __init__(self, *args): 

48 pass 

49 

50 def __reduce__(self): 

51 return _immutabledict_reconstructor, (dict(self),) 

52 

53 def union(self, __d=None): 

54 if not __d: 

55 return self 

56 

57 new = dict.__new__(self.__class__) 

58 dict.__init__(new, self) 

59 dict.update(new, __d) 

60 return new 

61 

62 def _union_w_kw(self, __d=None, **kw): 

63 # not sure if C version works correctly w/ this yet 

64 if not __d and not kw: 

65 return self 

66 

67 new = dict.__new__(self.__class__) 

68 dict.__init__(new, self) 

69 if __d: 

70 dict.update(new, __d) 

71 dict.update(new, kw) 

72 return new 

73 

74 def merge_with(self, *dicts): 

75 new = None 

76 for d in dicts: 

77 if d: 

78 if new is None: 

79 new = dict.__new__(self.__class__) 

80 dict.__init__(new, self) 

81 dict.update(new, d) 

82 if new is None: 

83 return self 

84 

85 return new 

86 

87 def __repr__(self): 

88 return "immutabledict(%s)" % dict.__repr__(self) 

89 

90 return immutabledict 

91 

92 

93try: 

94 from sqlalchemy.cimmutabledict import immutabledict 

95 

96 collections_abc.Mapping.register(immutabledict) 

97 

98except ImportError: 

99 immutabledict = _immutabledict_py_fallback() 

100 

101 def _immutabledict_reconstructor(*arg): 

102 """do the pickle dance""" 

103 return immutabledict(*arg) 

104 

105 

106def coerce_to_immutabledict(d): 

107 if not d: 

108 return EMPTY_DICT 

109 elif isinstance(d, immutabledict): 

110 return d 

111 else: 

112 return immutabledict(d) 

113 

114 

115EMPTY_DICT = immutabledict() 

116 

117 

118class FacadeDict(ImmutableContainer, dict): 

119 """A dictionary that is not publicly mutable.""" 

120 

121 clear = pop = popitem = setdefault = update = ImmutableContainer._immutable 

122 

123 def __new__(cls, *args): 

124 new = dict.__new__(cls) 

125 return new 

126 

127 def copy(self): 

128 raise NotImplementedError( 

129 "an immutabledict shouldn't need to be copied. use dict(d) " 

130 "if you need a mutable dictionary." 

131 ) 

132 

133 def __reduce__(self): 

134 return FacadeDict, (dict(self),) 

135 

136 def _insert_item(self, key, value): 

137 """insert an item into the dictionary directly.""" 

138 dict.__setitem__(self, key, value) 

139 

140 def __repr__(self): 

141 return "FacadeDict(%s)" % dict.__repr__(self) 

142 

143 

144class Properties(object): 

145 """Provide a __getattr__/__setattr__ interface over a dict.""" 

146 

147 __slots__ = ("_data",) 

148 

149 def __init__(self, data): 

150 object.__setattr__(self, "_data", data) 

151 

152 def __len__(self): 

153 return len(self._data) 

154 

155 def __iter__(self): 

156 return iter(list(self._data.values())) 

157 

158 def __dir__(self): 

159 return dir(super(Properties, self)) + [ 

160 str(k) for k in self._data.keys() 

161 ] 

162 

163 def __add__(self, other): 

164 return list(self) + list(other) 

165 

166 def __setitem__(self, key, obj): 

167 self._data[key] = obj 

168 

169 def __getitem__(self, key): 

170 return self._data[key] 

171 

172 def __delitem__(self, key): 

173 del self._data[key] 

174 

175 def __setattr__(self, key, obj): 

176 self._data[key] = obj 

177 

178 def __getstate__(self): 

179 return {"_data": self._data} 

180 

181 def __setstate__(self, state): 

182 object.__setattr__(self, "_data", state["_data"]) 

183 

184 def __getattr__(self, key): 

185 try: 

186 return self._data[key] 

187 except KeyError: 

188 raise AttributeError(key) 

189 

190 def __contains__(self, key): 

191 return key in self._data 

192 

193 def as_immutable(self): 

194 """Return an immutable proxy for this :class:`.Properties`.""" 

195 

196 return ImmutableProperties(self._data) 

197 

198 def update(self, value): 

199 self._data.update(value) 

200 

201 def get(self, key, default=None): 

202 if key in self: 

203 return self[key] 

204 else: 

205 return default 

206 

207 def keys(self): 

208 return list(self._data) 

209 

210 def values(self): 

211 return list(self._data.values()) 

212 

213 def items(self): 

214 return list(self._data.items()) 

215 

216 def has_key(self, key): 

217 return key in self._data 

218 

219 def clear(self): 

220 self._data.clear() 

221 

222 

223class OrderedProperties(Properties): 

224 """Provide a __getattr__/__setattr__ interface with an OrderedDict 

225 as backing store.""" 

226 

227 __slots__ = () 

228 

229 def __init__(self): 

230 Properties.__init__(self, OrderedDict()) 

231 

232 

233class ImmutableProperties(ImmutableContainer, Properties): 

234 """Provide immutable dict/object attribute to an underlying dictionary.""" 

235 

236 __slots__ = () 

237 

238 

239def _ordered_dictionary_sort(d, key=None): 

240 """Sort an OrderedDict in-place.""" 

241 

242 items = [(k, d[k]) for k in sorted(d, key=key)] 

243 

244 d.clear() 

245 

246 d.update(items) 

247 

248 

249if py37: 

250 OrderedDict = dict 

251 sort_dictionary = _ordered_dictionary_sort 

252 

253else: 

254 # prevent sort_dictionary from being used against a plain dictionary 

255 # for Python < 3.7 

256 

257 def sort_dictionary(d, key=None): 

258 """Sort an OrderedDict in place.""" 

259 

260 d._ordered_dictionary_sort(key=key) 

261 

262 class OrderedDict(dict): 

263 """Dictionary that maintains insertion order. 

264 

265 Superseded by Python dict as of Python 3.7 

266 

267 """ 

268 

269 __slots__ = ("_list",) 

270 

271 def _ordered_dictionary_sort(self, key=None): 

272 _ordered_dictionary_sort(self, key=key) 

273 

274 def __reduce__(self): 

275 return OrderedDict, (self.items(),) 

276 

277 def __init__(self, ____sequence=None, **kwargs): 

278 self._list = [] 

279 if ____sequence is None: 

280 if kwargs: 

281 self.update(**kwargs) 

282 else: 

283 self.update(____sequence, **kwargs) 

284 

285 def clear(self): 

286 self._list = [] 

287 dict.clear(self) 

288 

289 def copy(self): 

290 return self.__copy__() 

291 

292 def __copy__(self): 

293 return OrderedDict(self) 

294 

295 def update(self, ____sequence=None, **kwargs): 

296 if ____sequence is not None: 

297 if hasattr(____sequence, "keys"): 

298 for key in ____sequence.keys(): 

299 self.__setitem__(key, ____sequence[key]) 

300 else: 

301 for key, value in ____sequence: 

302 self[key] = value 

303 if kwargs: 

304 self.update(kwargs) 

305 

306 def setdefault(self, key, value): 

307 if key not in self: 

308 self.__setitem__(key, value) 

309 return value 

310 else: 

311 return self.__getitem__(key) 

312 

313 def __iter__(self): 

314 return iter(self._list) 

315 

316 def keys(self): 

317 return list(self) 

318 

319 def values(self): 

320 return [self[key] for key in self._list] 

321 

322 def items(self): 

323 return [(key, self[key]) for key in self._list] 

324 

325 if py2k: 

326 

327 def itervalues(self): 

328 return iter(self.values()) 

329 

330 def iterkeys(self): 

331 return iter(self) 

332 

333 def iteritems(self): 

334 return iter(self.items()) 

335 

336 def __setitem__(self, key, obj): 

337 if key not in self: 

338 try: 

339 self._list.append(key) 

340 except AttributeError: 

341 # work around Python pickle loads() with 

342 # dict subclass (seems to ignore __setstate__?) 

343 self._list = [key] 

344 dict.__setitem__(self, key, obj) 

345 

346 def __delitem__(self, key): 

347 dict.__delitem__(self, key) 

348 self._list.remove(key) 

349 

350 def pop(self, key, *default): 

351 present = key in self 

352 value = dict.pop(self, key, *default) 

353 if present: 

354 self._list.remove(key) 

355 return value 

356 

357 def popitem(self): 

358 item = dict.popitem(self) 

359 self._list.remove(item[0]) 

360 return item 

361 

362 

363class OrderedSet(set): 

364 def __init__(self, d=None): 

365 set.__init__(self) 

366 if d is not None: 

367 self._list = unique_list(d) 

368 set.update(self, self._list) 

369 else: 

370 self._list = [] 

371 

372 def add(self, element): 

373 if element not in self: 

374 self._list.append(element) 

375 set.add(self, element) 

376 

377 def remove(self, element): 

378 set.remove(self, element) 

379 self._list.remove(element) 

380 

381 def insert(self, pos, element): 

382 if element not in self: 

383 self._list.insert(pos, element) 

384 set.add(self, element) 

385 

386 def discard(self, element): 

387 if element in self: 

388 self._list.remove(element) 

389 set.remove(self, element) 

390 

391 def clear(self): 

392 set.clear(self) 

393 self._list = [] 

394 

395 def __getitem__(self, key): 

396 return self._list[key] 

397 

398 def __iter__(self): 

399 return iter(self._list) 

400 

401 def __add__(self, other): 

402 return self.union(other) 

403 

404 def __repr__(self): 

405 return "%s(%r)" % (self.__class__.__name__, self._list) 

406 

407 __str__ = __repr__ 

408 

409 def update(self, iterable): 

410 for e in iterable: 

411 if e not in self: 

412 self._list.append(e) 

413 set.add(self, e) 

414 return self 

415 

416 __ior__ = update 

417 

418 def union(self, other): 

419 result = self.__class__(self) 

420 result.update(other) 

421 return result 

422 

423 __or__ = union 

424 

425 def intersection(self, other): 

426 other = set(other) 

427 return self.__class__(a for a in self if a in other) 

428 

429 __and__ = intersection 

430 

431 def symmetric_difference(self, other): 

432 other = set(other) 

433 result = self.__class__(a for a in self if a not in other) 

434 result.update(a for a in other if a not in self) 

435 return result 

436 

437 __xor__ = symmetric_difference 

438 

439 def difference(self, other): 

440 other = set(other) 

441 return self.__class__(a for a in self if a not in other) 

442 

443 __sub__ = difference 

444 

445 def intersection_update(self, other): 

446 other = set(other) 

447 set.intersection_update(self, other) 

448 self._list = [a for a in self._list if a in other] 

449 return self 

450 

451 __iand__ = intersection_update 

452 

453 def symmetric_difference_update(self, other): 

454 set.symmetric_difference_update(self, other) 

455 self._list = [a for a in self._list if a in self] 

456 self._list += [a for a in other._list if a in self] 

457 return self 

458 

459 __ixor__ = symmetric_difference_update 

460 

461 def difference_update(self, other): 

462 set.difference_update(self, other) 

463 self._list = [a for a in self._list if a in self] 

464 return self 

465 

466 __isub__ = difference_update 

467 

468 

469class IdentitySet(object): 

470 """A set that considers only object id() for uniqueness. 

471 

472 This strategy has edge cases for builtin types- it's possible to have 

473 two 'foo' strings in one of these sets, for example. Use sparingly. 

474 

475 """ 

476 

477 def __init__(self, iterable=None): 

478 self._members = dict() 

479 if iterable: 

480 self.update(iterable) 

481 

482 def add(self, value): 

483 self._members[id(value)] = value 

484 

485 def __contains__(self, value): 

486 return id(value) in self._members 

487 

488 def remove(self, value): 

489 del self._members[id(value)] 

490 

491 def discard(self, value): 

492 try: 

493 self.remove(value) 

494 except KeyError: 

495 pass 

496 

497 def pop(self): 

498 try: 

499 pair = self._members.popitem() 

500 return pair[1] 

501 except KeyError: 

502 raise KeyError("pop from an empty set") 

503 

504 def clear(self): 

505 self._members.clear() 

506 

507 def __cmp__(self, other): 

508 raise TypeError("cannot compare sets using cmp()") 

509 

510 def __eq__(self, other): 

511 if isinstance(other, IdentitySet): 

512 return self._members == other._members 

513 else: 

514 return False 

515 

516 def __ne__(self, other): 

517 if isinstance(other, IdentitySet): 

518 return self._members != other._members 

519 else: 

520 return True 

521 

522 def issubset(self, iterable): 

523 if isinstance(iterable, self.__class__): 

524 other = iterable 

525 else: 

526 other = self.__class__(iterable) 

527 

528 if len(self) > len(other): 

529 return False 

530 for m in itertools_filterfalse( 

531 other._members.__contains__, iter(self._members.keys()) 

532 ): 

533 return False 

534 return True 

535 

536 def __le__(self, other): 

537 if not isinstance(other, IdentitySet): 

538 return NotImplemented 

539 return self.issubset(other) 

540 

541 def __lt__(self, other): 

542 if not isinstance(other, IdentitySet): 

543 return NotImplemented 

544 return len(self) < len(other) and self.issubset(other) 

545 

546 def issuperset(self, iterable): 

547 if isinstance(iterable, self.__class__): 

548 other = iterable 

549 else: 

550 other = self.__class__(iterable) 

551 

552 if len(self) < len(other): 

553 return False 

554 

555 for m in itertools_filterfalse( 

556 self._members.__contains__, iter(other._members.keys()) 

557 ): 

558 return False 

559 return True 

560 

561 def __ge__(self, other): 

562 if not isinstance(other, IdentitySet): 

563 return NotImplemented 

564 return self.issuperset(other) 

565 

566 def __gt__(self, other): 

567 if not isinstance(other, IdentitySet): 

568 return NotImplemented 

569 return len(self) > len(other) and self.issuperset(other) 

570 

571 def union(self, iterable): 

572 result = self.__class__() 

573 members = self._members 

574 result._members.update(members) 

575 result._members.update((id(obj), obj) for obj in iterable) 

576 return result 

577 

578 def __or__(self, other): 

579 if not isinstance(other, IdentitySet): 

580 return NotImplemented 

581 return self.union(other) 

582 

583 def update(self, iterable): 

584 self._members.update((id(obj), obj) for obj in iterable) 

585 

586 def __ior__(self, other): 

587 if not isinstance(other, IdentitySet): 

588 return NotImplemented 

589 self.update(other) 

590 return self 

591 

592 def difference(self, iterable): 

593 result = self.__class__() 

594 members = self._members 

595 if isinstance(iterable, self.__class__): 

596 other = set(iterable._members.keys()) 

597 else: 

598 other = {id(obj) for obj in iterable} 

599 result._members.update( 

600 ((k, v) for k, v in members.items() if k not in other) 

601 ) 

602 return result 

603 

604 def __sub__(self, other): 

605 if not isinstance(other, IdentitySet): 

606 return NotImplemented 

607 return self.difference(other) 

608 

609 def difference_update(self, iterable): 

610 self._members = self.difference(iterable)._members 

611 

612 def __isub__(self, other): 

613 if not isinstance(other, IdentitySet): 

614 return NotImplemented 

615 self.difference_update(other) 

616 return self 

617 

618 def intersection(self, iterable): 

619 result = self.__class__() 

620 members = self._members 

621 if isinstance(iterable, self.__class__): 

622 other = set(iterable._members.keys()) 

623 else: 

624 other = {id(obj) for obj in iterable} 

625 result._members.update( 

626 (k, v) for k, v in members.items() if k in other 

627 ) 

628 return result 

629 

630 def __and__(self, other): 

631 if not isinstance(other, IdentitySet): 

632 return NotImplemented 

633 return self.intersection(other) 

634 

635 def intersection_update(self, iterable): 

636 self._members = self.intersection(iterable)._members 

637 

638 def __iand__(self, other): 

639 if not isinstance(other, IdentitySet): 

640 return NotImplemented 

641 self.intersection_update(other) 

642 return self 

643 

644 def symmetric_difference(self, iterable): 

645 result = self.__class__() 

646 members = self._members 

647 if isinstance(iterable, self.__class__): 

648 other = iterable._members 

649 else: 

650 other = {id(obj): obj for obj in iterable} 

651 result._members.update( 

652 ((k, v) for k, v in members.items() if k not in other) 

653 ) 

654 result._members.update( 

655 ((k, v) for k, v in other.items() if k not in members) 

656 ) 

657 return result 

658 

659 def __xor__(self, other): 

660 if not isinstance(other, IdentitySet): 

661 return NotImplemented 

662 return self.symmetric_difference(other) 

663 

664 def symmetric_difference_update(self, iterable): 

665 self._members = self.symmetric_difference(iterable)._members 

666 

667 def __ixor__(self, other): 

668 if not isinstance(other, IdentitySet): 

669 return NotImplemented 

670 self.symmetric_difference(other) 

671 return self 

672 

673 def copy(self): 

674 return type(self)(iter(self._members.values())) 

675 

676 __copy__ = copy 

677 

678 def __len__(self): 

679 return len(self._members) 

680 

681 def __iter__(self): 

682 return iter(self._members.values()) 

683 

684 def __hash__(self): 

685 raise TypeError("set objects are unhashable") 

686 

687 def __repr__(self): 

688 return "%s(%r)" % (type(self).__name__, list(self._members.values())) 

689 

690 

691class WeakSequence(object): 

692 def __init__(self, __elements=()): 

693 # adapted from weakref.WeakKeyDictionary, prevent reference 

694 # cycles in the collection itself 

695 def _remove(item, selfref=weakref.ref(self)): 

696 self = selfref() 

697 if self is not None: 

698 self._storage.remove(item) 

699 

700 self._remove = _remove 

701 self._storage = [ 

702 weakref.ref(element, _remove) for element in __elements 

703 ] 

704 

705 def append(self, item): 

706 self._storage.append(weakref.ref(item, self._remove)) 

707 

708 def __len__(self): 

709 return len(self._storage) 

710 

711 def __iter__(self): 

712 return ( 

713 obj for obj in (ref() for ref in self._storage) if obj is not None 

714 ) 

715 

716 def __getitem__(self, index): 

717 try: 

718 obj = self._storage[index] 

719 except KeyError: 

720 raise IndexError("Index %s out of range" % index) 

721 else: 

722 return obj() 

723 

724 

725class OrderedIdentitySet(IdentitySet): 

726 def __init__(self, iterable=None): 

727 IdentitySet.__init__(self) 

728 self._members = OrderedDict() 

729 if iterable: 

730 for o in iterable: 

731 self.add(o) 

732 

733 

734class PopulateDict(dict): 

735 """A dict which populates missing values via a creation function. 

736 

737 Note the creation function takes a key, unlike 

738 collections.defaultdict. 

739 

740 """ 

741 

742 def __init__(self, creator): 

743 self.creator = creator 

744 

745 def __missing__(self, key): 

746 self[key] = val = self.creator(key) 

747 return val 

748 

749 

750class WeakPopulateDict(dict): 

751 """Like PopulateDict, but assumes a self + a method and does not create 

752 a reference cycle. 

753 

754 """ 

755 

756 def __init__(self, creator_method): 

757 self.creator = creator_method.__func__ 

758 weakself = creator_method.__self__ 

759 self.weakself = weakref.ref(weakself) 

760 

761 def __missing__(self, key): 

762 self[key] = val = self.creator(self.weakself(), key) 

763 return val 

764 

765 

766# Define collections that are capable of storing 

767# ColumnElement objects as hashable keys/elements. 

768# At this point, these are mostly historical, things 

769# used to be more complicated. 

770column_set = set 

771column_dict = dict 

772ordered_column_set = OrderedSet 

773 

774 

775_getters = PopulateDict(operator.itemgetter) 

776 

777_property_getters = PopulateDict( 

778 lambda idx: property(operator.itemgetter(idx)) 

779) 

780 

781 

782def unique_list(seq, hashfunc=None): 

783 seen = set() 

784 seen_add = seen.add 

785 if not hashfunc: 

786 return [x for x in seq if x not in seen and not seen_add(x)] 

787 else: 

788 return [ 

789 x 

790 for x in seq 

791 if hashfunc(x) not in seen and not seen_add(hashfunc(x)) 

792 ] 

793 

794 

795class UniqueAppender(object): 

796 """Appends items to a collection ensuring uniqueness. 

797 

798 Additional appends() of the same object are ignored. Membership is 

799 determined by identity (``is a``) not equality (``==``). 

800 """ 

801 

802 def __init__(self, data, via=None): 

803 self.data = data 

804 self._unique = {} 

805 if via: 

806 self._data_appender = getattr(data, via) 

807 elif hasattr(data, "append"): 

808 self._data_appender = data.append 

809 elif hasattr(data, "add"): 

810 self._data_appender = data.add 

811 

812 def append(self, item): 

813 id_ = id(item) 

814 if id_ not in self._unique: 

815 self._data_appender(item) 

816 self._unique[id_] = True 

817 

818 def __iter__(self): 

819 return iter(self.data) 

820 

821 

822def coerce_generator_arg(arg): 

823 if len(arg) == 1 and isinstance(arg[0], types.GeneratorType): 

824 return list(arg[0]) 

825 else: 

826 return arg 

827 

828 

829def to_list(x, default=None): 

830 if x is None: 

831 return default 

832 if not isinstance(x, collections_abc.Iterable) or isinstance( 

833 x, string_types + binary_types 

834 ): 

835 return [x] 

836 elif isinstance(x, list): 

837 return x 

838 else: 

839 return list(x) 

840 

841 

842def has_intersection(set_, iterable): 

843 r"""return True if any items of set\_ are present in iterable. 

844 

845 Goes through special effort to ensure __hash__ is not called 

846 on items in iterable that don't support it. 

847 

848 """ 

849 # TODO: optimize, write in C, etc. 

850 return bool(set_.intersection([i for i in iterable if i.__hash__])) 

851 

852 

853def to_set(x): 

854 if x is None: 

855 return set() 

856 if not isinstance(x, set): 

857 return set(to_list(x)) 

858 else: 

859 return x 

860 

861 

862def to_column_set(x): 

863 if x is None: 

864 return column_set() 

865 if not isinstance(x, column_set): 

866 return column_set(to_list(x)) 

867 else: 

868 return x 

869 

870 

871def update_copy(d, _new=None, **kw): 

872 """Copy the given dict and update with the given values.""" 

873 

874 d = d.copy() 

875 if _new: 

876 d.update(_new) 

877 d.update(**kw) 

878 return d 

879 

880 

881def flatten_iterator(x): 

882 """Given an iterator of which further sub-elements may also be 

883 iterators, flatten the sub-elements into a single iterator. 

884 

885 """ 

886 for elem in x: 

887 if not isinstance(elem, str) and hasattr(elem, "__iter__"): 

888 for y in flatten_iterator(elem): 

889 yield y 

890 else: 

891 yield elem 

892 

893 

894class LRUCache(dict): 

895 """Dictionary with 'squishy' removal of least 

896 recently used items. 

897 

898 Note that either get() or [] should be used here, but 

899 generally its not safe to do an "in" check first as the dictionary 

900 can change subsequent to that call. 

901 

902 """ 

903 

904 __slots__ = "capacity", "threshold", "size_alert", "_counter", "_mutex" 

905 

906 def __init__(self, capacity=100, threshold=0.5, size_alert=None): 

907 self.capacity = capacity 

908 self.threshold = threshold 

909 self.size_alert = size_alert 

910 self._counter = 0 

911 self._mutex = threading.Lock() 

912 

913 def _inc_counter(self): 

914 self._counter += 1 

915 return self._counter 

916 

917 def get(self, key, default=None): 

918 item = dict.get(self, key, default) 

919 if item is not default: 

920 item[2] = self._inc_counter() 

921 return item[1] 

922 else: 

923 return default 

924 

925 def __getitem__(self, key): 

926 item = dict.__getitem__(self, key) 

927 item[2] = self._inc_counter() 

928 return item[1] 

929 

930 def values(self): 

931 return [i[1] for i in dict.values(self)] 

932 

933 def setdefault(self, key, value): 

934 if key in self: 

935 return self[key] 

936 else: 

937 self[key] = value 

938 return value 

939 

940 def __setitem__(self, key, value): 

941 item = dict.get(self, key) 

942 if item is None: 

943 item = [key, value, self._inc_counter()] 

944 dict.__setitem__(self, key, item) 

945 else: 

946 item[1] = value 

947 self._manage_size() 

948 

949 @property 

950 def size_threshold(self): 

951 return self.capacity + self.capacity * self.threshold 

952 

953 def _manage_size(self): 

954 if not self._mutex.acquire(False): 

955 return 

956 try: 

957 size_alert = bool(self.size_alert) 

958 while len(self) > self.capacity + self.capacity * self.threshold: 

959 if size_alert: 

960 size_alert = False 

961 self.size_alert(self) 

962 by_counter = sorted( 

963 dict.values(self), key=operator.itemgetter(2), reverse=True 

964 ) 

965 for item in by_counter[self.capacity :]: 

966 try: 

967 del self[item[0]] 

968 except KeyError: 

969 # deleted elsewhere; skip 

970 continue 

971 finally: 

972 self._mutex.release() 

973 

974 

975class ScopedRegistry(object): 

976 """A Registry that can store one or multiple instances of a single 

977 class on the basis of a "scope" function. 

978 

979 The object implements ``__call__`` as the "getter", so by 

980 calling ``myregistry()`` the contained object is returned 

981 for the current scope. 

982 

983 :param createfunc: 

984 a callable that returns a new object to be placed in the registry 

985 

986 :param scopefunc: 

987 a callable that will return a key to store/retrieve an object. 

988 """ 

989 

990 def __init__(self, createfunc, scopefunc): 

991 """Construct a new :class:`.ScopedRegistry`. 

992 

993 :param createfunc: A creation function that will generate 

994 a new value for the current scope, if none is present. 

995 

996 :param scopefunc: A function that returns a hashable 

997 token representing the current scope (such as, current 

998 thread identifier). 

999 

1000 """ 

1001 self.createfunc = createfunc 

1002 self.scopefunc = scopefunc 

1003 self.registry = {} 

1004 

1005 def __call__(self): 

1006 key = self.scopefunc() 

1007 try: 

1008 return self.registry[key] 

1009 except KeyError: 

1010 return self.registry.setdefault(key, self.createfunc()) 

1011 

1012 def has(self): 

1013 """Return True if an object is present in the current scope.""" 

1014 

1015 return self.scopefunc() in self.registry 

1016 

1017 def set(self, obj): 

1018 """Set the value for the current scope.""" 

1019 

1020 self.registry[self.scopefunc()] = obj 

1021 

1022 def clear(self): 

1023 """Clear the current scope, if any.""" 

1024 

1025 try: 

1026 del self.registry[self.scopefunc()] 

1027 except KeyError: 

1028 pass 

1029 

1030 

1031class ThreadLocalRegistry(ScopedRegistry): 

1032 """A :class:`.ScopedRegistry` that uses a ``threading.local()`` 

1033 variable for storage. 

1034 

1035 """ 

1036 

1037 def __init__(self, createfunc): 

1038 self.createfunc = createfunc 

1039 self.registry = threading.local() 

1040 

1041 def __call__(self): 

1042 try: 

1043 return self.registry.value 

1044 except AttributeError: 

1045 val = self.registry.value = self.createfunc() 

1046 return val 

1047 

1048 def has(self): 

1049 return hasattr(self.registry, "value") 

1050 

1051 def set(self, obj): 

1052 self.registry.value = obj 

1053 

1054 def clear(self): 

1055 try: 

1056 del self.registry.value 

1057 except AttributeError: 

1058 pass 

1059 

1060 

1061def has_dupes(sequence, target): 

1062 """Given a sequence and search object, return True if there's more 

1063 than one, False if zero or one of them. 

1064 

1065 

1066 """ 

1067 # compare to .index version below, this version introduces less function 

1068 # overhead and is usually the same speed. At 15000 items (way bigger than 

1069 # a relationship-bound collection in memory usually is) it begins to 

1070 # fall behind the other version only by microseconds. 

1071 c = 0 

1072 for item in sequence: 

1073 if item is target: 

1074 c += 1 

1075 if c > 1: 

1076 return True 

1077 return False 

1078 

1079 

1080# .index version. the two __contains__ calls as well 

1081# as .index() and isinstance() slow this down. 

1082# def has_dupes(sequence, target): 

1083# if target not in sequence: 

1084# return False 

1085# elif not isinstance(sequence, collections_abc.Sequence): 

1086# return False 

1087# 

1088# idx = sequence.index(target) 

1089# return target in sequence[idx + 1:]