Skip to content

Commit

Permalink
Merge f9292d4 into 5c6e755
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Jul 30, 2018
2 parents 5c6e755 + f9292d4 commit ce3d9b1
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -8,6 +8,9 @@

- Nothing changed yet.

- ``ExternalizableInstanceDict`` no longer inherits from
``AbstractDynamicIO``, it just implements the same interface (with
the exception of many of the ``_ext`` methods). This class is deprecated.

1.0.0a4 (2018-07-30)
====================
Expand Down
10 changes: 8 additions & 2 deletions src/nti/externalization/_datastructures.pxd
Expand Up @@ -56,9 +56,15 @@ cdef class AbstractDynamicObjectIO(ExternalizableDictionaryMixin):
cpdef find_factory_for_named_value(self, key, value, registry)
cdef _updateFromExternalObject(self, parsed)

# cdef class _ExternalizableInstanceDict(AbstractDynamicObjectIO):
# cdef dict __dict__

cdef class ExternalizableInstanceDict(AbstractDynamicObjectIO):
pass

# This class is sometimes subclassed while also subclassing persistent.Persistent,
# which doesn't work if it's an extension class, so we can't do that. It's rarely used,
# so performance doesn't matter as much.
#cdef class ExternalizableInstanceDict(AbstractDynamicObjectIO):
# pass

cdef class InterfaceObjectIO(AbstractDynamicObjectIO):
cdef readonly _ext_self
Expand Down
84 changes: 74 additions & 10 deletions src/nti/externalization/datastructures.py
Expand Up @@ -11,7 +11,7 @@
# There are a *lot* of fixme (XXX and the like) in this file.
# Turn those off in general so we can see through the noise.
# pylint:disable=fixme

# pylint:disable=keyword-arg-before-vararg

# stdlib imports
import numbers
Expand All @@ -26,6 +26,7 @@

from nti.schema.interfaces import find_most_derived_interface

from .interfaces import IInternalObjectIO
from .interfaces import IInternalObjectIOFinder
from .interfaces import IAnonymousObjectFactory
from .interfaces import StandardInternalFields
Expand Down Expand Up @@ -90,6 +91,11 @@ def _ext_standard_external_dictionary(self, replacement, mergeFrom=None, **kwarg
decorate_callback=kwargs.get('decorate_callback', NotGiven))

def toExternalDictionary(self, mergeFrom=None, *unused_args, **kwargs):
"""
Produce the standard external dictionary for this object.
Uses `_ext_replacement`.
"""
return self._ext_standard_external_dictionary(self._ext_replacement(),
mergeFrom=mergeFrom,
**kwargs)
Expand Down Expand Up @@ -317,21 +323,34 @@ def _updateFromExternalObject(self, parsed):
interface.classImplements(AbstractDynamicObjectIO, IInternalObjectIOFinder)


class ExternalizableInstanceDict(AbstractDynamicObjectIO):
"""
Externalizes to a dictionary containing the members of ``__dict__``
that do not start with an underscore.
Meant to be used as a super class; also can be used as an external object superclass.
"""
class _ExternalizableInstanceDict(AbstractDynamicObjectIO):

# TODO: there should be some better way to customize this if desired (an explicit list)
# TODO: Play well with __slots__? ZODB supports slots, but doesn't recommend them
# TODO: This won't evolve well. Need something more sophisticated,
# probably a meta class.

_update_accepts_type_attrs = False

def __init__(self, context):
self.context = context
for name in (
'_update_accepts_type_attrs',
'__external_use_minimal_base__',
'_excluded_in_ivars_',
'_excluded_out_ivars_',
'_ext_primitive_out_ivars_',
'_prefer_oid_'
):
try:
v = getattr(context, name)
except AttributeError:
continue
else:
setattr(self, name, v)

def _ext_replacement(self):
return self.context

def _ext_all_possible_keys(self):
return frozenset(self._ext_replacement().__dict__.keys())

Expand All @@ -345,13 +364,58 @@ def _ext_setattr(self, ext_self, k, value):

def _ext_accept_update_key(self, k, ext_self, ext_keys):
return (
super(ExternalizableInstanceDict, self)._ext_accept_update_key(k, ext_self, ext_keys)
super(_ExternalizableInstanceDict, self)._ext_accept_update_key(k, ext_self, ext_keys)
or (self._update_accepts_type_attrs and hasattr(ext_self, k))
)

__repr__ = make_repr()


class ExternalizableInstanceDict(object):
"""
Externalizes to a dictionary containing the members of
``__dict__`` that do not start with an underscore.
Meant to be used as a super class; also can be used as an external
object superclass.
.. versionchanged:: 1.0a5
No longer extends `AbstractDynamicObjectIO`, just delegates to it.
.. deprecated:: 1.0a5
Prefer interfaces.
"""
# pylint:disable=protected-access
_update_accepts_type_attrs = _ExternalizableInstanceDict._update_accepts_type_attrs
__external_use_minimal_base__ = _ExternalizableInstanceDict.__external_use_minimal_base__
_excluded_out_ivars_ = AbstractDynamicObjectIO._excluded_out_ivars_
_excluded_in_ivars_ = AbstractDynamicObjectIO._excluded_in_ivars_
_ext_primitive_out_ivars_ = AbstractDynamicObjectIO._ext_primitive_out_ivars_
_prefer_oid_ = AbstractDynamicObjectIO._prefer_oid_

def _ext_replacement(self):
"See `ExternalizableDictionaryMixin._ext_replacement`."
return self

def __make_io(self):
return _ExternalizableInstanceDict(self._ext_replacement())

def __getattr__(self, name):
# here if we didn't have the attribute. Does our IO?
return getattr(self.__make_io(), name)

def updateFromExternalObject(self, parsed, *unused_args, **unused_kwargs):
"See `~.IInternalObjectIO.updateFromExternalObject`"
self.__make_io().updateFromExternalObject(parsed)

def toExternalObject(self, mergeFrom=None, *args, **kwargs):
"See `~.IInternalObjectIO.toExternalObject`. Calls `toExternalDictionary`."
return self.toExternalDictionary(mergeFrom, *args, **kwargs)

def toExternalDictionary(self, mergeFrom=None, *unused_args, **kwargs):
"See `ExternalizableDictionaryMixin.toExternalDictionary`"
return self.__make_io().toExternalDictionary(mergeFrom)

interface.classImplements(ExternalizableInstanceDict, IInternalObjectIO)

_primitives = six.string_types + (numbers.Number, bool)

Expand Down
13 changes: 13 additions & 0 deletions src/nti/externalization/tests/test_datastructures.py
Expand Up @@ -514,3 +514,16 @@ def _ext_replacement(self):
return context

return FUT()


def test_can_also_subclass_persistent(self):
from nti.externalization.datastructures import ExternalizableInstanceDict
from persistent import Persistent

class Base(ExternalizableInstanceDict):
pass

class P(Base, Persistent):
pass

self.assertIsNotNone(P)

0 comments on commit ce3d9b1

Please sign in to comment.