comparison mercurial/thirdparty/zope/interface/advice.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) 2003 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 """Class advice.
15
16 This module was adapted from 'protocols.advice', part of the Python
17 Enterprise Application Kit (PEAK). Please notify the PEAK authors
18 (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
19 Zope-specific changes are required, so that the PEAK version of this module
20 can be kept in sync.
21
22 PEAK is a Python application framework that interoperates with (but does
23 not require) Zope 3 and Twisted. It provides tools for manipulating UML
24 models, object-relational persistence, aspect-oriented programming, and more.
25 Visit the PEAK home page at http://peak.telecommunity.com for more information.
26 """
27
28 from types import FunctionType
29 try:
30 from types import ClassType
31 except ImportError:
32 __python3 = True
33 else:
34 __python3 = False
35
36 import sys
37
38 def getFrameInfo(frame):
39 """Return (kind,module,locals,globals) for a frame
40
41 'kind' is one of "exec", "module", "class", "function call", or "unknown".
42 """
43
44 f_locals = frame.f_locals
45 f_globals = frame.f_globals
46
47 sameNamespace = f_locals is f_globals
48 hasModule = '__module__' in f_locals
49 hasName = '__name__' in f_globals
50
51 sameName = hasModule and hasName
52 sameName = sameName and f_globals['__name__']==f_locals['__module__']
53
54 module = hasName and sys.modules.get(f_globals['__name__']) or None
55
56 namespaceIsModule = module and module.__dict__ is f_globals
57
58 if not namespaceIsModule:
59 # some kind of funky exec
60 kind = "exec"
61 elif sameNamespace and not hasModule:
62 kind = "module"
63 elif sameName and not sameNamespace:
64 kind = "class"
65 elif not sameNamespace:
66 kind = "function call"
67 else: # pragma: no cover
68 # How can you have f_locals is f_globals, and have '__module__' set?
69 # This is probably module-level code, but with a '__module__' variable.
70 kind = "unknown"
71 return kind, module, f_locals, f_globals
72
73
74 def addClassAdvisor(callback, depth=2):
75 """Set up 'callback' to be passed the containing class upon creation
76
77 This function is designed to be called by an "advising" function executed
78 in a class suite. The "advising" function supplies a callback that it
79 wishes to have executed when the containing class is created. The
80 callback will be given one argument: the newly created containing class.
81 The return value of the callback will be used in place of the class, so
82 the callback should return the input if it does not wish to replace the
83 class.
84
85 The optional 'depth' argument to this function determines the number of
86 frames between this function and the targeted class suite. 'depth'
87 defaults to 2, since this skips this function's frame and one calling
88 function frame. If you use this function from a function called directly
89 in the class suite, the default will be correct, otherwise you will need
90 to determine the correct depth yourself.
91
92 This function works by installing a special class factory function in
93 place of the '__metaclass__' of the containing class. Therefore, only
94 callbacks *after* the last '__metaclass__' assignment in the containing
95 class will be executed. Be sure that classes using "advising" functions
96 declare any '__metaclass__' *first*, to ensure all callbacks are run."""
97 # This entire approach is invalid under Py3K. Don't even try to fix
98 # the coverage for this block there. :(
99 if __python3: # pragma: no cover
100 raise TypeError('Class advice impossible in Python3')
101
102 frame = sys._getframe(depth)
103 kind, module, caller_locals, caller_globals = getFrameInfo(frame)
104
105 # This causes a problem when zope interfaces are used from doctest.
106 # In these cases, kind == "exec".
107 #
108 #if kind != "class":
109 # raise SyntaxError(
110 # "Advice must be in the body of a class statement"
111 # )
112
113 previousMetaclass = caller_locals.get('__metaclass__')
114 if __python3: # pragma: no cover
115 defaultMetaclass = caller_globals.get('__metaclass__', type)
116 else:
117 defaultMetaclass = caller_globals.get('__metaclass__', ClassType)
118
119
120 def advise(name, bases, cdict):
121
122 if '__metaclass__' in cdict:
123 del cdict['__metaclass__']
124
125 if previousMetaclass is None:
126 if bases:
127 # find best metaclass or use global __metaclass__ if no bases
128 meta = determineMetaclass(bases)
129 else:
130 meta = defaultMetaclass
131
132 elif isClassAdvisor(previousMetaclass):
133 # special case: we can't compute the "true" metaclass here,
134 # so we need to invoke the previous metaclass and let it
135 # figure it out for us (and apply its own advice in the process)
136 meta = previousMetaclass
137
138 else:
139 meta = determineMetaclass(bases, previousMetaclass)
140
141 newClass = meta(name,bases,cdict)
142
143 # this lets the callback replace the class completely, if it wants to
144 return callback(newClass)
145
146 # introspection data only, not used by inner function
147 advise.previousMetaclass = previousMetaclass
148 advise.callback = callback
149
150 # install the advisor
151 caller_locals['__metaclass__'] = advise
152
153
154 def isClassAdvisor(ob):
155 """True if 'ob' is a class advisor function"""
156 return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass')
157
158
159 def determineMetaclass(bases, explicit_mc=None):
160 """Determine metaclass from 1+ bases and optional explicit __metaclass__"""
161
162 meta = [getattr(b,'__class__',type(b)) for b in bases]
163
164 if explicit_mc is not None:
165 # The explicit metaclass needs to be verified for compatibility
166 # as well, and allowed to resolve the incompatible bases, if any
167 meta.append(explicit_mc)
168
169 if len(meta)==1:
170 # easy case
171 return meta[0]
172
173 candidates = minimalBases(meta) # minimal set of metaclasses
174
175 if not candidates: # pragma: no cover
176 # they're all "classic" classes
177 assert(not __python3) # This should not happen under Python 3
178 return ClassType
179
180 elif len(candidates)>1:
181 # We could auto-combine, but for now we won't...
182 raise TypeError("Incompatible metatypes",bases)
183
184 # Just one, return it
185 return candidates[0]
186
187
188 def minimalBases(classes):
189 """Reduce a list of base classes to its ordered minimum equivalent"""
190
191 if not __python3: # pragma: no cover
192 classes = [c for c in classes if c is not ClassType]
193 candidates = []
194
195 for m in classes:
196 for n in classes:
197 if issubclass(n,m) and m is not n:
198 break
199 else:
200 # m has no subclasses in 'classes'
201 if m in candidates:
202 candidates.remove(m) # ensure that we're later in the list
203 candidates.append(m)
204
205 return candidates