If there is nothing specifically registered to externalize an object, and the object itself doesn't have a usable toExternalObject() method, we go through a set of other steps to try to externalize it. One of the last is recoginizing something as a sequence, which we will externalize as a LocatedExternalList:
https://github.com/NextThought/nti.externalization/blob/59896bebe27a8289036c634ca5f1b12c66e57faf/src/nti/externalization/externalization/externalizer.py#L281-L291
That checks for the (old) zope.interface.common.sequence.IFiniteSequence interface, or a specific list of types:
https://github.com/NextThought/nti.externalization/blob/59896bebe27a8289036c634ca5f1b12c66e57faf/src/nti/externalization/externalization/externalizer.py#L70-L80
This can surprise people that write sequence-like objects, especially if they extend collections.abc.Sequence: they don't get externalized, and the reason isn't obvious (especially if the sequence-like object is just replacing a plain list or tuple). The solution right now is to declare the type as @implementer(IFiniteSequence) but that shouldn't be necessary.
We could either add collections.abc.Sequence to that list (but checking for abstract types is relatively slow, and isinstance is O(n) in the list of types), or we could take advantage of the new-and-improved ABC interfaces in zope.interface:
>>> from zope.interface.common.collections import ISequence
>>> ISequence.__sro__
(<ABCInterfaceClass zope.interface.common.collections.ISequence>,
<ABCInterfaceClass zope.interface.common.collections.IReversible>,
<ABCInterfaceClass zope.interface.common.collections.ICollection>,
<ABCInterfaceClass zope.interface.common.collections.ISized>,
<ABCInterfaceClass zope.interface.common.collections.IIterable>,
<ABCInterfaceClass zope.interface.common.collections.IContainer>,
<ABCInterfaceClass zope.interface.common.ABCInterface>,
<InterfaceClass zope.interface.Interface>)
>>> ISequence.providedBy([])
True
>>> from persistent.list import PersistentList
>>> ISequence.providedBy(PersistentList())
True
>>> ISequence.providedBy(())
True
Unfortunately, that doesn't automatically pick up new subclasses of Sequence defined after ISequence was imported, so that's not a complete solution currently:
>>> from collections.abc import Sequence
>>> class S(Sequence):
... __getitem__ = __len__ = lambda self: None
...
>>> ISequence.providedBy(S())
False
If there is nothing specifically registered to externalize an object, and the object itself doesn't have a usable
toExternalObject()method, we go through a set of other steps to try to externalize it. One of the last is recoginizing something as a sequence, which we will externalize as aLocatedExternalList:https://github.com/NextThought/nti.externalization/blob/59896bebe27a8289036c634ca5f1b12c66e57faf/src/nti/externalization/externalization/externalizer.py#L281-L291
That checks for the (old)
zope.interface.common.sequence.IFiniteSequenceinterface, or a specific list of types:https://github.com/NextThought/nti.externalization/blob/59896bebe27a8289036c634ca5f1b12c66e57faf/src/nti/externalization/externalization/externalizer.py#L70-L80
This can surprise people that write sequence-like objects, especially if they extend
collections.abc.Sequence: they don't get externalized, and the reason isn't obvious (especially if the sequence-like object is just replacing a plainlistortuple). The solution right now is to declare the type as@implementer(IFiniteSequence)but that shouldn't be necessary.We could either add
collections.abc.Sequenceto that list (but checking for abstract types is relatively slow, andisinstanceisO(n)in the list of types), or we could take advantage of the new-and-improved ABC interfaces in zope.interface:Unfortunately, that doesn't automatically pick up new subclasses of
Sequencedefined afterISequencewas imported, so that's not a complete solution currently: