Mercurial > public > mercurial-scm > hg
comparison mercurial/thirdparty/zope/interface/interface.py @ 37176:943d77fc07a3
thirdparty: vendor zope.interface 4.4.3
I've been trying to formalize interfaces for various components
of Mercurial. So far, we've been using the "abc" package. This
package is "good enough" for a lot of tasks. But it quickly
falls over. For example, if you declare an @abc.abstractproperty,
you must implement that attribute with a @property or the class
compile time checking performed by abc will complain. This often
forces you to implement dumb @property wrappers to return a
_ prefixed attribute of the sane name. That's ugly.
I've also wanted to implement automated checking that classes
conform to various interfaces and don't expose other "public"
attributes.
After doing a bit of research and asking around, the general
consensus seems to be that zope.interface is the best package for
doing interface-based programming in Python. It has built-in
support for verifying classes and objects conform to interfaces.
It allows an interface's properties to be defined during __init__.
There's even an "adapter registry" that allow you to register
interfaces and look up which classes implement them. That could
potentially be useful for places where our custom registry.py
modules currently facilitates central registrations, but at a
type level. Imagine extensions providing alternate implementations
of things like the local repository interface to allow opening
repositories with custom requirements.
Anyway, this commit vendors zope.interface 4.4.3. The contents of
the source tarball have been copied into mercurial/thirdparty/zope/
without modifications.
Test modules have been removed because they are not interesting
to us.
The LICENSE.txt file has been copied so it lives next to the
source.
The Python modules don't use relative imports. zope/__init__.py
defines a namespace package. So we'll need to modify the source
code before this package is usable inside Mercurial. This will
be done in subsequent commits.
# no-check-commit for various style failures
Differential Revision: https://phab.mercurial-scm.org/D2928
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Wed, 21 Mar 2018 19:48:50 -0700 |
parents | |
children | 68ee61822182 |
comparison
equal
deleted
inserted
replaced
37175:fbe34945220d | 37176:943d77fc07a3 |
---|---|
1 ############################################################################## | |
2 # | |
3 # Copyright (c) 2001, 2002 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 """Interface object implementation | |
15 """ | |
16 from __future__ import generators | |
17 | |
18 import sys | |
19 from types import MethodType | |
20 from types import FunctionType | |
21 import warnings | |
22 import weakref | |
23 | |
24 from zope.interface.exceptions import Invalid | |
25 from zope.interface.ro import ro | |
26 | |
27 | |
28 CO_VARARGS = 4 | |
29 CO_VARKEYWORDS = 8 | |
30 TAGGED_DATA = '__interface_tagged_values__' | |
31 | |
32 _decorator_non_return = object() | |
33 | |
34 def invariant(call): | |
35 f_locals = sys._getframe(1).f_locals | |
36 tags = f_locals.setdefault(TAGGED_DATA, {}) | |
37 invariants = tags.setdefault('invariants', []) | |
38 invariants.append(call) | |
39 return _decorator_non_return | |
40 | |
41 | |
42 def taggedValue(key, value): | |
43 """Attaches a tagged value to an interface at definition time.""" | |
44 f_locals = sys._getframe(1).f_locals | |
45 tagged_values = f_locals.setdefault(TAGGED_DATA, {}) | |
46 tagged_values[key] = value | |
47 return _decorator_non_return | |
48 | |
49 | |
50 class Element(object): | |
51 | |
52 # We can't say this yet because we don't have enough | |
53 # infrastructure in place. | |
54 # | |
55 #implements(IElement) | |
56 | |
57 def __init__(self, __name__, __doc__=''): | |
58 """Create an 'attribute' description | |
59 """ | |
60 if not __doc__ and __name__.find(' ') >= 0: | |
61 __doc__ = __name__ | |
62 __name__ = None | |
63 | |
64 self.__name__=__name__ | |
65 self.__doc__=__doc__ | |
66 self.__tagged_values = {} | |
67 | |
68 def getName(self): | |
69 """ Returns the name of the object. """ | |
70 return self.__name__ | |
71 | |
72 def getDoc(self): | |
73 """ Returns the documentation for the object. """ | |
74 return self.__doc__ | |
75 | |
76 def getTaggedValue(self, tag): | |
77 """ Returns the value associated with 'tag'. """ | |
78 return self.__tagged_values[tag] | |
79 | |
80 def queryTaggedValue(self, tag, default=None): | |
81 """ Returns the value associated with 'tag'. """ | |
82 return self.__tagged_values.get(tag, default) | |
83 | |
84 def getTaggedValueTags(self): | |
85 """ Returns a list of all tags. """ | |
86 return self.__tagged_values.keys() | |
87 | |
88 def setTaggedValue(self, tag, value): | |
89 """ Associates 'value' with 'key'. """ | |
90 self.__tagged_values[tag] = value | |
91 | |
92 class SpecificationBasePy(object): | |
93 | |
94 def providedBy(self, ob): | |
95 """Is the interface implemented by an object | |
96 """ | |
97 spec = providedBy(ob) | |
98 return self in spec._implied | |
99 | |
100 def implementedBy(self, cls): | |
101 """Test whether the specification is implemented by a class or factory. | |
102 | |
103 Raise TypeError if argument is neither a class nor a callable. | |
104 """ | |
105 spec = implementedBy(cls) | |
106 return self in spec._implied | |
107 | |
108 def isOrExtends(self, interface): | |
109 """Is the interface the same as or extend the given interface | |
110 """ | |
111 return interface in self._implied | |
112 | |
113 __call__ = isOrExtends | |
114 | |
115 SpecificationBase = SpecificationBasePy | |
116 try: | |
117 from zope.interface._zope_interface_coptimizations import SpecificationBase | |
118 except ImportError: | |
119 pass | |
120 | |
121 _marker = object() | |
122 class InterfaceBasePy(object): | |
123 """Base class that wants to be replaced with a C base :) | |
124 """ | |
125 | |
126 def __call__(self, obj, alternate=_marker): | |
127 """Adapt an object to the interface | |
128 """ | |
129 conform = getattr(obj, '__conform__', None) | |
130 if conform is not None: | |
131 adapter = self._call_conform(conform) | |
132 if adapter is not None: | |
133 return adapter | |
134 | |
135 adapter = self.__adapt__(obj) | |
136 | |
137 if adapter is not None: | |
138 return adapter | |
139 elif alternate is not _marker: | |
140 return alternate | |
141 else: | |
142 raise TypeError("Could not adapt", obj, self) | |
143 | |
144 def __adapt__(self, obj): | |
145 """Adapt an object to the reciever | |
146 """ | |
147 if self.providedBy(obj): | |
148 return obj | |
149 | |
150 for hook in adapter_hooks: | |
151 adapter = hook(self, obj) | |
152 if adapter is not None: | |
153 return adapter | |
154 | |
155 | |
156 InterfaceBase = InterfaceBasePy | |
157 try: | |
158 from zope.interface._zope_interface_coptimizations import InterfaceBase | |
159 except ImportError: | |
160 pass | |
161 | |
162 | |
163 adapter_hooks = [] | |
164 try: | |
165 from zope.interface._zope_interface_coptimizations import adapter_hooks | |
166 except ImportError: | |
167 pass | |
168 | |
169 | |
170 class Specification(SpecificationBase): | |
171 """Specifications | |
172 | |
173 An interface specification is used to track interface declarations | |
174 and component registrations. | |
175 | |
176 This class is a base class for both interfaces themselves and for | |
177 interface specifications (declarations). | |
178 | |
179 Specifications are mutable. If you reassign their bases, their | |
180 relations with other specifications are adjusted accordingly. | |
181 """ | |
182 | |
183 # Copy some base class methods for speed | |
184 isOrExtends = SpecificationBase.isOrExtends | |
185 providedBy = SpecificationBase.providedBy | |
186 | |
187 def __init__(self, bases=()): | |
188 self._implied = {} | |
189 self.dependents = weakref.WeakKeyDictionary() | |
190 self.__bases__ = tuple(bases) | |
191 | |
192 def subscribe(self, dependent): | |
193 self.dependents[dependent] = self.dependents.get(dependent, 0) + 1 | |
194 | |
195 def unsubscribe(self, dependent): | |
196 n = self.dependents.get(dependent, 0) - 1 | |
197 if not n: | |
198 del self.dependents[dependent] | |
199 elif n > 0: | |
200 self.dependents[dependent] = n | |
201 else: | |
202 raise KeyError(dependent) | |
203 | |
204 def __setBases(self, bases): | |
205 # Register ourselves as a dependent of our old bases | |
206 for b in self.__bases__: | |
207 b.unsubscribe(self) | |
208 | |
209 # Register ourselves as a dependent of our bases | |
210 self.__dict__['__bases__'] = bases | |
211 for b in bases: | |
212 b.subscribe(self) | |
213 | |
214 self.changed(self) | |
215 | |
216 __bases__ = property( | |
217 | |
218 lambda self: self.__dict__.get('__bases__', ()), | |
219 __setBases, | |
220 ) | |
221 | |
222 def changed(self, originally_changed): | |
223 """We, or something we depend on, have changed | |
224 """ | |
225 try: | |
226 del self._v_attrs | |
227 except AttributeError: | |
228 pass | |
229 | |
230 implied = self._implied | |
231 implied.clear() | |
232 | |
233 ancestors = ro(self) | |
234 | |
235 try: | |
236 if Interface not in ancestors: | |
237 ancestors.append(Interface) | |
238 except NameError: | |
239 pass # defining Interface itself | |
240 | |
241 self.__sro__ = tuple(ancestors) | |
242 self.__iro__ = tuple([ancestor for ancestor in ancestors | |
243 if isinstance(ancestor, InterfaceClass) | |
244 ]) | |
245 | |
246 for ancestor in ancestors: | |
247 # We directly imply our ancestors: | |
248 implied[ancestor] = () | |
249 | |
250 # Now, advise our dependents of change: | |
251 for dependent in tuple(self.dependents.keys()): | |
252 dependent.changed(originally_changed) | |
253 | |
254 | |
255 def interfaces(self): | |
256 """Return an iterator for the interfaces in the specification. | |
257 """ | |
258 seen = {} | |
259 for base in self.__bases__: | |
260 for interface in base.interfaces(): | |
261 if interface not in seen: | |
262 seen[interface] = 1 | |
263 yield interface | |
264 | |
265 | |
266 def extends(self, interface, strict=True): | |
267 """Does the specification extend the given interface? | |
268 | |
269 Test whether an interface in the specification extends the | |
270 given interface | |
271 """ | |
272 return ((interface in self._implied) | |
273 and | |
274 ((not strict) or (self != interface)) | |
275 ) | |
276 | |
277 def weakref(self, callback=None): | |
278 return weakref.ref(self, callback) | |
279 | |
280 def get(self, name, default=None): | |
281 """Query for an attribute description | |
282 """ | |
283 try: | |
284 attrs = self._v_attrs | |
285 except AttributeError: | |
286 attrs = self._v_attrs = {} | |
287 attr = attrs.get(name) | |
288 if attr is None: | |
289 for iface in self.__iro__: | |
290 attr = iface.direct(name) | |
291 if attr is not None: | |
292 attrs[name] = attr | |
293 break | |
294 | |
295 if attr is None: | |
296 return default | |
297 else: | |
298 return attr | |
299 | |
300 class InterfaceClass(Element, InterfaceBase, Specification): | |
301 """Prototype (scarecrow) Interfaces Implementation.""" | |
302 | |
303 # We can't say this yet because we don't have enough | |
304 # infrastructure in place. | |
305 # | |
306 #implements(IInterface) | |
307 | |
308 def __init__(self, name, bases=(), attrs=None, __doc__=None, | |
309 __module__=None): | |
310 | |
311 if attrs is None: | |
312 attrs = {} | |
313 | |
314 if __module__ is None: | |
315 __module__ = attrs.get('__module__') | |
316 if isinstance(__module__, str): | |
317 del attrs['__module__'] | |
318 else: | |
319 try: | |
320 # Figure out what module defined the interface. | |
321 # This is how cPython figures out the module of | |
322 # a class, but of course it does it in C. :-/ | |
323 __module__ = sys._getframe(1).f_globals['__name__'] | |
324 except (AttributeError, KeyError): # pragma: no cover | |
325 pass | |
326 | |
327 self.__module__ = __module__ | |
328 | |
329 d = attrs.get('__doc__') | |
330 if d is not None: | |
331 if not isinstance(d, Attribute): | |
332 if __doc__ is None: | |
333 __doc__ = d | |
334 del attrs['__doc__'] | |
335 | |
336 if __doc__ is None: | |
337 __doc__ = '' | |
338 | |
339 Element.__init__(self, name, __doc__) | |
340 | |
341 tagged_data = attrs.pop(TAGGED_DATA, None) | |
342 if tagged_data is not None: | |
343 for key, val in tagged_data.items(): | |
344 self.setTaggedValue(key, val) | |
345 | |
346 for base in bases: | |
347 if not isinstance(base, InterfaceClass): | |
348 raise TypeError('Expected base interfaces') | |
349 | |
350 Specification.__init__(self, bases) | |
351 | |
352 # Make sure that all recorded attributes (and methods) are of type | |
353 # `Attribute` and `Method` | |
354 for name, attr in list(attrs.items()): | |
355 if name in ('__locals__', '__qualname__', '__annotations__'): | |
356 # __locals__: Python 3 sometimes adds this. | |
357 # __qualname__: PEP 3155 (Python 3.3+) | |
358 # __annotations__: PEP 3107 (Python 3.0+) | |
359 del attrs[name] | |
360 continue | |
361 if isinstance(attr, Attribute): | |
362 attr.interface = self | |
363 if not attr.__name__: | |
364 attr.__name__ = name | |
365 elif isinstance(attr, FunctionType): | |
366 attrs[name] = fromFunction(attr, self, name=name) | |
367 elif attr is _decorator_non_return: | |
368 del attrs[name] | |
369 else: | |
370 raise InvalidInterface("Concrete attribute, " + name) | |
371 | |
372 self.__attrs = attrs | |
373 | |
374 self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) | |
375 | |
376 def interfaces(self): | |
377 """Return an iterator for the interfaces in the specification. | |
378 """ | |
379 yield self | |
380 | |
381 def getBases(self): | |
382 return self.__bases__ | |
383 | |
384 def isEqualOrExtendedBy(self, other): | |
385 """Same interface or extends?""" | |
386 return self == other or other.extends(self) | |
387 | |
388 def names(self, all=False): | |
389 """Return the attribute names defined by the interface.""" | |
390 if not all: | |
391 return self.__attrs.keys() | |
392 | |
393 r = self.__attrs.copy() | |
394 | |
395 for base in self.__bases__: | |
396 r.update(dict.fromkeys(base.names(all))) | |
397 | |
398 return r.keys() | |
399 | |
400 def __iter__(self): | |
401 return iter(self.names(all=True)) | |
402 | |
403 def namesAndDescriptions(self, all=False): | |
404 """Return attribute names and descriptions defined by interface.""" | |
405 if not all: | |
406 return self.__attrs.items() | |
407 | |
408 r = {} | |
409 for base in self.__bases__[::-1]: | |
410 r.update(dict(base.namesAndDescriptions(all))) | |
411 | |
412 r.update(self.__attrs) | |
413 | |
414 return r.items() | |
415 | |
416 def getDescriptionFor(self, name): | |
417 """Return the attribute description for the given name.""" | |
418 r = self.get(name) | |
419 if r is not None: | |
420 return r | |
421 | |
422 raise KeyError(name) | |
423 | |
424 __getitem__ = getDescriptionFor | |
425 | |
426 def __contains__(self, name): | |
427 return self.get(name) is not None | |
428 | |
429 def direct(self, name): | |
430 return self.__attrs.get(name) | |
431 | |
432 def queryDescriptionFor(self, name, default=None): | |
433 return self.get(name, default) | |
434 | |
435 def validateInvariants(self, obj, errors=None): | |
436 """validate object to defined invariants.""" | |
437 for call in self.queryTaggedValue('invariants', []): | |
438 try: | |
439 call(obj) | |
440 except Invalid as e: | |
441 if errors is None: | |
442 raise | |
443 else: | |
444 errors.append(e) | |
445 for base in self.__bases__: | |
446 try: | |
447 base.validateInvariants(obj, errors) | |
448 except Invalid: | |
449 if errors is None: | |
450 raise | |
451 if errors: | |
452 raise Invalid(errors) | |
453 | |
454 def __repr__(self): # pragma: no cover | |
455 try: | |
456 return self._v_repr | |
457 except AttributeError: | |
458 name = self.__name__ | |
459 m = self.__module__ | |
460 if m: | |
461 name = '%s.%s' % (m, name) | |
462 r = "<%s %s>" % (self.__class__.__name__, name) | |
463 self._v_repr = r | |
464 return r | |
465 | |
466 def _call_conform(self, conform): | |
467 try: | |
468 return conform(self) | |
469 except TypeError: # pragma: no cover | |
470 # We got a TypeError. It might be an error raised by | |
471 # the __conform__ implementation, or *we* may have | |
472 # made the TypeError by calling an unbound method | |
473 # (object is a class). In the later case, we behave | |
474 # as though there is no __conform__ method. We can | |
475 # detect this case by checking whether there is more | |
476 # than one traceback object in the traceback chain: | |
477 if sys.exc_info()[2].tb_next is not None: | |
478 # There is more than one entry in the chain, so | |
479 # reraise the error: | |
480 raise | |
481 # This clever trick is from Phillip Eby | |
482 | |
483 return None # pragma: no cover | |
484 | |
485 def __reduce__(self): | |
486 return self.__name__ | |
487 | |
488 def __cmp(self, other): | |
489 # Yes, I did mean to name this __cmp, rather than __cmp__. | |
490 # It is a private method used by __lt__ and __gt__. | |
491 # I don't want to override __eq__ because I want the default | |
492 # __eq__, which is really fast. | |
493 """Make interfaces sortable | |
494 | |
495 TODO: It would ne nice if: | |
496 | |
497 More specific interfaces should sort before less specific ones. | |
498 Otherwise, sort on name and module. | |
499 | |
500 But this is too complicated, and we're going to punt on it | |
501 for now. | |
502 | |
503 For now, sort on interface and module name. | |
504 | |
505 None is treated as a pseudo interface that implies the loosest | |
506 contact possible, no contract. For that reason, all interfaces | |
507 sort before None. | |
508 | |
509 """ | |
510 if other is None: | |
511 return -1 | |
512 | |
513 n1 = (getattr(self, '__name__', ''), getattr(self, '__module__', '')) | |
514 n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) | |
515 | |
516 # This spelling works under Python3, which doesn't have cmp(). | |
517 return (n1 > n2) - (n1 < n2) | |
518 | |
519 def __hash__(self): | |
520 d = self.__dict__ | |
521 if '__module__' not in d or '__name__' not in d: # pragma: no cover | |
522 warnings.warn('Hashing uninitialized InterfaceClass instance') | |
523 return 1 | |
524 return hash((self.__name__, self.__module__)) | |
525 | |
526 def __eq__(self, other): | |
527 c = self.__cmp(other) | |
528 return c == 0 | |
529 | |
530 def __ne__(self, other): | |
531 c = self.__cmp(other) | |
532 return c != 0 | |
533 | |
534 def __lt__(self, other): | |
535 c = self.__cmp(other) | |
536 return c < 0 | |
537 | |
538 def __le__(self, other): | |
539 c = self.__cmp(other) | |
540 return c <= 0 | |
541 | |
542 def __gt__(self, other): | |
543 c = self.__cmp(other) | |
544 return c > 0 | |
545 | |
546 def __ge__(self, other): | |
547 c = self.__cmp(other) | |
548 return c >= 0 | |
549 | |
550 | |
551 Interface = InterfaceClass("Interface", __module__ = 'zope.interface') | |
552 | |
553 class Attribute(Element): | |
554 """Attribute descriptions | |
555 """ | |
556 | |
557 # We can't say this yet because we don't have enough | |
558 # infrastructure in place. | |
559 # | |
560 # implements(IAttribute) | |
561 | |
562 interface = None | |
563 | |
564 | |
565 class Method(Attribute): | |
566 """Method interfaces | |
567 | |
568 The idea here is that you have objects that describe methods. | |
569 This provides an opportunity for rich meta-data. | |
570 """ | |
571 | |
572 # We can't say this yet because we don't have enough | |
573 # infrastructure in place. | |
574 # | |
575 # implements(IMethod) | |
576 | |
577 positional = required = () | |
578 _optional = varargs = kwargs = None | |
579 def _get_optional(self): | |
580 if self._optional is None: | |
581 return {} | |
582 return self._optional | |
583 def _set_optional(self, opt): | |
584 self._optional = opt | |
585 def _del_optional(self): | |
586 self._optional = None | |
587 optional = property(_get_optional, _set_optional, _del_optional) | |
588 | |
589 def __call__(self, *args, **kw): | |
590 raise BrokenImplementation(self.interface, self.__name__) | |
591 | |
592 def getSignatureInfo(self): | |
593 return {'positional': self.positional, | |
594 'required': self.required, | |
595 'optional': self.optional, | |
596 'varargs': self.varargs, | |
597 'kwargs': self.kwargs, | |
598 } | |
599 | |
600 def getSignatureString(self): | |
601 sig = [] | |
602 for v in self.positional: | |
603 sig.append(v) | |
604 if v in self.optional.keys(): | |
605 sig[-1] += "=" + repr(self.optional[v]) | |
606 if self.varargs: | |
607 sig.append("*" + self.varargs) | |
608 if self.kwargs: | |
609 sig.append("**" + self.kwargs) | |
610 | |
611 return "(%s)" % ", ".join(sig) | |
612 | |
613 def fromFunction(func, interface=None, imlevel=0, name=None): | |
614 name = name or func.__name__ | |
615 method = Method(name, func.__doc__) | |
616 defaults = getattr(func, '__defaults__', None) or () | |
617 code = func.__code__ | |
618 # Number of positional arguments | |
619 na = code.co_argcount-imlevel | |
620 names = code.co_varnames[imlevel:] | |
621 opt = {} | |
622 # Number of required arguments | |
623 nr = na-len(defaults) | |
624 if nr < 0: | |
625 defaults=defaults[-nr:] | |
626 nr = 0 | |
627 | |
628 # Determine the optional arguments. | |
629 opt.update(dict(zip(names[nr:], defaults))) | |
630 | |
631 method.positional = names[:na] | |
632 method.required = names[:nr] | |
633 method.optional = opt | |
634 | |
635 argno = na | |
636 | |
637 # Determine the function's variable argument's name (i.e. *args) | |
638 if code.co_flags & CO_VARARGS: | |
639 method.varargs = names[argno] | |
640 argno = argno + 1 | |
641 else: | |
642 method.varargs = None | |
643 | |
644 # Determine the function's keyword argument's name (i.e. **kw) | |
645 if code.co_flags & CO_VARKEYWORDS: | |
646 method.kwargs = names[argno] | |
647 else: | |
648 method.kwargs = None | |
649 | |
650 method.interface = interface | |
651 | |
652 for key, value in func.__dict__.items(): | |
653 method.setTaggedValue(key, value) | |
654 | |
655 return method | |
656 | |
657 | |
658 def fromMethod(meth, interface=None, name=None): | |
659 if isinstance(meth, MethodType): | |
660 func = meth.__func__ | |
661 else: | |
662 func = meth | |
663 return fromFunction(func, interface, imlevel=1, name=name) | |
664 | |
665 | |
666 # Now we can create the interesting interfaces and wire them up: | |
667 def _wire(): | |
668 from zope.interface.declarations import classImplements | |
669 | |
670 from zope.interface.interfaces import IAttribute | |
671 classImplements(Attribute, IAttribute) | |
672 | |
673 from zope.interface.interfaces import IMethod | |
674 classImplements(Method, IMethod) | |
675 | |
676 from zope.interface.interfaces import IInterface | |
677 classImplements(InterfaceClass, IInterface) | |
678 | |
679 from zope.interface.interfaces import ISpecification | |
680 classImplements(Specification, ISpecification) | |
681 | |
682 # We import this here to deal with module dependencies. | |
683 from zope.interface.declarations import implementedBy | |
684 from zope.interface.declarations import providedBy | |
685 from zope.interface.exceptions import InvalidInterface | |
686 from zope.interface.exceptions import BrokenImplementation |