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

zopefoundation / ZODB / 18153960591

01 Oct 2025 06:50AM UTC coverage: 83.781% (-0.03%) from 83.811%
18153960591

Pull #415

github

web-flow
Update docs/articles/old-guide/convert_zodb_guide.py

Co-authored-by: Michael Howitz <icemac@gmx.net>
Pull Request #415: Apply the latest zope.meta templates

2441 of 3542 branches covered (68.92%)

193 of 257 new or added lines in 48 files covered. (75.1%)

12 existing lines in 6 files now uncovered.

13353 of 15938 relevant lines covered (83.78%)

0.84 hits per line

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

81.42
/src/ZODB/POSException.py
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
"""ZODB-defined exceptions
15

16
$Id$"""
17

18
import transaction.interfaces
1✔
19
# BBB: We moved the two transactions to the transaction package
20
from transaction.interfaces import TransactionError  # noqa: F401 import unused
1✔
21
from transaction.interfaces import TransactionFailedError  # noqa: F401
1✔
22

23
from ZODB.utils import oid_repr
1✔
24
from ZODB.utils import readable_tid_repr
1✔
25

26

27
def _fmt_undo(oid, reason):
1✔
28
    s = reason and (": %s" % reason) or ""
×
NEW
29
    return f"Undo error {oid_repr(oid)}{s}"
×
30

31

32
def _recon(class_, state):
1✔
33
    err = class_.__new__(class_)
×
34
    err.__setstate__(state)
×
35
    return err
×
36

37

38
_recon.__no_side_effects__ = True
1✔
39

40

41
class POSError(Exception):
1✔
42
    """Persistent object system error."""
43

44
    def __reduce__(self):
1✔
45
        # Copy extra data from internal structures
46
        state = self.__dict__.copy()
×
47
        state['args'] = self.args
×
48

49
        return (_recon, (self.__class__, state))
×
50

51
    def __setstate__(self, state):
1✔
52
        # PyPy doesn't store the 'args' attribute in an instance's
53
        # __dict__; instead, it uses what amounts to a slot. Because
54
        # we customize the pickled representation to just be a dictionary,
55
        # the args would then get lost, leading to unprintable exceptions
56
        # and worse. Manually assign to args from the state to be sure
57
        # this doesn't happen.
58
        super().__setstate__(state)
×
59
        self.args = state['args']
×
60

61

62
class POSKeyError(POSError, KeyError):
1✔
63
    """Key not found in database."""
64

65
    def __str__(self):
1✔
66
        return oid_repr(self.args[0])
1✔
67

68

69
class ConflictError(POSError, transaction.interfaces.TransientError):
1✔
70
    """Two transactions tried to modify the same object at once.
71

72
    This transaction should be resubmitted.
73

74
    Instance attributes:
75
      oid : string
76
        the OID (8-byte packed string) of the object in conflict
77
      class_name : string
78
        the fully-qualified name of that object's class
79
      message : string
80
        a human-readable explanation of the error
81
      serials : (string, string)
82
        a pair of 8-byte packed strings; these are the serial numbers
83
        related to conflict.  The first is the revision of object that
84
        is in conflict, the currently committed serial.  The second is
85
        the revision the current transaction read when it started.
86
      data : string
87
        The database record that failed to commit, used to put the
88
        class name in the error message.
89

90
    The caller should pass either object or oid as a keyword argument,
91
    but not both of them.  If object is passed, it should be a
92
    persistent object with an _p_oid attribute.
93
    """
94

95
    def __init__(self, message=None, object=None, oid=None, serials=None,
1✔
96
                 data=None):
97
        if message is None:
1✔
98
            self.message = "database conflict error"
1✔
99
        else:
100
            self.message = message
1✔
101

102
        if object is None:
1✔
103
            self.oid = None
1✔
104
            self.class_name = None
1✔
105
        else:
106
            self.oid = object._p_oid
1✔
107
            klass = object.__class__
1✔
108
            self.class_name = klass.__module__ + "." + klass.__name__
1✔
109

110
        if oid is not None:
1✔
111
            assert self.oid is None
1✔
112
            self.oid = oid
1✔
113

114
        if data is not None:
1✔
115
            # avoid circular import chain
116
            from ZODB.utils import get_pickle_metadata
1✔
117
            self.class_name = '.'.join(get_pickle_metadata(data))
1✔
118

119
        self.serials = serials
1✔
120

121
    def __str__(self):
1✔
122
        extras = []
1✔
123
        if self.oid:
1✔
124
            extras.append("oid %s" % oid_repr(self.oid))
1✔
125
        if self.class_name:
1✔
126
            extras.append("class %s" % self.class_name)
1✔
127
        if self.serials:
1✔
128
            current, old = self.serials
1✔
129
            extras.append("serial this txn started with %s" %
1✔
130
                          readable_tid_repr(old))
131
            extras.append("serial currently committed %s" %
1✔
132
                          readable_tid_repr(current))
133
        if extras:
1✔
134
            return "{} ({})".format(self.message, ", ".join(extras))
1✔
135
        else:
136
            return self.message
1✔
137

138
    def get_oid(self):
1✔
139
        return self.oid
×
140

141
    def get_class_name(self):
1✔
142
        return self.class_name
×
143

144
    def get_old_serial(self):
1✔
145
        return self.serials[1]
×
146

147
    def get_new_serial(self):
1✔
148
        return self.serials[0]
×
149

150
    def get_serials(self):
1✔
151
        return self.serials
×
152

153

154
class ReadConflictError(ConflictError):
1✔
155
    """Conflict detected when object was requested to stay unchanged.
156

157
    An object was requested to stay not modified via
158
    checkCurrentSerialInTransaction, and at commit time was found to be
159
    changed by another transaction (eg. another thread or process).
160

161
    Note: for backward compatibility ReadConflictError is also raised on
162
    plain object access if
163

164
      - object is found to be removed, and
165
      - there is possibility that database pack was running simultaneously.
166
    """
167

168
    def __init__(self, message=None, object=None, serials=None, **kw):
1✔
169
        if message is None:
1✔
170
            message = "database read conflict error"
1✔
171
        ConflictError.__init__(self, message=message, object=object,
1✔
172
                               serials=serials, **kw)
173

174

175
class BTreesConflictError(ConflictError):
1✔
176
    """A special subclass for BTrees conflict errors."""
177

178
    msgs = [
1✔
179
        # 0; i2 or i3 bucket split; positions are all -1
180
        'Conflicting bucket split',
181

182
        # 1; keys the same, but i2 and i3 values differ, and both values
183
        # differ from i1's value
184
        'Conflicting changes',
185

186
        # 2; i1's value changed in i2, but key+value deleted in i3
187
        'Conflicting delete and change',
188

189
        # 3; i1's value changed in i3, but key+value deleted in i2
190
        'Conflicting delete and change',
191

192
        # 4; i1 and i2 both added the same key, or both deleted the
193
        # same key
194
        'Conflicting inserts or deletes',
195

196
        # 5;  i2 and i3 both deleted the same key
197
        'Conflicting deletes',
198

199
        # 6; i2 and i3 both added the same key
200
        'Conflicting inserts',
201

202
        # 7; i2 and i3 both deleted the same key, or i2 changed the value
203
        # associated with a key and i3 deleted that key
204
        'Conflicting deletes, or delete and change',
205

206
        # 8; i2 and i3 both deleted the same key, or i3 changed the value
207
        # associated with a key and i2 deleted that key
208
        'Conflicting deletes, or delete and change',
209

210
        # 9; i2 and i3 both deleted the same key
211
        'Conflicting deletes',
212

213
        # 10; i2 and i3 deleted all the keys, and didn't insert any,
214
        # leaving an empty bucket; conflict resolution doesn't have
215
        # enough info to unlink an empty bucket from its containing
216
        # BTree correctly
217
        'Empty bucket from deleting all keys',
218

219
        # 11; conflicting changes in an internal BTree node
220
        'Conflicting changes in an internal BTree node',
221

222
        # 12; i2 or i3 was empty
223
        'Empty bucket in a transaction',
224

225
        # 13; delete of first key, which causes change to parent node
226
        'Delete of first key',
227
    ]
228

229
    def __init__(self, p1, p2, p3, reason):
1✔
230
        self.p1 = p1
1✔
231
        self.p2 = p2
1✔
232
        self.p3 = p3
1✔
233
        self.reason = reason
1✔
234

235
    def __repr__(self):
1✔
236
        return "BTreesConflictError(%d, %d, %d, %d)" % (self.p1,
×
237
                                                        self.p2,
238
                                                        self.p3,
239
                                                        self.reason)
240

241
    def __str__(self):
1✔
242
        return "BTrees conflict error at %d/%d/%d: %s" % (
1✔
243
            self.p1, self.p2, self.p3, self.msgs[self.reason])
244

245

246
class DanglingReferenceError(
1✔
247
        POSError, transaction.interfaces.TransactionError):
248
    """An object has a persistent reference to a missing object.
249

250
    If an object is stored and it has a reference to another object
251
    that does not exist (for example, it was deleted by pack), this
252
    exception may be raised.  Whether a storage supports this feature,
253
    it a quality of implementation issue.
254

255
    Instance attributes:
256
    referer: oid of the object being written
257
    missing: referenced oid that does not have a corresponding object
258
    """
259

260
    def __init__(self, Aoid, Boid):
1✔
261
        self.referer = Aoid
×
262
        self.missing = Boid
×
263

264
    def __str__(self):
1✔
265
        return "from {} to {}".format(oid_repr(self.referer),
×
266
                                      oid_repr(self.missing))
267

268

269
############################################################################
270
# Only used in storages; versions are no longer supported.
271

272
class VersionError(POSError):
1✔
273
    """An error in handling versions occurred."""
274

275

276
class VersionCommitError(VersionError):
1✔
277
    """An invalid combination of versions was used in a version commit."""
278

279

280
class VersionLockError(VersionError, transaction.interfaces.TransactionError):
1✔
281
    """Modification to an object modified in an unsaved version.
282

283
    An attempt was made to modify an object that has been modified in an
284
    unsaved version.
285
    """
286
############################################################################
287

288

289
class UndoError(POSError):
1✔
290
    """An attempt was made to undo a non-undoable transaction."""
291

292
    def __init__(self, reason, oid=None):
1✔
293
        self._reason = reason
1✔
294
        self._oid = oid
1✔
295

296
    def __str__(self):
1✔
297
        return _fmt_undo(self._oid, self._reason)
×
298

299

300
class MultipleUndoErrors(UndoError):
1✔
301
    """Several undo errors occurred during a single transaction."""
302

303
    def __init__(self, errs):
1✔
304
        # provide a reason and oid for clients that only look at that
305
        UndoError.__init__(self, *errs[0])
1✔
306
        self._errs = errs
1✔
307

308
    def __str__(self):
1✔
309
        return "\n".join([_fmt_undo(*pair) for pair in self._errs])
×
310

311

312
class StorageError(POSError):
1✔
313
    """Base class for storage based exceptions."""
314

315

316
class StorageTransactionError(StorageError):
1✔
317
    """An operation was invoked for an invalid transaction or state."""
318

319

320
class StorageSystemError(StorageError):
1✔
321
    """Panic! Internal storage error!"""
322

323

324
class MountedStorageError(StorageError):
1✔
325
    """Unable to access mounted storage."""
326

327

328
class ReadOnlyError(StorageError):
1✔
329
    """Unable to modify objects in a read-only storage."""
330

331

332
class TransactionTooLargeError(StorageTransactionError):
1✔
333
    """The transaction exhausted some finite storage resource."""
334

335

336
class ExportError(POSError):
1✔
337
    """An export file doesn't have the right format."""
338

339

340
class Unsupported(POSError):
1✔
341
    """A feature was used that is not supported by the storage."""
342

343

344
class ReadOnlyHistoryError(POSError):
1✔
345
    """Unable to add or modify objects in an historical connection."""
346

347

348
class InvalidObjectReference(POSError):
1✔
349
    """An object contains an invalid reference to another object.
350

351
    An invalid reference may be one of:
352

353
    o A reference to a wrapped persistent object.
354

355
    o A reference to an object in a different database connection.
356

357
    TODO:  The exception ought to have a member that is the invalid object.
358
    """
359

360

361
class ConnectionStateError(POSError):
1✔
362
    """A Connection isn't in the required state for an operation.
363

364
    o An operation such as a load is attempted on a closed connection.
365

366
    o An attempt to close a connection is made while the connection is
367
      still joined to a transaction (for example, a transaction is in
368
      progress, with uncommitted modifications in the connection).
369
    """
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