Skip to content

Commit

Permalink
Merge 7889551 into d60691f
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden authored Sep 20, 2017
2 parents d60691f + 7889551 commit b4f190a
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 97 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@

- First PyPI release.
- Add support for Python 3.
- Drop support for externalizing to plists. See
https://github.com/NextThought/nti.externalization/issues/21
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
'https://docs.python.org/': None,
#'https://ntiwref.readthedocs.io/en/latest': None,
'https://persistent.readthedocs.io/en/latest': None,
'https://zopecomponent.readthedocs.io/en/latest': None,
'https://zodb.readthedocs.io/en/latest': None,
}

extlinks = {
Expand Down
1 change: 0 additions & 1 deletion src/nti/externalization/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

<utility factory=".representation.JsonRepresenter" />
<utility factory=".representation.YamlRepresenter" />
<utility factory=".representation.PlistRepresenter" />

<!-- Offer to adapt strings to dates -->
<!-- TODO: This is a bit ad-hoc. Surely there's a more formal set
Expand Down
13 changes: 4 additions & 9 deletions src/nti/externalization/externalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,7 @@ def _to_external_object_state(obj, state, top_level=False, decorate=True,
result.append(ext_obj)
result = state.registry.getAdapter(result,
ILocatedExternalSequence)
# PList doesn't support None values, JSON does. The closest
# coersion I can think of is False.
elif obj is None:
if state.coerceNone:
result = False
else:
elif obj is not None:
# Otherwise, we probably won't be able to JSON-ify it.
# TODO: Should this live here, or at a higher level where the ultimate
# external target/use-case is known?
Expand Down Expand Up @@ -307,7 +302,6 @@ def _to_external_object_state(obj, state, top_level=False, decorate=True,


def toExternalObject(obj,
coerceNone=False,
name=_NotGiven,
registry=component,
catch_components=(),
Expand Down Expand Up @@ -677,8 +671,9 @@ def _clean(m):
"nti.externalization.persistence",
"NoPickle")

EXT_FORMAT_JSON = 'json' #: Constant requesting JSON format data
EXT_FORMAT_PLIST = 'plist' #: Constant requesting PList (XML) format data
#: Constant requesting JSON format data
EXT_FORMAT_JSON = 'json'


zope.deferredimport.deprecatedFrom(
"Import from .representation",
Expand Down
58 changes: 29 additions & 29 deletions src/nti/externalization/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,29 @@
"""
Externalization Interfaces
.. $Id$
"""

from __future__ import print_function, absolute_import, division
__docformat__ = "restructuredtext en"

logger = __import__('logging').getLogger(__name__)

from zope import interface

from zope.component.interfaces import IFactory

from zope.interface.common.mapping import IFullMapping

from zope.interface.common.sequence import ISequence

from zope.location import ILocation

from zope.interface.interfaces import ObjectEvent
from zope.interface.interfaces import IObjectEvent

from zope.lifecycleevent import ObjectModifiedEvent
from zope.lifecycleevent import IObjectModifiedEvent


# pylint:disable=inherit-non-class,no-method-argument,no-self-argument


class StandardExternalFields(object):
"""
Expand Down Expand Up @@ -92,7 +97,7 @@ class INonExternalizableReplacer(interface.Interface):
some object cannot be externalized.
"""

def __call__(obj):
def __call__(obj): # pylint:disable=signature-differs
"""
:return: An externalized object to replace the given object. Possibly the
given object itself if some higher level will handle it.
Expand Down Expand Up @@ -126,10 +131,11 @@ def decorateExternalObject(origial, external):
though this is not guaranteed).
:param original: The object that is being externalized.
Passed to facilitate using non-classes as decorators.
Passed to facilitate using non-classes as decorators.
:param external: The externalization of that object, produced
by an implementation of :class:`~nti.externalization.interfaces.IInternalObjectExternalizer` or
default rules.
by an implementation of
:class:`~nti.externalization.interfaces.IInternalObjectExternalizer`
or default rules.
:return: Undefined.
"""

Expand All @@ -153,11 +159,11 @@ def decorateExternalMapping(original, external):
Decorate the externalized object mapping.
:param original: The object that is being externalized. Passed
to facilitate using non-classes as decorators.
to facilitate using non-classes as decorators.
:param external: The externalization of that object, an
:class:`~nti.externalization.interfaces.ILocatedExternalMapping`,
produced by an implementation of
:class:`~nti.externalization.interfaces.IInternalObjectExternalizer` or default rules.
:class:`~nti.externalization.interfaces.ILocatedExternalMapping`,
produced by an implementation of
:class:`~nti.externalization.interfaces.IInternalObjectExternalizer` or default rules.
:return: Undefined.
"""

Expand Down Expand Up @@ -186,8 +192,10 @@ class ILocatedExternalSequence(IExternalizedObject, ILocation, ISequence):
@interface.implementer(ILocatedExternalMapping)
class LocatedExternalDict(dict):
"""
A dictionary that implements :class:`~nti.externalization.interfaces.ILocatedExternalMapping`. Returned
by :func:`~nti.externalization.externalization.to_standard_external_dictionary`.
A dictionary that implements
:class:`~nti.externalization.interfaces.ILocatedExternalMapping`.
Returned by
:func:`~nti.externalization.externalization.to_standard_external_dictionary`.
This class is not :class:`.IContentTypeAware`, and it indicates so explicitly by declaring a
`mime_type` value of None.
Expand All @@ -202,8 +210,10 @@ class LocatedExternalDict(dict):
@interface.implementer(ILocatedExternalSequence)
class LocatedExternalList(list):
"""
A list that implements :class:`~nti.externalization.interfaces.ILocatedExternalSequence`. Returned
by :func:`~nti.externalization.externalization.to_external_object`.
A list that implements
:class:`~nti.externalization.interfaces.ILocatedExternalSequence`.
Returned by
:func:`~nti.externalization.externalization.to_external_object`.
This class is not :class:`.IContentTypeAware`, and it indicates so explicitly by declaring a
`mimeType` value of None.
Expand Down Expand Up @@ -260,9 +270,6 @@ class IExternalObjectIO(IExternalObjectRepresenter,
#: Constant requesting JSON format data
EXT_REPR_JSON = u'json'

#: Constant requesting PList (XML) format data
EXT_REPR_PLIST = u'plist'

#: Constant requesting YAML format data
EXT_REPR_YAML = u'yaml'

Expand Down Expand Up @@ -290,8 +297,9 @@ class IExternalizedObjectFactoryFinder(interface.Interface):

def find_factory(externalized_object):
"""
Given an externalized object, return a :class:`zope.component.interfaces.IFactory` to create the proper
internal types.
Given an externalized object, return a
:class:`zope.component.interfaces.IFactory` to create the
proper internal types.
:return: An :class:`zope.component.interfaces.IFactory`, or :const:`None`.
"""
Expand Down Expand Up @@ -347,14 +355,6 @@ class IInternalObjectIO(IInternalObjectExternalizer, IInternalObjectUpdater):
in external forms.
"""


from zope.interface.interfaces import ObjectEvent
from zope.interface.interfaces import IObjectEvent

from zope.lifecycleevent import ObjectModifiedEvent
from zope.lifecycleevent import IObjectModifiedEvent


class IObjectWillUpdateFromExternalEvent(IObjectEvent):
"""
An object will be updated from an external value.
Expand Down
3 changes: 1 addition & 2 deletions src/nti/externalization/internalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,7 @@ def update_from_external_object(containedObject, externalObject,
``v`` is the newly-updated value, and ``x`` is the external object used to update ``v``.
:param callable pre_hook: If given, called with the before update_from_external_object is
called for every nested object. Signature ``f(k,x)`` where ``k`` is either the key name,
or None in the case of a sequence and ``x`` is the external object
or None in the case of a sequence and ``x`` is the external object
:return: `containedObject` after updates from `externalObject`
"""

Expand Down
53 changes: 6 additions & 47 deletions src/nti/externalization/representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@
"""
External representation support.
.. $Id$
"""

from __future__ import print_function, absolute_import, division
__docformat__ = "restructuredtext en"

import collections
import plistlib

import six
from six import iteritems

from zope import component
from zope import interface
Expand All @@ -22,7 +16,6 @@

from nti.externalization.interfaces import EXT_REPR_JSON
from nti.externalization.interfaces import EXT_REPR_YAML
from nti.externalization.interfaces import EXT_REPR_PLIST

from nti.externalization.interfaces import IExternalObjectIO
from nti.externalization.interfaces import IExternalObjectRepresenter
Expand All @@ -36,9 +29,11 @@
def to_external_representation(obj, ext_format=EXT_REPR_JSON,
name=_NotGiven, registry=component):
"""
Transforms (and returns) the `obj` into its external (string) representation.
Transforms (and returns) the `obj` into its external (string)
representation.
:param ext_format: One of :const:`EXT_FORMAT_JSON` or :const:`EXT_FORMAT_PLIST`.
:param ext_format: One of :const:`nti.externalization.interfaces.EXT_REPR_JSON` or
:const:`nti.externalization.interfaces.EXT_REPR_YAML`.
"""
# It would seem nice to be able to do this in one step during
# the externalization process itself, but we would wind up traversing
Expand All @@ -57,39 +52,6 @@ def to_json_representation(obj):
return to_external_representation(obj, EXT_REPR_JSON)


# Plist



# The API changed in Python 3.4
_plist_dump = getattr(plistlib, 'dump', None) or plistlib.writePlist
_plist_dumps = getattr(plistlib, 'dumps', None) or plistlib.writePlistToString


@interface.named(EXT_REPR_PLIST)
@interface.implementer(IExternalObjectRepresenter)
class PlistRepresenter(object):

def stripNoneFromExternal(self, obj):
"""
Given an already externalized object, strips ``None`` values.
"""
if isinstance(obj, (list, tuple)):
obj = [self.stripNoneFromExternal(x) for x in obj if x is not None]
elif isinstance(obj, collections.Mapping):
obj = {k: self.stripNoneFromExternal(v)
for k, v in iteritems(obj)
if v is not None and k is not None}
return obj

def dump(self, obj, fp=None):
ext = self.stripNoneFromExternal(obj)
if fp is not None:
_plist_dump(ext, fp)
else:
return _plist_dumps(ext)


# JSON


Expand Down Expand Up @@ -143,7 +105,7 @@ def load(self, stream):
# Depending on whether the simplejson C speedups are active, we can still
# get back a non-unicode string if the object was a naked string. (If the python
# version is used, it returns unicode; the C version returns str.)
if isinstance(value, six.binary_type):
if isinstance(value, bytes):
# we know it's simple ascii or it would have produced unicode
value = to_unicode(value)
return value
Expand Down Expand Up @@ -199,10 +161,7 @@ def load(self, stream):
from ZODB.POSException import ConnectionStateError


def make_repr(default=None):
if default is None:
def default(self):
return "%s().__dict__.update( %s )" % (self.__class__.__name__, self.__dict__)
def make_repr(default=lambda self: "%s().__dict__.update( %s )" % (self.__class__.__name__, self.__dict__)):

def __repr__(self):
try:
Expand Down
12 changes: 3 additions & 9 deletions src/nti/externalization/tests/test_externalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import sys
import json
import plistlib

import unittest

try:
Expand All @@ -43,8 +43,6 @@
from nti.externalization.datastructures import ExternalizableInstanceDict
from nti.externalization.datastructures import ExternalizableDictionaryMixin

from nti.externalization.externalization import EXT_FORMAT_JSON
from nti.externalization.externalization import EXT_FORMAT_PLIST

from nti.externalization.externalization import _manager
from nti.externalization.externalization import _DevmodeNonExternalizableObjectReplacer
Expand All @@ -57,6 +55,7 @@
from nti.externalization.externalization import to_standard_external_dictionary

from nti.externalization.interfaces import EXT_REPR_YAML
from nti.externalization.interfaces import EXT_REPR_JSON
from nti.externalization.interfaces import LocatedExternalList
from nti.externalization.interfaces import LocatedExternalDict

Expand Down Expand Up @@ -136,13 +135,8 @@ def test_hookable(self):
def test_to_external_representation_none_handling(self):
d = {'a': 1, 'None': None}
# JSON keeps None
assert_that(json.loads(to_external_representation(d, EXT_FORMAT_JSON)),
assert_that(json.loads(to_external_representation(d, EXT_REPR_JSON)),
is_(d))
# PList strips it
# The api changed in Python 3.4
read_plist = getattr(plistlib, 'loads', getattr(plistlib, 'readPlistFromString', None))
assert_that(read_plist(to_external_representation(d, EXT_FORMAT_PLIST)),
is_({'a': 1}))

def test_to_external_representation_yaml(self):
l = LocatedExternalList()
Expand Down

0 comments on commit b4f190a

Please sign in to comment.