Skip to content

Commit

Permalink
Always format 8-byte oids as ints in hexadecimal.
Browse files Browse the repository at this point in the history
Fixes #95

Built on #97 as they touch the same code.
  • Loading branch information
jamadden committed Oct 19, 2018
1 parent 67447f2 commit 14a1ddd
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
and the behaviour prior to 4.4.0. See `issue 92
<https://github.com/zopefoundation/persistent/issues/92>`_.

- Change the repr of persistent objects to format the OID as in
integer in hexadecimal notation if it is an 8-byte byte string, as
ZODB does. This eliminates some issues in doctests. See `issue 95
<https://github.com/zopefoundation/persistent/pull/95>`_.

4.4.2 (2018-08-28)
------------------
Expand Down
24 changes: 21 additions & 3 deletions persistent/cPersistence.c
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,9 @@ Per_repr(cPersistentObject *self)
PyObject *jar_str = NULL;
PyObject *result = NULL;

unsigned char* oid_bytes;
unsigned long long oid_value;

prepr = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "_p_repr");
if (prepr)
{
Expand All @@ -1448,9 +1451,24 @@ Per_repr(cPersistentObject *self)
prepr_exc_str = PyUnicode_FromString("");
}

oid_str = repr_helper(self->oid, " oid %R");
if (!oid_str)
goto cleanup;
if (self->oid && PyBytes_Check(self->oid) && PyBytes_GET_SIZE(self->oid) == 8) {
oid_bytes = (unsigned char*)PyBytes_AS_STRING(self->oid);
oid_value = ((unsigned long long)oid_bytes[0] << 56)
| ((unsigned long long)oid_bytes[1] << 48)
| ((unsigned long long)oid_bytes[2] << 40)
| ((unsigned long long)oid_bytes[3] << 32)
| ((unsigned long long)oid_bytes[4] << 24)
| ((unsigned long long)oid_bytes[5] << 16)
| ((unsigned long long)oid_bytes[6] << 8)
| ((unsigned long long)oid_bytes[7]);
oid_str = PyUnicode_FromFormat(" oid 0x%x", oid_value);
}

if (!oid_str) {
oid_str = repr_helper(self->oid, " oid %R");
if (!oid_str)
goto cleanup;
}

jar_str = repr_helper(self->jar, " in %R");
if (!jar_str)
Expand Down
12 changes: 10 additions & 2 deletions persistent/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################

import struct

from zope.interface import implementer

Expand Down Expand Up @@ -52,6 +52,11 @@
# check in __getattribute__
_SPECIAL_NAMES = set(SPECIAL_NAMES)

# Represent 8-byte OIDs as hex integer, just like
# ZODB does.
_OID_STRUCT = struct.Struct('>Q')
_OID_UNPACK = _OID_STRUCT.unpack

@implementer(IPersistent)
class Persistent(object):
""" Pure Python implmentation of Persistent base class
Expand Down Expand Up @@ -574,7 +579,10 @@ def __repr__(self):

if oid is not None:
try:
oid_str = ' oid %r' % (oid,)
if isinstance(oid, bytes) and len(oid) == 8:
oid_str = ' oid 0x%x' % (_OID_UNPACK(oid)[0],)
else:
oid_str = ' oid %r' % (oid,)
except Exception as e:
oid_str = ' oid %r' % (e,)

Expand Down
46 changes: 32 additions & 14 deletions persistent/tests/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -1697,13 +1697,16 @@ class TestPersistent(self._getTargetClass()):
self.assertEqual(candidate._p_state, GHOST)
self.assertEqual(candidate.set_by_new, 1)

# The number 12345678 as a p64, 8-byte string
_PACKED_OID = b'\x00\x00\x00\x00\x00\xbcaN'
# The number 12345678 printed in hex
_HEX_OID = '0xbc614e'

def _normalize_repr(self, r):
# addresses
r = re.sub(r'0x[0-9a-fA-F]*', '0xdeadbeef', r)
r = re.sub(r'at 0x[0-9a-fA-F]*', 'at 0xdeadbeef', r)
# Python 3.7 removed the trailing , in exception reprs
r = r.replace("',)", "')")
# Python 2 doesn't have a leading b prefix for byte literals
r = r.replace("oid '", "oid b'")
return r

def _normalized_repr(self, o):
Expand All @@ -1730,12 +1733,12 @@ def __repr__(self):

def test_repr_oid_no_jar(self):
p = self._makeOne()
p._p_oid = b'12345678'
p._p_oid = self._PACKED_OID

result = self._normalized_repr(p)
self.assertEqual(
result,
"<persistent.Persistent object at 0xdeadbeef oid b'12345678'>")
"<persistent.Persistent object at 0xdeadbeef oid " + self._HEX_OID + ">")

def test_repr_no_oid_repr_jar_raises_exception(self):
p = self._makeOne()
Expand All @@ -1758,7 +1761,17 @@ def test_repr_oid_raises_exception_no_jar(self):
class BadOID(bytes):
def __repr__(self):
raise Exception("oid repr failed")
p._p_oid = BadOID(b'12345678')

# Our OID is bytes, 8 bytes long. We don't call its repr.
p._p_oid = BadOID(self._PACKED_OID)

result = self._normalized_repr(p)
self.assertEqual(
result,
"<persistent.Persistent object at 0xdeadbeef oid " + self._HEX_OID + ">")

# Anything other than 8 bytes, though, we do.
p._p_oid = BadOID(b'1234567')

result = self._normalized_repr(p)
self.assertEqual(
Expand All @@ -1772,7 +1785,7 @@ def test_repr_oid_and_jar_raise_exception(self):
class BadOID(bytes):
def __repr__(self):
raise Exception("oid repr failed")
p._p_oid = BadOID(b'12345678')
p._p_oid = BadOID(b'1234567')

class Jar(object):
def __repr__(self):
Expand Down Expand Up @@ -1806,12 +1819,17 @@ def __repr__(self):
raise BaseException("oid repr failed")
p._p_oid = BadOID(b'12345678')

# An 8 byte byte string doesn't have repr called.
repr(p)

# Anything other does.
p._p_oid = BadOID(b'1234567')
with self.assertRaisesRegex(BaseException, 'oid repr failed'):
repr(p)

def test_repr_oid_and_jar(self):
p = self._makeOne()
p._p_oid = b'12345678'
p._p_oid = self._PACKED_OID

class Jar(object):
def __repr__(self):
Expand All @@ -1822,7 +1840,7 @@ def __repr__(self):
result = self._normalized_repr(p)
self.assertEqual(
result,
"<persistent.Persistent object at 0xdeadbeef oid b'12345678' in <SomeJar>>")
"<persistent.Persistent object at 0xdeadbeef oid " + self._HEX_OID + " in <SomeJar>>")

def test__p_repr(self):
class P(self._getTargetClass()):
Expand All @@ -1842,12 +1860,12 @@ def _p_repr(self):
"<persistent.tests.test_persistence.P object at 0xdeadbeef"
" _p_repr Exception('_p_repr failed')>")

p._p_oid = b'12345678'
p._p_oid = self._PACKED_OID
result = self._normalized_repr(p)
self.assertEqual(
result,
"<persistent.tests.test_persistence.P object at 0xdeadbeef oid b'12345678'"
" _p_repr Exception('_p_repr failed')>")
"<persistent.tests.test_persistence.P object at 0xdeadbeef oid " + self._HEX_OID
+ " _p_repr Exception('_p_repr failed')>")

class Jar(object):
def __repr__(self):
Expand All @@ -1857,8 +1875,8 @@ def __repr__(self):
result = self._normalized_repr(p)
self.assertEqual(
result,
"<persistent.tests.test_persistence.P object at 0xdeadbeef oid b'12345678'"
" in <SomeJar> _p_repr Exception('_p_repr failed')>")
"<persistent.tests.test_persistence.P object at 0xdeadbeef oid " + self._HEX_OID
+ " in <SomeJar> _p_repr Exception('_p_repr failed')>")

def test__p_repr_in_instance_ignored(self):
class P(self._getTargetClass()):
Expand Down

0 comments on commit 14a1ddd

Please sign in to comment.