Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make _ext_getattr handle a default, and add _ext_replacement_getattr … #75

Merged
merged 1 commit into from Jul 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()