/usr/share/pyshared/ZODB/utils.txt is in python-zodb 1:3.9.7-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | ZODB Utilits Module
===================
The ZODB.utils module provides a number of helpful, somewhat random
:), utility functions.
>>> import ZODB.utils
This document documents a few of them. Over time, it may document
more.
64-bit integers and strings
---------------------------------
ZODB uses 64-bit transaction ids that are typically represented as
strings, but are sometimes manipulated as integers. Object ids are
strings too and it is common to ise 64-bit strings that are just
packed integers.
Functions p64 and u64 pack and unpack integers as strings:
>>> ZODB.utils.p64(250347764455111456)
'\x03yi\xf7"\xa8\xfb '
>>> print ZODB.utils.u64('\x03yi\xf7"\xa8\xfb ')
250347764455111456
The contant z64 has zero packed as a 64-bit string:
>>> ZODB.utils.z64
'\x00\x00\x00\x00\x00\x00\x00\x00'
Transaction id generation
-------------------------
Storages assign transaction ids as transactions are committed. These
are based on UTC time, but must be strictly increasing. The
newTid function akes this pretty easy.
To see this work (in a predictable way), we'll first hack time.time:
>>> import time
>>> old_time = time.time
>>> time.time = lambda : 1224825068.12
Now, if we ask for a new time stamp, we'll get one based on our faux
time:
>>> tid = ZODB.utils.newTid(None)
>>> tid
'\x03yi\xf7"\xa54\x88'
newTid requires an old tid as an argument. The old tid may be None, if
we don't have a previous transaction id.
This time is based on the current time, which we can see by converting
it to a time stamp.
>>> import ZODB.TimeStamp
>>> print ZODB.TimeStamp.TimeStamp(tid)
2008-10-24 05:11:08.120000
To assure that we get a new tid that is later than the old, we can
pass an existing tid. Let's pass the tid we just got.
>>> tid2 = ZODB.utils.newTid(tid)
>>> long(ZODB.utils.u64(tid)), long(ZODB.utils.u64(tid2))
(250347764454864008L, 250347764454864009L)
Here, since we called it at the same time, we got a time stamp that
was only slightly larger than the previos one. Of course, at a later
time, the time stamp we get will be based on the time:
>>> time.time = lambda : 1224825069.12
>>> tid = ZODB.utils.newTid(tid2)
>>> print ZODB.TimeStamp.TimeStamp(tid)
2008-10-24 05:11:09.120000
>>> time.time = old_time
Locking support
---------------
Storages are required to be thread safe. The locking descriptor helps
automate that. It arranges for a lock to be acquired when a function
is called and released when a function exits. To demonstrate this,
we'll create a "lock" type that simply prints when it is called:
>>> class Lock:
... def acquire(self):
... print 'acquire'
... def release(self):
... print 'release'
Now we'll demonstrate the descriptor:
>>> class C:
... _lock = Lock()
... _lock_acquire = _lock.acquire
... _lock_release = _lock.release
...
... @ZODB.utils.locked
... def meth(self, *args, **kw):
... print 'meth', args, kw
The descriptor expects the instance it wraps to have a '_lock
attribute.
>>> C().meth(1, 2, a=3)
acquire
meth (1, 2) {'a': 3}
release
.. Edge cases
We can get the method from the class:
>>> C.meth # doctest: +ELLIPSIS
<ZODB.utils.Locked object at ...>
>>> C.meth(C())
acquire
meth () {}
release
>>> class C2:
... _lock = Lock()
... _lock_acquire = _lock.acquire
... _lock_release = _lock.release
>>> C.meth(C2()) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
TypeError: unbound method meth() must be called with C instance
as first argument (got C2 instance instead)
Preconditions
-------------
Often, we want to supply method preconditions. The locking descriptor
supports optional method preconditions [1]_.
>>> class C:
... def __init__(self):
... _lock = Lock()
... self._lock_acquire = _lock.acquire
... self._lock_release = _lock.release
... self._opened = True
... self._transaction = None
...
... def opened(self):
... """The object is open
... """
... print 'checking if open'
... return self._opened
...
... def not_in_transaction(self):
... """The object is not in a transaction
... """
... print 'checking if in a transaction'
... return self._transaction is None
...
... @ZODB.utils.locked(opened, not_in_transaction)
... def meth(self, *args, **kw):
... print 'meth', args, kw
>>> c = C()
>>> c.meth(1, 2, a=3)
acquire
checking if open
checking if in a transaction
meth (1, 2) {'a': 3}
release
>>> c._transaction = 1
>>> c.meth(1, 2, a=3) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
AssertionError:
('Failed precondition: ', 'The object is not in a transaction')
>>> c._opened = False
>>> c.meth(1, 2, a=3) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
AssertionError: ('Failed precondition: ', 'The object is open')
.. [1] Arguably, preconditions should be handled via separate
descriptors, but for ZODB storages, almost all methods need to be
locked. Combining preconditions with locking provides both
efficiency and concise expressions. A more general-purpose
facility would almost certainly provide separate descriptors for
preconditions.
|