Commit 1ebbdf77 authored by Jim Fulton's avatar Jim Fulton

Document history, undoLog and undoInfo in IDatabase and have them return text

for user_name and description
parent 539e5f82
......@@ -901,7 +901,7 @@ class DB(object):
See :meth:`ZODB.interfaces.IStorage.history`.
"""
return self.storage.history(oid, size)
return text_transaction_info(self.storage.history(oid, size))
def supportsUndo(self):
"""Return whether the database supports undo.
......@@ -920,7 +920,7 @@ class DB(object):
if not self.supportsUndo():
return ()
return self.storage.undoLog(*args, **kw)
return text_transaction_info(self.storage.undoLog(*args, **kw))
def undoInfo(self, *args, **kw):
"""Return a sequence of descriptions for transactions.
......@@ -929,7 +929,7 @@ class DB(object):
"""
if not self.supportsUndo():
return ()
return self.storage.undoInfo(*args, **kw)
return text_transaction_info(self.storage.undoInfo(*args, **kw))
def undoMultiple(self, ids, txn=None):
"""Undo multiple transactions identified by ids.
......@@ -1064,3 +1064,12 @@ def connection(*args, **kw):
managing a separate database object.
"""
return DB(*args, **kw).open_then_close_db_when_connection_closes()
transaction_meta_data_text_variables = 'user_name', 'description'
def text_transaction_info(info):
for d in info:
for name in transaction_meta_data_text_variables:
if name in d:
d[name] = d[name].decode('utf-8')
return info
......@@ -383,7 +383,41 @@ class IDatabase(IStorageDB):
include useful objects.
"""
# TODO: Should this method be moved into some subinterface?
def history(oid, size=1):
"""Return a sequence of history information dictionaries.
Up to size objects (including no objects) may be returned.
The information provides a log of the changes made to the
object. Data are reported in reverse chronological order.
Each dictionary has the following keys:
time
UTC seconds since the epoch (as in time.time) that the
object revision was committed.
tid
The transaction identifier of the transaction that
committed the version.
user_name
The text (unicode) user identifier, if any (or an empty
string) of the user on whos behalf the revision was
committed.
description
The text (unicode) transaction description for the
transaction that committed the revision.
size
The size of the revision data record.
If the transaction had extension items, then these items are
also included if they don't conflict with the keys above.
"""
def pack(t=None, days=0):
"""Pack the storage, deleting unused object revisions.
......@@ -400,7 +434,76 @@ class IDatabase(IStorageDB):
time if t is not specified.
"""
# TODO: Should this method be moved into some subinterface?
def undoLog(first, last, filter=None):
"""Return a sequence of descriptions for undoable transactions.
Application code should call undoLog() on a DB instance instead of on
the storage directly.
A transaction description is a mapping with at least these keys:
"time": The time, as float seconds since the epoch, when
the transaction committed.
"user_name": The text value of the `.user` attribute on that
transaction.
"description": The text value of the `.description` attribute on
that transaction.
"id`" A bytes uniquely identifying the transaction to the
storage. If it's desired to undo this transaction,
this is the `transaction_id` to pass to `undo()`.
In addition, if any name+value pairs were added to the transaction
by `setExtendedInfo()`, those may be added to the transaction
description mapping too (for example, FileStorage's `undoLog()` does
this).
`filter` is a callable, taking one argument. A transaction
description mapping is passed to `filter` for each potentially
undoable transaction. The sequence returned by `undoLog()` excludes
descriptions for which `filter` returns a false value. By default,
`filter` always returns a true value.
ZEO note: Arbitrary callables cannot be passed from a ZEO client
to a ZEO server, and a ZEO client's implementation of `undoLog()`
ignores any `filter` argument that may be passed. ZEO clients
should use the related `undoInfo()` method instead (if they want
to do filtering).
Now picture a list containing descriptions of all undoable
transactions that pass the filter, most recent transaction first (at
index 0). The `first` and `last` arguments specify the slice of this
(conceptual) list to be returned:
`first`: This is the index of the first transaction description
in the slice. It must be >= 0.
`last`: If >= 0, first:last acts like a Python slice, selecting
the descriptions at indices `first`, first+1, ..., up to
but not including index `last`. At most last-first
descriptions are in the slice, and `last` should be at
least as large as `first` in this case. If `last` is
less than 0, then abs(last) is taken to be the maximum
number of descriptions in the slice (which still begins
at index `first`). When `last` < 0, the same effect
could be gotten by passing the positive first-last for
`last` instead.
"""
def undoInfo(first=0, last=-20, specification=None):
"""Return a sequence of descriptions for undoable transactions.
This is like `undoLog()`, except for the `specification` argument.
If given, `specification` is a dictionary, and `undoInfo()`
synthesizes a `filter` function `f` for `undoLog()` such that
`f(desc)` returns true for a transaction description mapping
`desc` if and only if `desc` maps each key in `specification` to
the same value `specification` maps that key to. In other words,
only extensions (or supersets) of `specification` match.
ZEO note: `undoInfo()` passes the `specification` argument from a
ZEO client to its ZEO server (while a ZEO client ignores any `filter`
argument passed to `undoLog()`).
"""
def undo(id, txn=None):
"""Undo a transaction identified by id.
......@@ -940,10 +1043,10 @@ class IStorageUndoable(IStorage):
"time": The time, as float seconds since the epoch, when
the transaction committed.
"user_name": The value of the `.user` attribute on that
transaction, **bytes**.
"description": The value of the `.description` attribute on
that transaction, **bytes**.
"user_name": The bytes value of the `.user` attribute on that
transaction.
"description": The bytes value of the `.description` attribute on
that transaction.
"id`" A bytes uniquely identifying the transaction to the
storage. If it's desired to undo this transaction,
this is the `transaction_id` to pass to `undo()`.
......@@ -983,7 +1086,6 @@ class IStorageUndoable(IStorage):
could be gotten by passing the positive first-last for
`last` instead.
"""
# DB pass through
def undoInfo(first=0, last=-20, specification=None):
"""Return a sequence of descriptions for undoable transactions.
......@@ -1000,7 +1102,6 @@ class IStorageUndoable(IStorage):
ZEO client to its ZEO server (while a ZEO client ignores any `filter`
argument passed to `undoLog()`).
"""
# DB pass-through
class IMVCCStorage(IStorage):
......
......@@ -11,6 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from six import PY2
from ZODB.tests.MinPO import MinPO
import doctest
......@@ -75,6 +76,39 @@ class DBTests(ZODB.tests.util.TestCase):
import ZODB.serialize
self.assertTrue(self.db.references is ZODB.serialize.referencesf)
def test_history_and_undo_meta_data_text_handlinf(self):
db = self.db
conn = db.open()
for i in range(3):
with conn.transaction_manager as t:
t.note(u'work %s' % i)
t.setUser(u'user%s' % i)
conn.root()[i] = 42
conn.close()
from ZODB.utils import z64
def check(info, text):
for i, h in enumerate(reversed(info)):
for (name, expect) in (('description', 'work %s'),
('user_name', '/ user%s')):
expect = expect % i
if not text:
expect = expect.encode('ascii')
self.assertEqual(h[name], expect)
if PY2:
expect = unicode if text else str
for name in 'description', 'user_name':
self.assertTrue(isinstance(h[name], expect))
check(db.storage.history(z64, 3), False)
check(db.storage.undoLog(0, 3) , False)
check(db.storage.undoInfo(0, 3) , False)
check(db.history(z64, 3), True)
check(db.undoLog(0, 3) , True)
check(db.undoInfo(0, 3) , True)
def test_invalidateCache():
"""The invalidateCache method invalidates a connection caches for all of
......
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