Skip to content

Commit

Permalink
Merge b7a7f7c into dca4a8a
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Jul 28, 2018
2 parents dca4a8a + b7a7f7c commit 1f55c34
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -11,6 +11,10 @@
Document the intended API, ``_ext_replacement()``. See `issue 73
<https://github.com/NextThought/nti.externalization/issues/73>`_.

- Make ``AbstractDynamicObjectIO._ext_getattr`` handle a default
value, and add ``_ext_replacement_getattr`` for when it will only
be called once. See `issue 73
<https://github.com/NextThought/nti.externalization/issues/73>`_.

1.0.0a3 (2018-07-28)
====================
Expand Down
3 changes: 2 additions & 1 deletion src/nti/externalization/_datastructures.pxd
Expand Up @@ -42,7 +42,8 @@ cdef class AbstractDynamicObjectIO(ExternalizableDictionaryMixin):

cpdef _ext_all_possible_keys(self)
cpdef _ext_setattr(self, ext_self, k, value)
cpdef _ext_getattr(self, ext_self, k)
cpdef _ext_getattr(self, ext_self, k, default=*)
cpdef _ext_replacement_getattr(self, k, default=*)

@cython.locals(
k=str # cython can optimize k.startswith('constantstring')
Expand Down
37 changes: 28 additions & 9 deletions src/nti/externalization/datastructures.py
Expand Up @@ -159,13 +159,28 @@ def _ext_all_possible_keys(self):
def _ext_setattr(self, ext_self, k, value):
raise NotImplementedError()

def _ext_getattr(self, ext_self, k):
def _ext_getattr(self, ext_self, k, default=NotGiven):
"""
Return the attribute of the `ext_self` object with the external name `k`.
If the attribute does not exist, should raise (typically :exc:`AttributeError`)
_ext_getattr(object, name[, default]) -> value
Return the attribute of the *ext_self* object with the internal name *name*.
If the attribute does not exist, should raise (typically :exc:`AttributeError`),
unless *default* is given, in which case it returns that.
.. versionchanged:: 1.0a4
Add the *default* argument.
"""
raise NotImplementedError()

def _ext_replacement_getattr(self, name, default=NotGiven):
"""
Like `_ext_getattr`, but automatically fills in `_ext_replacement`
for the *ext_self* argument.
.. versionadded:: 1.0a4
"""
return self._ext_getattr(self._ext_replacement(), name, default)

def _ext_keys(self):
"""
Return only the names of attributes that should be externalized.
Expand Down Expand Up @@ -320,11 +335,13 @@ class ExternalizableInstanceDict(AbstractDynamicObjectIO):
def _ext_all_possible_keys(self):
return frozenset(self._ext_replacement().__dict__.keys())

def _ext_getattr(self, ext_self, k):
return getattr(ext_self, k)
def _ext_getattr(self, ext_self, k, default=NotGiven):
if default is NotGiven:
return getattr(ext_self, k)
return getattr(ext_self, k, default)

def _ext_setattr(self, ext_self, k, v):
setattr(ext_self, k, v)
def _ext_setattr(self, ext_self, k, value):
setattr(ext_self, k, value)

def _ext_accept_update_key(self, k, ext_self, ext_keys):
return (
Expand Down Expand Up @@ -450,9 +467,11 @@ def _ext_all_possible_keys(self):
])
return cache.ext_all_possible_keys

def _ext_getattr(self, ext_self, k):
def _ext_getattr(self, ext_self, k, default=NotGiven):
# TODO: Should this be directed through IField.get?
return getattr(ext_self, k)
if default is NotGiven:
return getattr(ext_self, k)
return getattr(ext_self, k, default)

def _ext_setattr(self, ext_self, k, value):
validate_named_field_value(ext_self, self._iface, k, value)()
Expand Down
43 changes: 39 additions & 4 deletions src/nti/externalization/tests/test_datastructures.py
Expand Up @@ -28,9 +28,31 @@
# pylint: disable=inherit-non-class,attribute-defined-outside-init,abstract-method
# pylint:disable=no-value-for-parameter,too-many-function-args

class TestAbstractDynamicObjectIO(ExternalizationLayerTest):
class CommonTestMixins(object):

def _makeOne(self):
def _makeOne(self, context=None):
raise NotImplementedError()

def test_ext_getattr_default(self):
io = self._makeOne()
assert_that(io._ext_getattr(self, 'no_such_attribute', None),
is_(none()))

def test_ext_getattr_no_default(self):
io = self._makeOne()
get = io._ext_getattr
with self.assertRaises(AttributeError):
get(self, 'no_such_attribute')

def test_ext_replacement_getattr_default(self):
io = self._makeOne()
assert_that(io._ext_replacement_getattr('no_such_attribute', None),
is_(none()))

class TestAbstractDynamicObjectIO(CommonTestMixins,
ExternalizationLayerTest):

def _makeOne(self, context=None):
from nti.externalization.datastructures import AbstractDynamicObjectIO
class IO(AbstractDynamicObjectIO):
_ext_setattr = staticmethod(setattr)
Expand Down Expand Up @@ -139,14 +161,15 @@ def test_update_takes_external_fields(self):
assert_that(inst, has_property('id', 'id'))

class TestInterfaceObjectIO(CleanUp,
CommonTestMixins,
unittest.TestCase):

def _getTargetClass(self):
from nti.externalization.datastructures import InterfaceObjectIO
return InterfaceObjectIO

def _makeOne(self, ext_self, *args, **kwargs):
return self._getTargetClass()(ext_self, *args, **kwargs)
def _makeOne(self, context=None, iface_upper_bound=interface.Interface):
return self._getTargetClass()(context, iface_upper_bound=iface_upper_bound)

def test_find_primitive_keys_dne(self):
ext_self = self
Expand Down Expand Up @@ -479,3 +502,15 @@ class Consistent(object):

io = IO(Consistent())
assert_that(io, has_property('_iface', IGrandChild))


class TestExternalizableInstanceDict(CommonTestMixins,
unittest.TestCase):

def _makeOne(self, context=None):
from nti.externalization.datastructures import ExternalizableInstanceDict
class FUT(ExternalizableInstanceDict):
def _ext_replacement(self):
return context

return FUT()

0 comments on commit 1f55c34

Please sign in to comment.