Mercurial > public > mercurial-scm > hg
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 |