Skip to content

Commit

Permalink
Make @WithRerp respect the new default repr in persistent 4.4.
Browse files Browse the repository at this point in the history
Fixes #87
  • Loading branch information
jamadden committed Aug 22, 2018
1 parent 32e5cac commit 7385d08
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 9 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Expand Up @@ -6,7 +6,8 @@
1.0.0a11 (unreleased)
=====================

- Nothing changed yet.
- The ``@WithRepr`` decorator takes into account the updated default
repr of Persistent objects with persistent 4.4 and doesn't hide it.


1.0.0a10 (2018-08-21)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -163,7 +163,7 @@ def _c(m):
'BTrees',
'isodate',
'nti.schema',
'persistent',
'persistent >= 4.4.0',
'PyYAML',
'pytz',
'simplejson',
Expand Down
38 changes: 31 additions & 7 deletions src/nti/externalization/representation.py
Expand Up @@ -15,6 +15,7 @@

import warnings

from persistent import Persistent
from ZODB.POSException import POSError
import simplejson
import yaml
Expand Down Expand Up @@ -196,8 +197,35 @@ def __repr__(self):

return __repr__

class _PReprException(Exception):
# Raised for the sole purpose of carrying a smuggled
# repr.
def __init__(self, value):
self.value = value

def WithRepr(default=object()):
def __repr__(self):
return self.value


def _add_repr_to_cls(cls, default=_default_repr):
if issubclass(cls, Persistent):
# Persistent 4.4 includes the OID and JAR repr
# by default, and catches all the exceptions that our
# make_repr would catch, handling them much better. We only want the
# __dict__ in there by default, though
if default is _default_repr:
default = lambda self: repr(self.__dict__)

def _p_repr(self):
raise _PReprException(default(self))

cls._p_repr = _p_repr
else:
cls.__repr__ = make_repr(default)

return cls

def WithRepr(default=_default_repr):
"""
A class decorator factory to give a ``__repr__`` to
the object. Useful for persistent objects.
Expand All @@ -208,12 +236,8 @@ def WithRepr(default=object()):
# If we get one argument that is a type, we were
# called bare (@WithRepr), so decorate the type
if isinstance(default, type):
default.__repr__ = make_repr()
return default
return _add_repr_to_cls(default)

# If we got None or anything else, we were called as a factory,
# so return a decorator
def d(cls):
cls.__repr__ = make_repr(default)
return cls
return d
return lambda cls: _add_repr_to_cls(cls, default)
68 changes: 68 additions & 0 deletions src/nti/externalization/tests/test_representation.py
Expand Up @@ -8,8 +8,11 @@
from __future__ import print_function

# stdlib imports
import re
import unittest

from persistent import Persistent

import fudge

from . import ExternalizationLayerTest
Expand All @@ -23,6 +26,7 @@

# disable: accessing protected members, too many methods
# pylint: disable=W0212,R0904
# pylint:disable=attribute-defined-outside-init, useless-object-inheritance

class TestWithRepr(unittest.TestCase):

Expand Down Expand Up @@ -82,6 +86,70 @@ class Foo(object):
is_("<nti.externalization.tests.test_representation.Foo("
"AttributeError())>"))

def _normalize_repr(self, r):
# Pure-python vs C
r = r.replace('nti.externalization.tests.test_representation.', '')
# addresses
r = re.sub(r'0x[0-9a-fA-F]*', '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):
return self._normalize_repr(repr(o))

def test_persistent_subclass_default(self):
@representation.WithRepr
class Foo(Persistent):
pass

o = Foo()
r = self._normalized_repr(o)

assert_that(r,
is_('<Foo object at 0xdeadbeef _p_repr {}>'))

o._p_oid = b'12345678'
r = self._normalized_repr(o)

assert_that(r,
is_("<Foo object at 0xdeadbeef oid b'12345678' _p_repr {}>"))

o.a = 1

r = self._normalized_repr(o)

assert_that(r,
is_("<Foo object at 0xdeadbeef oid b'12345678' _p_repr {'a': 1}>"))

def test_persistent_subclass_custom(self):
@representation.WithRepr(lambda s: 'Hi')
class Foo(Persistent):
pass

o = Foo()
r = self._normalized_repr(o)
assert_that(r,
is_('<Foo object at 0xdeadbeef _p_repr Hi>'))

def test_persistent_subclass_raise(self):

def raise_(self):
raise AttributeError()

@representation.WithRepr(raise_)
class Foo(Persistent):
pass

o = Foo()
r = self._normalized_repr(o)

assert_that(r,
is_('<Foo object at 0xdeadbeef _p_repr AttributeError()>'))


class TestYaml(unittest.TestCase):

def test_unicode(self):
Expand Down

0 comments on commit 7385d08

Please sign in to comment.