• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / ExtensionClass / 6447471721

05 Oct 2023 11:08AM UTC coverage: 100.0%. Remained the same
6447471721

push

github

dataflake
- vb [ci skip]

88 of 88 branches covered (100.0%)

Branch coverage included in aggregate %.

366 of 366 relevant lines covered (100.0%)

7.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/ExtensionClass/__init__.py
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
"""ExtensionClass
7✔
15

16
Extension Class exists to support types derived from the old ExtensionType
17
meta-class that preceeded Python 2.2 and new-style classes.
18

19
As a meta-class, ExtensionClass provides the following features:
20

21
- Support for a class initialiser:
22

23
  >>> from ExtensionClass import ExtensionClass, Base
24

25
  >>> class C(Base):
26
  ...   def __class_init__(self):
27
  ...      print('class init called')
28
  ...      print(self.__name__)
29
  ...   def bar(self):
30
  ...      return 'bar called'
31
  class init called
32
  C
33
  >>> c = C()
34
  >>> int(c.__class__ is C)
35
  1
36
  >>> int(c.__class__ is type(c))
37
  1
38

39
- Making sure that every instance of the meta-class has Base as a base class:
40

41
  >>> X = ExtensionClass('X', (), {})
42
  >>> Base in X.__mro__
43
  1
44

45
- Provide an inheritedAttribute method for looking up attributes in
46
  base classes:
47

48
  >>> class C2(C):
49
  ...   def bar(*a):
50
  ...      return C2.inheritedAttribute('bar')(*a), 42
51
  class init called
52
  C2
53
  >>> o = C2()
54
  >>> o.bar()
55
  ('bar called', 42)
56

57
  This is for compatability with old code. New code should use super
58
  instead.
59

60
The base class, Base, exists mainly to support the __of__ protocol.
61
The __of__ protocol is similar to __get__ except that __of__ is called
62
when an implementor is retrieved from an instance as well as from a
63
class:
64

65
>>> class O(Base):
66
...   def __of__(*a):
67
...      return a
68

69
>>> o1 = O()
70
>>> o2 = O()
71
>>> C.o1 = o1
72
>>> c.o2 = o2
73
>>> c.o1 == (o1, c)
74
1
75
>>> C.o1 == o1
76
1
77
>>> int(c.o2 == (o2, c))
78
1
79

80
We accomplish this by making a class that implements __of__ a
81
descriptor and treating all descriptor ExtensionClasses this way. That
82
is, if an extension class is a descriptor, it's __get__ method will be
83
called even when it is retrieved from an instance.
84

85
>>> class O(Base):
86
...   def __get__(*a):
87
...      return a
88
...
89
>>> o1 = O()
90
>>> o2 = O()
91
>>> C.o1 = o1
92
>>> c.o2 = o2
93
>>> int(c.o1 == (o1, c, type(c)))
94
1
95
>>> int(C.o1 == (o1, None, type(c)))
96
1
97
>>> int(c.o2 == (o2, c, type(c)))
98
1
99
"""
100

101
import copyreg as copy_reg
7✔
102
import inspect
7✔
103
import os
7✔
104
import platform
7✔
105

106

107
_IS_PYPY = platform.python_implementation() == 'PyPy'
7✔
108
_IS_PURE = int(os.environ.get('PURE_PYTHON', '0'))
7✔
109
C_EXTENSION = not (_IS_PYPY or _IS_PURE)
7✔
110

111

112
def of_get(self, inst, type_=None):
7✔
113
    if not issubclass(type(type_), ExtensionClass):
7✔
114
        return self
7✔
115
    if inst is not None:
7✔
116
        return self.__of__(inst)
7✔
117
    return self
7✔
118

119

120
def pmc_init_of(cls):
7✔
121
    # set up __get__ if __of__ is implemented
122
    of = getattr(cls, '__of__', None)
7✔
123
    if of is not None:
7✔
124
        cls.__get__ = of_get
7✔
125
    else:
126
        get = getattr(cls, '__get__', None)
7✔
127
        if (get is not None and
7✔
128
                (get is of_get or getattr(get, '__func__', None) is of_get)):
129
            del cls.__get__
7✔
130

131

132
pmc_init_of_py = pmc_init_of
7✔
133

134
BasePy = type('dummy', (), {'__slots__': ()})
7✔
135
NoInstanceDictionaryBasePy = type('dummy', (), {'__slots__': ()})
7✔
136
ExtensionClassPy = type('dummy', (), {'__slots__': ()})
7✔
137

138

139
def _add_classic_mro(mro, cls):
7✔
140
    if cls not in mro:
7✔
141
        mro.append(cls)
7✔
142
    for base in cls.__bases__:
7✔
143
        if base not in mro:
7✔
144
            mro.append(base)
7✔
145
            _add_classic_mro(mro, base)
7✔
146

147

148
class ExtensionClass(type):
7✔
149

150
    def __new__(cls, name, bases=(), attrs=None):
7✔
151
        attrs = {} if attrs is None else attrs
7✔
152
        # Make sure we have an ExtensionClass instance as a base
153
        if (name != 'Base' and
7✔
154
                not any(isinstance(b, ExtensionClassPy) for b in bases)):
155
            bases += (BasePy,)
7✔
156
        if ('__slots__' not in attrs and
7✔
157
                any(issubclass(b, NoInstanceDictionaryBasePy) for b in bases)):
158
            attrs['__slots__'] = ()
7✔
159

160
        cls = type.__new__(cls, name, bases, attrs)
7✔
161

162
        # Inherit docstring
163
        if not cls.__doc__:
7✔
164
            cls.__doc__ = super(cls, cls).__doc__
7✔
165

166
        # set up __get__ if __of__ is implemented
167
        pmc_init_of_py(cls)
7✔
168

169
        # call class init method
170
        if hasattr(cls, '__class_init__'):
7✔
171
            class_init = cls.__class_init__
7✔
172
            if hasattr(class_init, '__func__'):
7✔
173
                class_init = class_init.__func__
7✔
174
            class_init(cls)
7✔
175
        return cls
7✔
176

177
    def __basicnew__(self):
7✔
178
        """Create a new empty object"""
179
        return self.__new__(self)
7✔
180

181
    def mro(self):
7✔
182
        """Compute an mro using the 'encapsulated base' scheme"""
183
        mro = [self]
7✔
184
        for base in self.__bases__:
7✔
185
            if hasattr(base, '__mro__'):
7✔
186
                for c in base.__mro__:
7✔
187
                    if c in (BasePy, NoInstanceDictionaryBasePy, object):
7✔
188
                        continue
7✔
189
                    if c in mro:
7✔
190
                        continue
7✔
191
                    mro.append(c)
7✔
192
            else:  # pragma: no cover (python 2 only)
193
                _add_classic_mro(mro, base)
194

195
        if NoInstanceDictionaryBasePy in self.__bases__:
7✔
196
            mro.append(NoInstanceDictionaryBasePy)
7✔
197
        elif self.__name__ != 'Base':
7✔
198
            mro.append(BasePy)
7✔
199
        mro.append(object)
7✔
200
        return mro
7✔
201

202
    def inheritedAttribute(self, name):
7✔
203
        """Look up an inherited attribute"""
204
        return getattr(super(self, self), name)
7✔
205

206
    def __setattr__(self, name, value):
7✔
207
        if name not in ('__get__', '__doc__', '__of__'):
7✔
208
            if (name.startswith('__') and name.endswith('__') and
7✔
209
                    name.count('_') == 4):
210
                raise TypeError(
7✔
211
                    "can't set attributes of built-in/extension type '%s.%s' "
212
                    "if the attribute name begins and ends with __ and "
213
                    "contains only 4 _ characters" %
214
                    (self.__module__, self.__name__))
215
        return type.__setattr__(self, name, value)
7✔
216

217

218
# Base and object are always moved to the last two positions
219
# in a subclasses mro, no matter how they are declared in the
220
# hierarchy. This means the Base_* methods effectively don't have
221
# to care or worry about using super(): it's always object.
222

223
def Base_getattro(self, name, _marker=object()):
7✔
224
    descr = marker = _marker
7✔
225

226
    # XXX: Why is this looping manually? The C code uses ``_PyType_Lookup``,
227
    # which is an internal function, but equivalent to ``getattr(type(self),
228
    # name)``.
229
    for base in type(self).__mro__:
7✔
230
        if name in base.__dict__:
7✔
231
            descr = base.__dict__[name]
7✔
232
            break
7✔
233

234
    # A data descriptor in the type has full control.
235
    if descr is not marker and inspect.isdatadescriptor(descr):
7✔
236
        return descr.__get__(self, type(self))
7✔
237

238
    # descr either wasn't defined, or it's not a data descriptor.
239
    try:
7✔
240
        # Don't do self.__dict__ otherwise you get recursion.
241
        # Not all instances will have dictionaries.
242
        inst_dict = object.__getattribute__(self, '__dict__')
7✔
243
    except AttributeError:
7✔
244
        pass
7✔
245
    else:
246
        try:
7✔
247
            descr = inst_dict[name]
7✔
248
        except KeyError:
7✔
249
            pass
7✔
250
        else:
251
            # If the tp_descr_get of res is of_get, then call it,
252
            # unless it is __parent__ --- we don't want to wrap that.
253
            # XXX: This isn't quite what the C implementation does. It actually
254
            # checks the get function. Here we test the type.
255
            if name == '__parent__' or not isinstance(descr, BasePy):
7✔
256
                return descr
7✔
257

258
    # Here, descr could be either a non-data descriptor
259
    # from the class dictionary, or *any* kind of object
260
    # from the instance dictionary. Unlike the way normal
261
    # Python classes handle non-data descriptors, we will invoke
262
    # __get__ even if it was found in the instance dictionary.
263
    if descr is not marker:
7✔
264
        try:
7✔
265
            descr_get = descr.__get__
7✔
266
        except AttributeError:
7✔
267
            return descr
7✔
268

269
        return descr_get(self, type(self))
7✔
270

271
    raise AttributeError(
7✔
272
        "'{:.50}' object has no attribute '{}'".format(
273
            type(self).__name__, name
274
        ))
275

276

277
def _slotnames(self):
7✔
278
    slotnames = copy_reg._slotnames(type(self))
7✔
279
    return [x for x in slotnames
7✔
280
            if not x.startswith('_p_') and
281
            not x.startswith('_v_')]
282

283

284
def Base__getstate__(self):
7✔
285
    idict = getattr(self, '__dict__', None)
7✔
286
    slotnames = _slotnames(self)
7✔
287
    if idict is not None:
7✔
288
        d = dict([x for x in idict.items()
7✔
289
                  if not x[0].startswith('_p_') and
290
                  not x[0].startswith('_v_')])
291
    else:
292
        d = None
7✔
293
    if slotnames:
7✔
294
        s = {}
7✔
295
        for slotname in slotnames:
7✔
296
            value = getattr(self, slotname, self)
7✔
297
            if value is not self:
7✔
298
                s[slotname] = value
7✔
299
        return d, s
7✔
300
    return d
7✔
301

302

303
def Base__setstate__(self, state):
7✔
304
    """ See IPersistent.
305
    """
306
    try:
7✔
307
        inst_dict, slots = state
7✔
308
    except BaseException:
7✔
309
        inst_dict, slots = state, ()
7✔
310
    idict = getattr(self, '__dict__', None)
7✔
311
    if inst_dict is not None:
7✔
312
        if idict is None:
7✔
313
            raise TypeError('No instance dict')  # pragma: no cover
314
        idict.clear()
7✔
315
        idict.update(inst_dict)
7✔
316
    slotnames = _slotnames(self)
7✔
317
    if slotnames:
7✔
318
        for k, v in slots.items():
7✔
319
            setattr(self, k, v)
7✔
320

321

322
def Base__reduce__(self):
7✔
323
    gna = getattr(self, '__getnewargs__', lambda: ())
7✔
324
    return (copy_reg.__newobj__,
7✔
325
            (type(self),) + gna(), self.__getstate__())
326

327

328
def Base__new__(cls, *args, **kw):
7✔
329
    return object.__new__(cls)
7✔
330

331

332
Base = ExtensionClass("Base", (object, ), {
7✔
333
    '__slots__': (),
334
    '__getattribute__': Base_getattro,
335
    '__getstate__': Base__getstate__,
336
    '__setstate__': Base__setstate__,
337
    '__reduce__': Base__reduce__,
338
    '__new__': Base__new__,
339
})
340

341

342
class NoInstanceDictionaryBase(Base):
7✔
343
    __slots__ = ()
7✔
344

345

346
BasePy = Base
7✔
347
ExtensionClassPy = ExtensionClass
7✔
348
NoInstanceDictionaryBasePy = NoInstanceDictionaryBase
7✔
349

350
if C_EXTENSION:  # pragma: no cover
351
    from ._ExtensionClass import *  # NOQA
352

353
# We always want to get the CAPI2 value (if possible) so that
354
# MethodObject and anything else using the PyExtensionClass_Export
355
# macro from ExtensionClass.h doesn't break with an AttributeError
356
try:
7✔
357
    from ._ExtensionClass import CAPI2  # noqa: F401 import unused
7✔
358
except ImportError:  # pragma: no cover
359
    pass
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc