Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1############################################################################## 

2# 

3# Copyright (c) 2006 Zope Foundation and Contributors. 

4# All Rights Reserved. 

5# 

6# This software is subject to the provisions of the Zope Public License, 

7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 

8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 

9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 

11# FOR A PARTICULAR PURPOSE. 

12# 

13############################################################################## 

14"""Basic components support 

15""" 

16from collections import defaultdict 

17 

18try: 

19 from zope.event import notify 

20except ImportError: # pragma: no cover 

21 def notify(*arg, **kw): pass 

22 

23from zope.interface.interfaces import ISpecification 

24from zope.interface.interfaces import ComponentLookupError 

25from zope.interface.interfaces import IAdapterRegistration 

26from zope.interface.interfaces import IComponents 

27from zope.interface.interfaces import IHandlerRegistration 

28from zope.interface.interfaces import ISubscriptionAdapterRegistration 

29from zope.interface.interfaces import IUtilityRegistration 

30from zope.interface.interfaces import Registered 

31from zope.interface.interfaces import Unregistered 

32 

33from zope.interface.interface import Interface 

34from zope.interface.declarations import implementedBy 

35from zope.interface.declarations import implementer 

36from zope.interface.declarations import implementer_only 

37from zope.interface.declarations import providedBy 

38from zope.interface.adapter import AdapterRegistry 

39from zope.interface._compat import CLASS_TYPES 

40from zope.interface._compat import STRING_TYPES 

41 

42__all__ = [ 

43 # Components is public API, but 

44 # the *Registration classes are just implementations 

45 # of public interfaces. 

46 'Components', 

47] 

48 

49class _UnhashableComponentCounter(object): 

50 # defaultdict(int)-like object for unhashable components 

51 

52 def __init__(self, otherdict): 

53 # [(component, count)] 

54 self._data = [item for item in otherdict.items()] 

55 

56 def __getitem__(self, key): 

57 for component, count in self._data: 

58 if component == key: 

59 return count 

60 return 0 

61 

62 def __setitem__(self, component, count): 

63 for i, data in enumerate(self._data): 

64 if data[0] == component: 

65 self._data[i] = component, count 

66 return 

67 self._data.append((component, count)) 

68 

69 def __delitem__(self, component): 

70 for i, data in enumerate(self._data): 

71 if data[0] == component: 

72 del self._data[i] 

73 return 

74 raise KeyError(component) # pragma: no cover 

75 

76def _defaultdict_int(): 

77 return defaultdict(int) 

78 

79class _UtilityRegistrations(object): 

80 

81 def __init__(self, utilities, utility_registrations): 

82 # {provided -> {component: count}} 

83 self._cache = defaultdict(_defaultdict_int) 

84 self._utilities = utilities 

85 self._utility_registrations = utility_registrations 

86 

87 self.__populate_cache() 

88 

89 def __populate_cache(self): 

90 for ((p, _), data) in iter(self._utility_registrations.items()): 

91 component = data[0] 

92 self.__cache_utility(p, component) 

93 

94 def __cache_utility(self, provided, component): 

95 try: 

96 self._cache[provided][component] += 1 

97 except TypeError: 

98 # The component is not hashable, and we have a dict. Switch to a strategy 

99 # that doesn't use hashing. 

100 prov = self._cache[provided] = _UnhashableComponentCounter(self._cache[provided]) 

101 prov[component] += 1 

102 

103 def __uncache_utility(self, provided, component): 

104 provided = self._cache[provided] 

105 # It seems like this line could raise a TypeError if component isn't 

106 # hashable and we haven't yet switched to _UnhashableComponentCounter. However, 

107 # we can't actually get in that situation. In order to get here, we would 

108 # have had to cache the utility already which would have switched 

109 # the datastructure if needed. 

110 count = provided[component] 

111 count -= 1 

112 if count == 0: 

113 del provided[component] 

114 else: 

115 provided[component] = count 

116 return count > 0 

117 

118 def _is_utility_subscribed(self, provided, component): 

119 try: 

120 return self._cache[provided][component] > 0 

121 except TypeError: 

122 # Not hashable and we're still using a dict 

123 return False 

124 

125 def registerUtility(self, provided, name, component, info, factory): 

126 subscribed = self._is_utility_subscribed(provided, component) 

127 

128 self._utility_registrations[(provided, name)] = component, info, factory 

129 self._utilities.register((), provided, name, component) 

130 

131 if not subscribed: 

132 self._utilities.subscribe((), provided, component) 

133 

134 self.__cache_utility(provided, component) 

135 

136 def unregisterUtility(self, provided, name, component): 

137 del self._utility_registrations[(provided, name)] 

138 self._utilities.unregister((), provided, name) 

139 

140 subscribed = self.__uncache_utility(provided, component) 

141 

142 if not subscribed: 

143 self._utilities.unsubscribe((), provided, component) 

144 

145 

146@implementer(IComponents) 

147class Components(object): 

148 

149 _v_utility_registrations_cache = None 

150 

151 def __init__(self, name='', bases=()): 

152 # __init__ is used for test cleanup as well as initialization. 

153 # XXX add a separate API for test cleanup. 

154 assert isinstance(name, STRING_TYPES) 

155 self.__name__ = name 

156 self._init_registries() 

157 self._init_registrations() 

158 self.__bases__ = tuple(bases) 

159 self._v_utility_registrations_cache = None 

160 

161 def __repr__(self): 

162 return "<%s %s>" % (self.__class__.__name__, self.__name__) 

163 

164 def __reduce__(self): 

165 # Mimic what a persistent.Persistent object does and elide 

166 # _v_ attributes so that they don't get saved in ZODB. 

167 # This allows us to store things that cannot be pickled in such 

168 # attributes. 

169 reduction = super(Components, self).__reduce__() 

170 # (callable, args, state, listiter, dictiter) 

171 # We assume the state is always a dict; the last three items 

172 # are technically optional and can be missing or None. 

173 filtered_state = {k: v for k, v in reduction[2].items() 

174 if not k.startswith('_v_')} 

175 reduction = list(reduction) 

176 reduction[2] = filtered_state 

177 return tuple(reduction) 

178 

179 def _init_registries(self): 

180 # Subclasses have never been required to call this method 

181 # if they override it, merely to fill in these two attributes. 

182 self.adapters = AdapterRegistry() 

183 self.utilities = AdapterRegistry() 

184 

185 def _init_registrations(self): 

186 self._utility_registrations = {} 

187 self._adapter_registrations = {} 

188 self._subscription_registrations = [] 

189 self._handler_registrations = [] 

190 

191 @property 

192 def _utility_registrations_cache(self): 

193 # We use a _v_ attribute internally so that data aren't saved in ZODB, 

194 # because this object cannot be pickled. 

195 cache = self._v_utility_registrations_cache 

196 if (cache is None 

197 or cache._utilities is not self.utilities 

198 or cache._utility_registrations is not self._utility_registrations): 

199 cache = self._v_utility_registrations_cache = _UtilityRegistrations( 

200 self.utilities, 

201 self._utility_registrations) 

202 return cache 

203 

204 def _getBases(self): 

205 # Subclasses might override 

206 return self.__dict__.get('__bases__', ()) 

207 

208 def _setBases(self, bases): 

209 # Subclasses might override 

210 self.adapters.__bases__ = tuple([ 

211 base.adapters for base in bases]) 

212 self.utilities.__bases__ = tuple([ 

213 base.utilities for base in bases]) 

214 self.__dict__['__bases__'] = tuple(bases) 

215 

216 __bases__ = property( 

217 lambda self: self._getBases(), 

218 lambda self, bases: self._setBases(bases), 

219 ) 

220 

221 def registerUtility(self, component=None, provided=None, name=u'', 

222 info=u'', event=True, factory=None): 

223 if factory: 

224 if component: 

225 raise TypeError("Can't specify factory and component.") 

226 component = factory() 

227 

228 if provided is None: 

229 provided = _getUtilityProvided(component) 

230 

231 if name == u'': 

232 name = _getName(component) 

233 

234 reg = self._utility_registrations.get((provided, name)) 

235 if reg is not None: 

236 if reg[:2] == (component, info): 

237 # already registered 

238 return 

239 self.unregisterUtility(reg[0], provided, name) 

240 

241 self._utility_registrations_cache.registerUtility( 

242 provided, name, component, info, factory) 

243 

244 if event: 

245 notify(Registered( 

246 UtilityRegistration(self, provided, name, component, info, 

247 factory) 

248 )) 

249 

250 def unregisterUtility(self, component=None, provided=None, name=u'', 

251 factory=None): 

252 if factory: 

253 if component: 

254 raise TypeError("Can't specify factory and component.") 

255 component = factory() 

256 

257 if provided is None: 

258 if component is None: 

259 raise TypeError("Must specify one of component, factory and " 

260 "provided") 

261 provided = _getUtilityProvided(component) 

262 

263 old = self._utility_registrations.get((provided, name)) 

264 if (old is None) or ((component is not None) and 

265 (component != old[0])): 

266 return False 

267 

268 if component is None: 

269 component = old[0] 

270 

271 # Note that component is now the old thing registered 

272 self._utility_registrations_cache.unregisterUtility( 

273 provided, name, component) 

274 

275 notify(Unregistered( 

276 UtilityRegistration(self, provided, name, component, *old[1:]) 

277 )) 

278 

279 return True 

280 

281 def registeredUtilities(self): 

282 for ((provided, name), data 

283 ) in iter(self._utility_registrations.items()): 

284 yield UtilityRegistration(self, provided, name, *data) 

285 

286 def queryUtility(self, provided, name=u'', default=None): 

287 return self.utilities.lookup((), provided, name, default) 

288 

289 def getUtility(self, provided, name=u''): 

290 utility = self.utilities.lookup((), provided, name) 

291 if utility is None: 

292 raise ComponentLookupError(provided, name) 

293 return utility 

294 

295 def getUtilitiesFor(self, interface): 

296 for name, utility in self.utilities.lookupAll((), interface): 

297 yield name, utility 

298 

299 def getAllUtilitiesRegisteredFor(self, interface): 

300 return self.utilities.subscriptions((), interface) 

301 

302 def registerAdapter(self, factory, required=None, provided=None, 

303 name=u'', info=u'', event=True): 

304 if provided is None: 

305 provided = _getAdapterProvided(factory) 

306 required = _getAdapterRequired(factory, required) 

307 if name == u'': 

308 name = _getName(factory) 

309 self._adapter_registrations[(required, provided, name) 

310 ] = factory, info 

311 self.adapters.register(required, provided, name, factory) 

312 

313 if event: 

314 notify(Registered( 

315 AdapterRegistration(self, required, provided, name, 

316 factory, info) 

317 )) 

318 

319 

320 def unregisterAdapter(self, factory=None, 

321 required=None, provided=None, name=u'', 

322 ): 

323 if provided is None: 

324 if factory is None: 

325 raise TypeError("Must specify one of factory and provided") 

326 provided = _getAdapterProvided(factory) 

327 

328 if (required is None) and (factory is None): 

329 raise TypeError("Must specify one of factory and required") 

330 

331 required = _getAdapterRequired(factory, required) 

332 old = self._adapter_registrations.get((required, provided, name)) 

333 if (old is None) or ((factory is not None) and 

334 (factory != old[0])): 

335 return False 

336 

337 del self._adapter_registrations[(required, provided, name)] 

338 self.adapters.unregister(required, provided, name) 

339 

340 notify(Unregistered( 

341 AdapterRegistration(self, required, provided, name, 

342 *old) 

343 )) 

344 

345 return True 

346 

347 def registeredAdapters(self): 

348 for ((required, provided, name), (component, info) 

349 ) in iter(self._adapter_registrations.items()): 

350 yield AdapterRegistration(self, required, provided, name, 

351 component, info) 

352 

353 def queryAdapter(self, object, interface, name=u'', default=None): 

354 return self.adapters.queryAdapter(object, interface, name, default) 

355 

356 def getAdapter(self, object, interface, name=u''): 

357 adapter = self.adapters.queryAdapter(object, interface, name) 

358 if adapter is None: 

359 raise ComponentLookupError(object, interface, name) 

360 return adapter 

361 

362 def queryMultiAdapter(self, objects, interface, name=u'', 

363 default=None): 

364 return self.adapters.queryMultiAdapter( 

365 objects, interface, name, default) 

366 

367 def getMultiAdapter(self, objects, interface, name=u''): 

368 adapter = self.adapters.queryMultiAdapter(objects, interface, name) 

369 if adapter is None: 

370 raise ComponentLookupError(objects, interface, name) 

371 return adapter 

372 

373 def getAdapters(self, objects, provided): 

374 for name, factory in self.adapters.lookupAll( 

375 list(map(providedBy, objects)), 

376 provided): 

377 adapter = factory(*objects) 

378 if adapter is not None: 

379 yield name, adapter 

380 

381 def registerSubscriptionAdapter(self, 

382 factory, required=None, provided=None, 

383 name=u'', info=u'', 

384 event=True): 

385 if name: 

386 raise TypeError("Named subscribers are not yet supported") 

387 if provided is None: 

388 provided = _getAdapterProvided(factory) 

389 required = _getAdapterRequired(factory, required) 

390 self._subscription_registrations.append( 

391 (required, provided, name, factory, info) 

392 ) 

393 self.adapters.subscribe(required, provided, factory) 

394 

395 if event: 

396 notify(Registered( 

397 SubscriptionRegistration(self, required, provided, name, 

398 factory, info) 

399 )) 

400 

401 def registeredSubscriptionAdapters(self): 

402 for data in self._subscription_registrations: 

403 yield SubscriptionRegistration(self, *data) 

404 

405 def unregisterSubscriptionAdapter(self, factory=None, 

406 required=None, provided=None, name=u'', 

407 ): 

408 if name: 

409 raise TypeError("Named subscribers are not yet supported") 

410 if provided is None: 

411 if factory is None: 

412 raise TypeError("Must specify one of factory and provided") 

413 provided = _getAdapterProvided(factory) 

414 

415 if (required is None) and (factory is None): 

416 raise TypeError("Must specify one of factory and required") 

417 

418 required = _getAdapterRequired(factory, required) 

419 

420 if factory is None: 

421 new = [(r, p, n, f, i) 

422 for (r, p, n, f, i) 

423 in self._subscription_registrations 

424 if not (r == required and p == provided) 

425 ] 

426 else: 

427 new = [(r, p, n, f, i) 

428 for (r, p, n, f, i) 

429 in self._subscription_registrations 

430 if not (r == required and p == provided and f == factory) 

431 ] 

432 

433 if len(new) == len(self._subscription_registrations): 

434 return False 

435 

436 

437 self._subscription_registrations[:] = new 

438 self.adapters.unsubscribe(required, provided, factory) 

439 

440 notify(Unregistered( 

441 SubscriptionRegistration(self, required, provided, name, 

442 factory, '') 

443 )) 

444 

445 return True 

446 

447 def subscribers(self, objects, provided): 

448 return self.adapters.subscribers(objects, provided) 

449 

450 def registerHandler(self, 

451 factory, required=None, 

452 name=u'', info=u'', 

453 event=True): 

454 if name: 

455 raise TypeError("Named handlers are not yet supported") 

456 required = _getAdapterRequired(factory, required) 

457 self._handler_registrations.append( 

458 (required, name, factory, info) 

459 ) 

460 self.adapters.subscribe(required, None, factory) 

461 

462 if event: 

463 notify(Registered( 

464 HandlerRegistration(self, required, name, factory, info) 

465 )) 

466 

467 def registeredHandlers(self): 

468 for data in self._handler_registrations: 

469 yield HandlerRegistration(self, *data) 

470 

471 def unregisterHandler(self, factory=None, required=None, name=u''): 

472 if name: 

473 raise TypeError("Named subscribers are not yet supported") 

474 

475 if (required is None) and (factory is None): 

476 raise TypeError("Must specify one of factory and required") 

477 

478 required = _getAdapterRequired(factory, required) 

479 

480 if factory is None: 

481 new = [(r, n, f, i) 

482 for (r, n, f, i) 

483 in self._handler_registrations 

484 if r != required 

485 ] 

486 else: 

487 new = [(r, n, f, i) 

488 for (r, n, f, i) 

489 in self._handler_registrations 

490 if not (r == required and f == factory) 

491 ] 

492 

493 if len(new) == len(self._handler_registrations): 

494 return False 

495 

496 self._handler_registrations[:] = new 

497 self.adapters.unsubscribe(required, None, factory) 

498 

499 notify(Unregistered( 

500 HandlerRegistration(self, required, name, factory, '') 

501 )) 

502 

503 return True 

504 

505 def handle(self, *objects): 

506 self.adapters.subscribers(objects, None) 

507 

508 def rebuildUtilityRegistryFromLocalCache(self, rebuild=False): 

509 """ 

510 Emergency maintenance method to rebuild the ``.utilities`` 

511 registry from the local copy maintained in this object, or 

512 detect the need to do so. 

513 

514 Most users will never need to call this, but it can be helpful 

515 in the event of suspected corruption. 

516 

517 By default, this method only checks for corruption. To make it 

518 actually rebuild the registry, pass `True` for *rebuild*. 

519 

520 :param bool rebuild: If set to `True` (not the default), 

521 this method will actually register and subscribe utilities 

522 in the registry as needed to synchronize with the local cache. 

523 

524 :return: A dictionary that's meant as diagnostic data. The keys 

525 and values may change over time. When called with a false *rebuild*, 

526 the keys ``"needed_registered"`` and ``"needed_subscribed"`` will be 

527 non-zero if any corruption was detected, but that will not be corrected. 

528 

529 .. versionadded:: 5.3.0 

530 """ 

531 regs = dict(self._utility_registrations) 

532 utils = self.utilities 

533 needed_registered = 0 

534 did_not_register = 0 

535 needed_subscribed = 0 

536 did_not_subscribe = 0 

537 

538 

539 # Avoid the expensive change process during this; we'll call 

540 # it once at the end if needed. 

541 assert 'changed' not in utils.__dict__ 

542 utils.changed = lambda _: None 

543 

544 if rebuild: 

545 register = utils.register 

546 subscribe = utils.subscribe 

547 else: 

548 register = subscribe = lambda *args: None 

549 

550 try: 

551 for (provided, name), (value, _info, _factory) in regs.items(): 

552 if utils.registered((), provided, name) != value: 

553 register((), provided, name, value) 

554 needed_registered += 1 

555 else: 

556 did_not_register += 1 

557 

558 if utils.subscribed((), provided, value) is None: 

559 needed_subscribed += 1 

560 subscribe((), provided, value) 

561 else: 

562 did_not_subscribe += 1 

563 finally: 

564 del utils.changed 

565 if rebuild and (needed_subscribed or needed_registered): 

566 utils.changed(utils) 

567 

568 return { 

569 'needed_registered': needed_registered, 

570 'did_not_register': did_not_register, 

571 'needed_subscribed': needed_subscribed, 

572 'did_not_subscribe': did_not_subscribe 

573 } 

574 

575def _getName(component): 

576 try: 

577 return component.__component_name__ 

578 except AttributeError: 

579 return u'' 

580 

581def _getUtilityProvided(component): 

582 provided = list(providedBy(component)) 

583 if len(provided) == 1: 

584 return provided[0] 

585 raise TypeError( 

586 "The utility doesn't provide a single interface " 

587 "and no provided interface was specified.") 

588 

589def _getAdapterProvided(factory): 

590 provided = list(implementedBy(factory)) 

591 if len(provided) == 1: 

592 return provided[0] 

593 raise TypeError( 

594 "The adapter factory doesn't implement a single interface " 

595 "and no provided interface was specified.") 

596 

597def _getAdapterRequired(factory, required): 

598 if required is None: 

599 try: 

600 required = factory.__component_adapts__ 

601 except AttributeError: 

602 raise TypeError( 

603 "The adapter factory doesn't have a __component_adapts__ " 

604 "attribute and no required specifications were specified" 

605 ) 

606 elif ISpecification.providedBy(required): 

607 raise TypeError("the required argument should be a list of " 

608 "interfaces, not a single interface") 

609 

610 result = [] 

611 for r in required: 

612 if r is None: 

613 r = Interface 

614 elif not ISpecification.providedBy(r): 

615 if isinstance(r, CLASS_TYPES): 

616 r = implementedBy(r) 

617 else: 

618 raise TypeError("Required specification must be a " 

619 "specification or class, not %r" % type(r) 

620 ) 

621 result.append(r) 

622 return tuple(result) 

623 

624 

625@implementer(IUtilityRegistration) 

626class UtilityRegistration(object): 

627 

628 def __init__(self, registry, provided, name, component, doc, factory=None): 

629 (self.registry, self.provided, self.name, self.component, self.info, 

630 self.factory 

631 ) = registry, provided, name, component, doc, factory 

632 

633 def __repr__(self): 

634 return '%s(%r, %s, %r, %s, %r, %r)' % ( 

635 self.__class__.__name__, 

636 self.registry, 

637 getattr(self.provided, '__name__', None), self.name, 

638 getattr(self.component, '__name__', repr(self.component)), 

639 self.factory, self.info, 

640 ) 

641 

642 def __hash__(self): 

643 return id(self) 

644 

645 def __eq__(self, other): 

646 return repr(self) == repr(other) 

647 

648 def __ne__(self, other): 

649 return repr(self) != repr(other) 

650 

651 def __lt__(self, other): 

652 return repr(self) < repr(other) 

653 

654 def __le__(self, other): 

655 return repr(self) <= repr(other) 

656 

657 def __gt__(self, other): 

658 return repr(self) > repr(other) 

659 

660 def __ge__(self, other): 

661 return repr(self) >= repr(other) 

662 

663@implementer(IAdapterRegistration) 

664class AdapterRegistration(object): 

665 

666 def __init__(self, registry, required, provided, name, component, doc): 

667 (self.registry, self.required, self.provided, self.name, 

668 self.factory, self.info 

669 ) = registry, required, provided, name, component, doc 

670 

671 def __repr__(self): 

672 return '%s(%r, %s, %s, %r, %s, %r)' % ( 

673 self.__class__.__name__, 

674 self.registry, 

675 '[' + ", ".join([r.__name__ for r in self.required]) + ']', 

676 getattr(self.provided, '__name__', None), self.name, 

677 getattr(self.factory, '__name__', repr(self.factory)), self.info, 

678 ) 

679 

680 def __hash__(self): 

681 return id(self) 

682 

683 def __eq__(self, other): 

684 return repr(self) == repr(other) 

685 

686 def __ne__(self, other): 

687 return repr(self) != repr(other) 

688 

689 def __lt__(self, other): 

690 return repr(self) < repr(other) 

691 

692 def __le__(self, other): 

693 return repr(self) <= repr(other) 

694 

695 def __gt__(self, other): 

696 return repr(self) > repr(other) 

697 

698 def __ge__(self, other): 

699 return repr(self) >= repr(other) 

700 

701@implementer_only(ISubscriptionAdapterRegistration) 

702class SubscriptionRegistration(AdapterRegistration): 

703 pass 

704 

705 

706@implementer_only(IHandlerRegistration) 

707class HandlerRegistration(AdapterRegistration): 

708 

709 def __init__(self, registry, required, name, handler, doc): 

710 (self.registry, self.required, self.name, self.handler, self.info 

711 ) = registry, required, name, handler, doc 

712 

713 @property 

714 def factory(self): 

715 return self.handler 

716 

717 provided = None 

718 

719 def __repr__(self): 

720 return '%s(%r, %s, %r, %s, %r)' % ( 

721 self.__class__.__name__, 

722 self.registry, 

723 '[' + ", ".join([r.__name__ for r in self.required]) + ']', 

724 self.name, 

725 getattr(self.factory, '__name__', repr(self.factory)), self.info, 

726 )