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

zopefoundation / ZODB / 5636305215

pending completion
5636305215

push

github

web-flow
Drop support for Python < 3.7 (#386)

* Bumped version for breaking release.

* Drop support for Python 2.7, 3.5, 3.6.

---------

Co-authored-by: Jens Vagelpohl <jens@plyp.com>

2877 of 4050 branches covered (71.04%)

554 of 554 new or added lines in 89 files covered. (100.0%)

13323 of 15914 relevant lines covered (83.72%)

0.84 hits per line

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

89.47
/src/ZODB/persistentclass.py
1
##############################################################################
2
#
3
# Copyright (c) 2004 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
"""Persistent Class Support
1✔
15

16
$Id$
17
"""
18

19

20
# Notes:
21
#
22
# Persistent classes are non-ghostable.  This has some interesting
23
# ramifications:
24
#
25
# - When an object is invalidated, it must reload its state
26
#
27
# - When an object is loaded from the database, its state must be
28
#   loaded.  Unfortunately, there isn't a clear signal when an object is
29
#   loaded from the database.  This should probably be fixed.
30
#
31
#   In the mean time, we need to infer.  This should be viewed as a
32
#   short term hack.
33
#
34
#   Here's the strategy we'll use:
35
#
36
#   - We'll have a need to be loaded flag that we'll set in
37
#     __new__, through an extra argument.
38
#
39
#   - When setting _p_oid and _p_jar, if both are set and we need to be
40
#     loaded, then we'll load out state.
41
#
42
#   - We'll use _p_changed is None to indicate that we're in this state.
43
#
44

45
class _p_DataDescr:
1✔
46
    # Descr used as base for _p_ data. Data are stored in
47
    # _p_class_dict.
48

49
    def __init__(self, name):
1✔
50
        self.__name__ = name
1✔
51

52
    def __get__(self, inst, cls):
1✔
53
        if inst is None:
1✔
54
            return self
1✔
55

56
        if '__global_persistent_class_not_stored_in_DB__' in inst.__dict__:
1!
57
            raise AttributeError(self.__name__)
×
58
        return inst._p_class_dict.get(self.__name__)
1✔
59

60
    def __set__(self, inst, v):
1✔
61
        inst._p_class_dict[self.__name__] = v
1✔
62

63
    def __delete__(self, inst):
1✔
64
        raise AttributeError(self.__name__)
×
65

66

67
class _p_oid_or_jar_Descr(_p_DataDescr):
1✔
68
    # Special descr for _p_oid and _p_jar that loads
69
    # state when set if both are set and _p_changed is None
70
    #
71
    # See notes above
72

73
    def __set__(self, inst, v):
1✔
74
        get = inst._p_class_dict.get
1✔
75
        if v == get(self.__name__):
1!
76
            return
×
77

78
        inst._p_class_dict[self.__name__] = v
1✔
79

80
        jar = get('_p_jar')
1✔
81
        if (jar is not None
1✔
82
                and get('_p_oid') is not None
83
                and get('_p_changed') is None):
84
            jar.setstate(inst)
1✔
85

86

87
class _p_ChangedDescr:
1✔
88
    # descriptor to handle special weird semantics of _p_changed
89

90
    def __get__(self, inst, cls):
1✔
91
        if inst is None:
1!
92
            return self
×
93
        return inst._p_class_dict['_p_changed']
1✔
94

95
    def __set__(self, inst, v):
1✔
96
        if v is None:
1!
97
            return
×
98
        inst._p_class_dict['_p_changed'] = bool(v)
1✔
99

100
    def __delete__(self, inst):
1✔
101
        inst._p_invalidate()
×
102

103

104
class _p_MethodDescr:
1✔
105
    """Provide unassignable class attributes
106
    """
107

108
    def __init__(self, func):
1✔
109
        self.func = func
1✔
110

111
    def __get__(self, inst, cls):
1✔
112
        if inst is None:
1✔
113
            return cls
1✔
114
        return self.func.__get__(inst, cls)
1✔
115

116
    def __set__(self, inst, v):
1✔
117
        raise AttributeError(self.__name__)
×
118

119
    def __delete__(self, inst):
1✔
120
        raise AttributeError(self.__name__)
×
121

122

123
special_class_descrs = '__dict__', '__weakref__'
1✔
124

125

126
class PersistentMetaClass(type):
1✔
127

128
    _p_jar = _p_oid_or_jar_Descr('_p_jar')
1✔
129
    _p_oid = _p_oid_or_jar_Descr('_p_oid')
1✔
130
    _p_changed = _p_ChangedDescr()
1✔
131
    _p_serial = _p_DataDescr('_p_serial')
1✔
132

133
    def __new__(self, name, bases, cdict, _p_changed=False):
1✔
134
        cdict = {k: v
1✔
135
                 for (k, v) in cdict.items()
136
                 if not k.startswith('_p_')}
137
        cdict['_p_class_dict'] = {'_p_changed': _p_changed}
1✔
138
        return super().__new__(
1✔
139
            self, name, bases, cdict)
140

141
    def __getnewargs__(self):
1✔
142
        return self.__name__, self.__bases__, {}, None
1✔
143

144
    __getnewargs__ = _p_MethodDescr(__getnewargs__)
1✔
145

146
    def _p_maybeupdate(self, name):
1✔
147
        get = self._p_class_dict.get
1✔
148
        data_manager = get('_p_jar')
1✔
149

150
        if (
1✔
151
            (data_manager is not None)
152
            and
153
            (get('_p_oid') is not None)
154
            and
155
            (get('_p_changed') is False)
156
        ):
157

158
            self._p_changed = True
1✔
159
            data_manager.register(self)
1✔
160

161
    def __setattr__(self, name, v):
1✔
162
        if not (name.startswith('_p_') or name.startswith('_v')):
1✔
163
            self._p_maybeupdate(name)
1✔
164
        super().__setattr__(name, v)
1✔
165

166
    def __delattr__(self, name):
1✔
167
        if not (name.startswith('_p_') or name.startswith('_v')):
1✔
168
            self._p_maybeupdate(name)
1✔
169
        super().__delattr__(name)
1✔
170

171
    def _p_deactivate(self):
1✔
172
        # persistent classes can't be ghosts
173
        pass
×
174

175
    _p_deactivate = _p_MethodDescr(_p_deactivate)
1✔
176

177
    def _p_invalidate(self):
1✔
178
        # reset state
179
        self._p_class_dict['_p_changed'] = None
1✔
180
        self._p_jar.setstate(self)
1✔
181

182
    _p_invalidate = _p_MethodDescr(_p_invalidate)
1✔
183

184
    def __getstate__(self):
1✔
185
        return (self.__bases__,
1✔
186
                {k: v
187
                 for (k, v) in self.__dict__.items()
188
                 if not (k.startswith('_p_')
189
                         or k.startswith('_v_')
190
                         or k in special_class_descrs)})
191

192
    __getstate__ = _p_MethodDescr(__getstate__)
1✔
193

194
    def __setstate__(self, state):
1✔
195
        bases, cdict = state
1✔
196
        if self.__bases__ != bases:
1✔
197
            # __getnewargs__ should've taken care of that
198
            raise AssertionError(self.__bases__, '!=', bases)
199
        cdict = {k: v
1✔
200
                 for (k, v) in cdict.items()
201
                 if not k.startswith('_p_')}
202

203
        _p_class_dict = self._p_class_dict
1✔
204
        self._p_class_dict = {}
1✔
205

206
        to_remove = [k for k in self.__dict__
1✔
207
                     if ((k not in cdict)
208
                         and
209
                         (k not in special_class_descrs)
210
                         and
211
                         (k != '_p_class_dict')
212
                         )]
213

214
        for k in to_remove:
1✔
215
            delattr(self, k)
1✔
216

217
        for k, v in cdict.items():
1✔
218
            setattr(self, k, v)
1✔
219

220
        self._p_class_dict = _p_class_dict
1✔
221

222
        self._p_changed = False
1✔
223

224
    __setstate__ = _p_MethodDescr(__setstate__)
1✔
225

226
    def _p_activate(self):
1✔
227
        self._p_jar.setstate(self)
×
228

229
    _p_activate = _p_MethodDescr(_p_activate)
1✔
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

© 2026 Coveralls, Inc