Commit 763b9a94 authored by Jim Fulton's avatar Jim Fulton

Fixed: ZODB.Connection.TransactionMetaData didn't custom data

  storage that some storages rely on.
parent 9cfba5ed
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
Change History Change History
================ ================
- Fixed: ``ZODB.Connection.TransactionMetaData`` didn't custom data
storage that some storages rely on.
5.1.0 (2016-11-17) 5.1.0 (2016-11-17)
================== ==================
......
...@@ -28,14 +28,13 @@ from persistent.TimeStamp import TimeStamp ...@@ -28,14 +28,13 @@ from persistent.TimeStamp import TimeStamp
import ZODB.interfaces import ZODB.interfaces
from . import POSException, utils from . import POSException, utils
from .Connection import TransactionMetaData
from .utils import z64, oid_repr, byte_ord, byte_chr, load_current from .utils import z64, oid_repr, byte_ord, byte_chr, load_current
from .UndoLogCompatible import UndoLogCompatible from .UndoLogCompatible import UndoLogCompatible
from ._compat import dumps, _protocol, py2_hasattr from ._compat import dumps, _protocol, py2_hasattr
log = logging.getLogger("ZODB.BaseStorage") log = logging.getLogger("ZODB.BaseStorage")
class BaseStorage(UndoLogCompatible): class BaseStorage(UndoLogCompatible):
"""Base class that supports storage implementations. """Base class that supports storage implementations.
...@@ -359,25 +358,14 @@ def checkCurrentSerialInTransaction(self, oid, serial, transaction): ...@@ -359,25 +358,14 @@ def checkCurrentSerialInTransaction(self, oid, serial, transaction):
BaseStorage.checkCurrentSerialInTransaction = checkCurrentSerialInTransaction BaseStorage.checkCurrentSerialInTransaction = checkCurrentSerialInTransaction
@zope.interface.implementer(ZODB.interfaces.IStorageTransactionInformation) @zope.interface.implementer(ZODB.interfaces.IStorageTransactionInformation)
class TransactionRecord(object): class TransactionRecord(TransactionMetaData):
"""Abstract base class for iterator protocol""" """Abstract base class for iterator protocol"""
def __init__(self, tid, status, user, description, extension): def __init__(self, tid, status, user, description, extension):
self.tid = tid self.tid = tid
self.status = status self.status = status
self.user = user TransactionMetaData.__init__(self, user, description, extension)
self.description = description
self.extension = extension
# XXX This is a workaround to make the TransactionRecord compatible with a
# transaction object because it is passed to tpc_begin().
def _ext_set(self, value):
self.extension = value
def _ext_get(self):
return self.extension
_extension = property(fset=_ext_set, fget=_ext_get)
@zope.interface.implementer(ZODB.interfaces.IStorageRecordInformation) @zope.interface.implementer(ZODB.interfaces.IStorageRecordInformation)
class DataRecord(object): class DataRecord(object):
......
...@@ -1321,3 +1321,22 @@ class TransactionMetaData(object): ...@@ -1321,3 +1321,22 @@ class TransactionMetaData(object):
@_extension.setter @_extension.setter
def _extension(self, v): def _extension(self, v):
self.extension = v self.extension = v
def data(self, ob):
try:
data = self._data
except AttributeError:
raise KeyError(ob)
try:
return data[id(ob)]
except KeyError:
raise KeyError(ob)
def set_data(self, ob, ob_data):
try:
data = self._data
except AttributeError:
data = self._data = {}
data[id(ob)] = ob_data
...@@ -546,6 +546,28 @@ class IStorageTransactionMetaData(Interface): ...@@ -546,6 +546,28 @@ class IStorageTransactionMetaData(Interface):
extension = Attribute( extension = Attribute(
"A dictionary carrying a transaction's extended_info data") "A dictionary carrying a transaction's extended_info data")
def set_data(ob, data):
"""Hold data on behalf of an object
For objects such as storages that
work with multiple transactions, it's convenient to store
transaction-specific data on the transaction itself. The
transaction knows nothing about the data, but simply holds it
on behalf of the object.
The object passed should be the object that needs the data, as
opposed to simple object like a string. (Internally, the id of
the object is used as the key.)
"""
def data(ob):
"""Retrieve data held on behalf of an object.
See set_data.
"""
class IStorage(Interface): class IStorage(Interface):
"""A storage is responsible for storing and retrieving data of objects. """A storage is responsible for storing and retrieving data of objects.
......
...@@ -87,6 +87,23 @@ class TransactionMetaDataTests(unittest.TestCase): ...@@ -87,6 +87,23 @@ class TransactionMetaDataTests(unittest.TestCase):
self.assertEqual(t.description, b'description\xc2\x80') self.assertEqual(t.description, b'description\xc2\x80')
self.assertEqual(t.extension, dict(foo='FOO')) self.assertEqual(t.extension, dict(foo='FOO'))
def test_data(self):
t = TransactionMetaData()
# Can't get data that wasn't set:
with self.assertRaises(KeyError) as c:
t.data(self)
self.assertEqual(c.exception.args, (self,))
data = dict(a=1)
t.set_data(self, data)
self.assertEqual(t.data(self), data)
# Can't get something we haven't stored.
with self.assertRaises(KeyError) as c:
t.data(data)
self.assertEqual(c.exception.args, (data,))
def test_suite(): def test_suite():
return unittest.makeSuite(TransactionMetaDataTests) return unittest.makeSuite(TransactionMetaDataTests)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment