Commit 99c9462e authored by Jason Madden's avatar Jason Madden

Always format 8-byte oids as ints in hexadecimal.

Fixes #95

Built on #97 as they touch the same code.
parent f16a5aa0
......@@ -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)
------------------
......
......@@ -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)
{
......@@ -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)
......
......@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import struct
from zope.interface import implementer
......@@ -52,6 +52,11 @@ SPECIAL_NAMES = ('__class__',
# 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
......@@ -574,7 +579,10 @@ class Persistent(object):
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,)
......
......@@ -1697,13 +1697,16 @@ class _Persistent_Base(object):
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):
......@@ -1730,12 +1733,12 @@ class _Persistent_Base(object):
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()
......@@ -1758,7 +1761,17 @@ class _Persistent_Base(object):
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(
......@@ -1772,7 +1785,7 @@ class _Persistent_Base(object):
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):
......@@ -1806,12 +1819,17 @@ class _Persistent_Base(object):
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):
......@@ -1822,7 +1840,7 @@ class _Persistent_Base(object):
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()):
......@@ -1842,12 +1860,12 @@ class _Persistent_Base(object):
"<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):
......@@ -1857,8 +1875,8 @@ class _Persistent_Base(object):
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()):
......
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